diff --git a/package-lock.json b/package-lock.json index 23d2ea4..8d128af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,23 +8,24 @@ "name": "newechoes", "version": "0.0.1", "dependencies": { - "@astrojs/mdx": "^4.1.0", - "@astrojs/node": "^9.1.2", - "@astrojs/react": "^4.2.1", - "@astrojs/sitemap": "^3.2.1", - "@astrojs/vercel": "^8.1.1", + "@astrojs/mdx": "^4.2.2", + "@astrojs/node": "^9.1.3", + "@astrojs/react": "^4.2.2", + "@astrojs/sitemap": "^3.3.0", + "@astrojs/vercel": "^8.1.3", "@tailwindcss/vite": "^4.0.9", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", - "astro": "^5.4.2", + "@types/three": "^0.174.0", + "astro": "^5.5.5", "cheerio": "^1.0.0-rc.12", - "echarts": "^5.6.0", "node-fetch": "^3.3.0", "octokit": "^3.1.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-masonry-css": "^1.0.16", - "tailwindcss": "^4.0.9" + "tailwindcss": "^4.0.9", + "three": "^0.174.0" }, "devDependencies": { "@tailwindcss/typography": "^0.5.16", @@ -46,24 +47,24 @@ } }, "node_modules/@astrojs/compiler": { - "version": "2.10.4", - "resolved": "https://registry.npmmirror.com/@astrojs/compiler/-/compiler-2.10.4.tgz", - "integrity": "sha512-86B3QGagP99MvSNwuJGiYSBHnh8nLvm2Q1IFI15wIUJJsPeQTO3eb2uwBmrqRsXykeR/mBzH8XCgz5AAt1BJrQ==", + "version": "2.11.0", + "resolved": "https://registry.npmmirror.com/@astrojs/compiler/-/compiler-2.11.0.tgz", + "integrity": "sha512-zZOO7i+JhojO8qmlyR/URui6LyfHJY6m+L9nwyX5GiKD78YoRaZ5tzz6X0fkl+5bD3uwlDHayf6Oe8Fu36RKNg==", "license": "MIT" }, "node_modules/@astrojs/internal-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmmirror.com/@astrojs/internal-helpers/-/internal-helpers-0.6.0.tgz", - "integrity": "sha512-XgHIJDQaGlFnTr0sDp1PiJrtqsWzbHP2qkTU+JpQ8SnBewKP2IKOe/wqCkl0CyfyRXRu3TSWu4t/cpYMVfuBNA==", + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/@astrojs/internal-helpers/-/internal-helpers-0.6.1.tgz", + "integrity": "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A==", "license": "MIT" }, "node_modules/@astrojs/markdown-remark": { - "version": "6.2.0", - "resolved": "https://registry.npmmirror.com/@astrojs/markdown-remark/-/markdown-remark-6.2.0.tgz", - "integrity": "sha512-LUDjgd9p1yG0qTFSocaj3GOLmZs8Hsw/pNtvqzvNY58Acebxvb/46vDO/e/wxYgsKgIfWS+p+ZI5SfOjoVrbCg==", + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/@astrojs/markdown-remark/-/markdown-remark-6.3.1.tgz", + "integrity": "sha512-c5F5gGrkczUaTVgmMW9g1YMJGzOtRvjjhw6IfGuxarM6ct09MpwysP10US729dy07gg8y+ofVifezvP3BNsWZg==", "license": "MIT", "dependencies": { - "@astrojs/internal-helpers": "0.6.0", + "@astrojs/internal-helpers": "0.6.1", "@astrojs/prism": "3.2.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", @@ -73,11 +74,11 @@ "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.0", + "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-smartypants": "^3.0.2", - "shiki": "^1.29.2", + "shiki": "^3.0.0", "smol-toml": "^1.3.1", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", @@ -87,20 +88,20 @@ } }, "node_modules/@astrojs/mdx": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/@astrojs/mdx/-/mdx-4.1.0.tgz", - "integrity": "sha512-M7BaYhVTT7Q/iS2EoEaUngQnN+D2jPCWmNS1TIY31bDyz3MOf+dZmuqODJOEUdBBAASkQE+MhzyPds/N2o6csw==", + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/@astrojs/mdx/-/mdx-4.2.2.tgz", + "integrity": "sha512-nWDvuCPenxoxbog3YK3yVWF3Jw7Lq1+ziWSAOc9fy6zAUbPDSr2bt3c6r6+oa1ll0miCQByj5UVts6eJvN/y+g==", "license": "MIT", "dependencies": { - "@astrojs/markdown-remark": "6.2.0", + "@astrojs/markdown-remark": "6.3.1", "@mdx-js/mdx": "^3.1.0", - "acorn": "^8.14.0", + "acorn": "^8.14.1", "es-module-lexer": "^1.6.0", "estree-util-visit": "^2.0.0", - "hast-util-to-html": "^9.0.4", + "hast-util-to-html": "^9.0.5", "kleur": "^4.1.5", "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.0", + "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.4", "unist-util-visit": "^5.0.0", @@ -114,12 +115,12 @@ } }, "node_modules/@astrojs/node": { - "version": "9.1.2", - "resolved": "https://registry.npmmirror.com/@astrojs/node/-/node-9.1.2.tgz", - "integrity": "sha512-MsKi741hLkRqzdtIqbrj82wmB+mQfKuSLD++hQZVBd5kU8FBNnzscM8F2rfR+KMtXSMxwLVVVT9MQ1x4rseAkg==", + "version": "9.1.3", + "resolved": "https://registry.npmmirror.com/@astrojs/node/-/node-9.1.3.tgz", + "integrity": "sha512-YcVxEmeZU8khNdrPYNPN3j//4tYPM+Pw6CthAJ6VE/bw65qEX7ErMRApalY2tibc3YhCeHMmsO9rXGhyW0NNyA==", "license": "MIT", "dependencies": { - "@astrojs/internal-helpers": "0.6.0", + "@astrojs/internal-helpers": "0.6.1", "send": "^1.1.0", "server-destroy": "^1.0.1" }, @@ -140,14 +141,14 @@ } }, "node_modules/@astrojs/react": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/@astrojs/react/-/react-4.2.1.tgz", - "integrity": "sha512-g0P6zxG7RPHNcbmMB15dJJ83+ApBVFBcgnf6BnMz/PVXM150Pa1vYKeuTcWhERqLNgmpI2uXuch5MecIhrUlqQ==", + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/@astrojs/react/-/react-4.2.2.tgz", + "integrity": "sha512-js5tP5/zKAcbzo5FBa5ykhiicBy3JMtzK1FP+vbu52AJA1us41OZnyjiujICC7TI/c8Ood0kNDJcgNRqrLQrnw==", "license": "MIT", "dependencies": { "@vitejs/plugin-react": "^4.3.4", "ultrahtml": "^1.5.3", - "vite": "^6.2.0" + "vite": "^6.2.3" }, "engines": { "node": "^18.17.1 || ^20.3.0 || >=22.0.0" @@ -160,14 +161,14 @@ } }, "node_modules/@astrojs/sitemap": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/@astrojs/sitemap/-/sitemap-3.2.1.tgz", - "integrity": "sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA==", + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/@astrojs/sitemap/-/sitemap-3.3.0.tgz", + "integrity": "sha512-nYE4lKQtk+Kbrw/w0G0TTgT724co0jUsU4tPlHY9au5HmTBKbwiCLwO/15b1/y13aZ4Kr9ZbMeMHlXuwn0ty4Q==", "license": "MIT", "dependencies": { "sitemap": "^8.0.0", "stream-replace-string": "^2.0.0", - "zod": "^3.23.8" + "zod": "^3.24.2" } }, "node_modules/@astrojs/telemetry": { @@ -189,12 +190,12 @@ } }, "node_modules/@astrojs/vercel": { - "version": "8.1.1", - "resolved": "https://registry.npmmirror.com/@astrojs/vercel/-/vercel-8.1.1.tgz", - "integrity": "sha512-4tTib9LkAtg7tqac4L+pnWEveUmYyWRXpHivaVV3lHKeQjB28KUZ+YXtR/VUG6/xpMduo+OCOOg0l1zOPq/e4w==", + "version": "8.1.3", + "resolved": "https://registry.npmmirror.com/@astrojs/vercel/-/vercel-8.1.3.tgz", + "integrity": "sha512-mHO28cc0FQbA6ncFteW5Hqf2l0gSthPNF8/dpM1sQAOD92Mbyi93/4EhTYj07Xhi3t37FU4tPtzyQoCAn4P4Kg==", "license": "MIT", "dependencies": { - "@astrojs/internal-helpers": "0.6.0", + "@astrojs/internal-helpers": "0.6.1", "@vercel/analytics": "^1.5.0", "@vercel/edge": "^1.2.1", "@vercel/nft": "^0.29.2", @@ -2200,65 +2201,63 @@ ] }, "node_modules/@shikijs/core": { - "version": "1.29.2", - "resolved": "https://registry.npmmirror.com/@shikijs/core/-/core-1.29.2.tgz", - "integrity": "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@shikijs/core/-/core-3.2.1.tgz", + "integrity": "sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==", "license": "MIT", "dependencies": { - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", + "@shikijs/types": "3.2.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" + "hast-util-to-html": "^9.0.5" } }, "node_modules/@shikijs/engine-javascript": { - "version": "1.29.2", - "resolved": "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz", - "integrity": "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-3.2.1.tgz", + "integrity": "sha512-eMdcUzN3FMQYxOmRf2rmU8frikzoSHbQDFH2hIuXsrMO+IBOCI9BeeRkCiBkcLDHeRKbOCtYMJK3D6U32ooU9Q==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "oniguruma-to-es": "^2.2.0" + "@shikijs/types": "3.2.1", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.1.0" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.2", - "resolved": "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", - "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-3.2.1.tgz", + "integrity": "sha512-wZZAkayEn6qu2+YjenEoFqj0OyQI64EWsNR6/71d1EkG4sxEOFooowKivsWPpaWNBu3sxAG+zPz5kzBL/SsreQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/types": "3.2.1", + "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "1.29.2", - "resolved": "https://registry.npmmirror.com/@shikijs/langs/-/langs-1.29.2.tgz", - "integrity": "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@shikijs/langs/-/langs-3.2.1.tgz", + "integrity": "sha512-If0iDHYRSGbihiA8+7uRsgb1er1Yj11pwpX1c6HLYnizDsKAw5iaT3JXj5ZpaimXSWky/IhxTm7C6nkiYVym+A==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2" + "@shikijs/types": "3.2.1" } }, "node_modules/@shikijs/themes": { - "version": "1.29.2", - "resolved": "https://registry.npmmirror.com/@shikijs/themes/-/themes-1.29.2.tgz", - "integrity": "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@shikijs/themes/-/themes-3.2.1.tgz", + "integrity": "sha512-k5DKJUT8IldBvAm8WcrDT5+7GA7se6lLksR+2E3SvyqGTyFMzU2F9Gb7rmD+t+Pga1MKrYFxDIeyWjMZWM6uBQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2" + "@shikijs/types": "3.2.1" } }, "node_modules/@shikijs/types": { - "version": "1.29.2", - "resolved": "https://registry.npmmirror.com/@shikijs/types/-/types-1.29.2.tgz", - "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@shikijs/types/-/types-3.2.1.tgz", + "integrity": "sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==", "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, @@ -2521,6 +2520,12 @@ "vite": "^5.2.0 || ^6" } }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmmirror.com/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT" + }, "node_modules/@types/acorn": { "version": "4.0.6", "resolved": "https://registry.npmmirror.com/@types/acorn/-/acorn-4.0.6.tgz", @@ -2583,12 +2588,6 @@ "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==", "license": "MIT" }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmmirror.com/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "license": "MIT" - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz", @@ -2698,12 +2697,38 @@ "@types/node": "*" } }, + "node_modules/@types/stats.js": { + "version": "0.17.3", + "resolved": "https://registry.npmmirror.com/@types/stats.js/-/stats.js-0.17.3.tgz", + "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.174.0", + "resolved": "https://registry.npmmirror.com/@types/three/-/three-0.174.0.tgz", + "integrity": "sha512-De/+vZnfg2aVWNiuy1Ldu+n2ydgw1osinmiZTAn0necE++eOfsygL8JpZgFjR2uHmAPo89MkxBj3JJ+2BMe+Uw==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": "*", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.18.1" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/webxr": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/@types/webxr/-/webxr-0.5.21.tgz", + "integrity": "sha512-geZIAtLzjGmgY2JUi6VxXdCrTb99A7yP49lxLr2Nm/uIK0PkkxcEi4OGhoGDO4pxCf3JwGz2GiJL2Ej4K2bKaA==", + "license": "MIT" + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -2846,6 +2871,12 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, + "node_modules/@webgpu/types": { + "version": "0.1.59", + "resolved": "https://registry.npmmirror.com/@webgpu/types/-/types-0.1.59.tgz", + "integrity": "sha512-jZJ6ipNli+rn++/GAPqsZXfsgjx951wlCW7vNAg+oGdp0ZYidTOkbVTVeK2frzowuD5ch7MRz7leOEX1PMv43A==", + "license": "BSD-3-Clause" + }, "node_modules/abbrev": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-3.0.0.tgz", @@ -2856,9 +2887,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3064,26 +3095,25 @@ } }, "node_modules/astro": { - "version": "5.4.2", - "resolved": "https://registry.npmmirror.com/astro/-/astro-5.4.2.tgz", - "integrity": "sha512-9Z3fAniIRJaK/o43OroZA1wHUIU+qHiOR9ovlVT/2XQaN25QRXScIsKWlFp0G/zrx5OuuoJ+QnaoHHW061u26A==", + "version": "5.5.5", + "resolved": "https://registry.npmmirror.com/astro/-/astro-5.5.5.tgz", + "integrity": "sha512-fdnnK5dhWNIQT/cXzvaGs9il4T5noi4jafobdntbuNOrRxI1JnOxDfrtBadUo6cknCRCFhYrXh4VndCqj1a4Sg==", "license": "MIT", "dependencies": { - "@astrojs/compiler": "^2.10.4", - "@astrojs/internal-helpers": "0.6.0", - "@astrojs/markdown-remark": "6.2.0", + "@astrojs/compiler": "^2.11.0", + "@astrojs/internal-helpers": "0.6.1", + "@astrojs/markdown-remark": "6.3.1", "@astrojs/telemetry": "3.2.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", - "@types/cookie": "^0.6.0", - "acorn": "^8.14.0", + "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", - "ci-info": "^4.1.0", + "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", - "cookie": "^0.7.2", + "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", @@ -3106,12 +3136,12 @@ "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", + "package-manager-detector": "^1.0.0", "picomatch": "^4.0.2", - "preferred-pm": "^4.1.1", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", - "shiki": "^1.29.2", + "shiki": "^3.0.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", @@ -3119,9 +3149,8 @@ "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", - "vite": "^6.2.0", + "vite": "^6.2.3", "vitefu": "^1.0.6", - "which-pm": "^3.0.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", @@ -3240,18 +3269,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.4.tgz", @@ -3467,9 +3484,9 @@ } }, "node_modules/ci-info": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-4.1.0.tgz", - "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-4.2.0.tgz", + "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", "funding": [ { "type": "github", @@ -3596,12 +3613,12 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" } }, "node_modules/cookie-es": { @@ -3898,22 +3915,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/echarts": { - "version": "5.6.0", - "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz", - "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "2.3.0", - "zrender": "5.6.1" - } - }, - "node_modules/echarts/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", @@ -4108,19 +4109,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/estree-util-attach-comments": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", @@ -4284,59 +4272,18 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up-simple": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/find-up-simple/-/find-up-simple-1.0.0.tgz", - "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-yarn-workspace-root2": { - "version": "1.2.16", - "resolved": "https://registry.npmmirror.com/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", - "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", - "license": "Apache-2.0", - "dependencies": { - "micromatch": "^4.0.2", - "pkg-dir": "^4.2.0" - } - }, "node_modules/flattie": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/flattie/-/flattie-1.1.1.tgz", @@ -4926,15 +4873,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -5318,55 +5256,6 @@ "node": ">=0.10" } }, - "node_modules/load-yaml-file": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/load-yaml-file/-/load-yaml-file-0.2.0.tgz", - "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.5", - "js-yaml": "^3.13.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/load-yaml-file/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/load-yaml-file/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmmirror.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -5783,6 +5672,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmmirror.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "license": "MIT" + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz", @@ -6502,31 +6397,6 @@ ], "license": "MIT" }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -6811,15 +6681,22 @@ "wrappy": "1" } }, + "node_modules/oniguruma-parser": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/oniguruma-parser/-/oniguruma-parser-0.5.4.tgz", + "integrity": "sha512-yNxcQ8sKvURiTwP0mV6bLQCYE7NKfKRRWunhbZnXgxSmB1OXa1lHrN3o4DZd+0Si0kU5blidK7BcROO8qv5TZA==", + "license": "MIT" + }, "node_modules/oniguruma-to-es": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", - "integrity": "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==", + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-4.1.0.tgz", + "integrity": "sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA==", "license": "MIT", "dependencies": { "emoji-regex-xs": "^1.0.0", - "regex": "^5.1.1", - "regex-recursion": "^5.1.1" + "oniguruma-parser": "^0.5.4", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" } }, "node_modules/p-limit": { @@ -6837,33 +6714,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-queue": { "version": "8.1.0", "resolved": "https://registry.npmmirror.com/p-queue/-/p-queue-8.1.0.tgz", @@ -6892,21 +6742,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.1.0.tgz", + "integrity": "sha512-Y8f9qUlBzW8qauJjd/eu6jlpJZsuPJm2ZAV0cDVd420o4EdpH5RPdoCv+60/TdJflGatr4sDfpAL6ArWZbM5tA==", + "license": "MIT" + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz", @@ -6987,15 +6834,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", @@ -7036,27 +6874,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/postcss": { "version": "8.5.3", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz", @@ -7099,24 +6916,10 @@ "node": ">=4" } }, - "node_modules/preferred-pm": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/preferred-pm/-/preferred-pm-4.1.1.tgz", - "integrity": "sha512-rU+ZAv1Ur9jAUZtGPebQVQPzdGhNzaEiQ7VL9+cjsAWPHFYOccNXPNiev1CCDSOg/2j7UujM7ojNhpkuILEVNQ==", - "license": "MIT", - "dependencies": { - "find-up-simple": "^1.0.0", - "find-yarn-workspace-root2": "1.2.16", - "which-pm": "^3.0.1" - }, - "engines": { - "node": ">=18.12" - } - }, "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "version": "1.30.0", + "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "license": "MIT", "engines": { "node": ">=6" @@ -7296,21 +7099,20 @@ } }, "node_modules/regex": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/regex/-/regex-5.1.1.tgz", - "integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==", + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" } }, "node_modules/regex-recursion": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/regex-recursion/-/regex-recursion-5.1.1.tgz", - "integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==", + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", "license": "MIT", "dependencies": { - "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, @@ -7863,18 +7665,18 @@ } }, "node_modules/shiki": { - "version": "1.29.2", - "resolved": "https://registry.npmmirror.com/shiki/-/shiki-1.29.2.tgz", - "integrity": "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/shiki/-/shiki-3.2.1.tgz", + "integrity": "sha512-VML/2o1/KGYkEf/stJJ+s9Ypn7jUKQPomGLGYso4JJFMFxVDyPNsjsI3MB3KLjlMOeH44gyaPdXC6rik2WXvUQ==", "license": "MIT", "dependencies": { - "@shikijs/core": "1.29.2", - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/langs": "1.29.2", - "@shikijs/themes": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", + "@shikijs/core": "3.2.1", + "@shikijs/engine-javascript": "3.2.1", + "@shikijs/engine-oniguruma": "3.2.1", + "@shikijs/langs": "3.2.1", + "@shikijs/themes": "3.2.1", + "@shikijs/types": "3.2.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, @@ -7984,12 +7786,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", @@ -8115,15 +7911,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/style-to-js": { "version": "1.1.16", "resolved": "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.16.tgz", @@ -8183,6 +7970,12 @@ "node": ">=18" } }, + "node_modules/three": { + "version": "0.174.0", + "resolved": "https://registry.npmmirror.com/three/-/three-0.174.0.tgz", + "integrity": "sha512-p+WG3W6Ov74alh3geCMkGK9NWuT62ee21cV3jEnun201zodVF4tCE5aZa2U122/mkLRmhJJUQmLLW1BH00uQJQ==", + "license": "MIT" + }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", @@ -8205,18 +7998,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", @@ -8701,9 +8482,9 @@ } }, "node_modules/vite": { - "version": "6.2.1", - "resolved": "https://registry.npmmirror.com/vite/-/vite-6.2.1.tgz", - "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.2.3.tgz", + "integrity": "sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -8860,18 +8641,6 @@ "node": ">= 8" } }, - "node_modules/which-pm": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/which-pm/-/which-pm-3.0.1.tgz", - "integrity": "sha512-v2JrMq0waAI4ju1xU5x3blsxBBMgdgZve580iYMN5frDaLGjbA24fok7wKCsya8KLVO19Ju4XDc5+zTZCJkQfg==", - "license": "MIT", - "dependencies": { - "load-yaml-file": "^0.2.0" - }, - "engines": { - "node": ">=18.12" - } - }, "node_modules/which-pm-runs": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/which-pm-runs/-/which-pm-runs-1.1.0.tgz", @@ -9080,21 +8849,6 @@ "zod": "^3" } }, - "node_modules/zrender": { - "version": "5.6.1", - "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz", - "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", - "license": "BSD-3-Clause", - "dependencies": { - "tslib": "2.3.0" - } - }, - "node_modules/zrender/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index ec1dd8e..53aa450 100644 --- a/package.json +++ b/package.json @@ -9,23 +9,24 @@ "astro": "astro" }, "dependencies": { - "@astrojs/mdx": "^4.1.0", - "@astrojs/node": "^9.1.2", - "@astrojs/react": "^4.2.1", - "@astrojs/sitemap": "^3.2.1", - "@astrojs/vercel": "^8.1.1", + "@astrojs/mdx": "^4.2.2", + "@astrojs/node": "^9.1.3", + "@astrojs/react": "^4.2.2", + "@astrojs/sitemap": "^3.3.0", + "@astrojs/vercel": "^8.1.3", "@tailwindcss/vite": "^4.0.9", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", - "astro": "^5.4.2", + "@types/three": "^0.174.0", + "astro": "^5.5.5", "cheerio": "^1.0.0-rc.12", - "echarts": "^5.6.0", "node-fetch": "^3.3.0", "octokit": "^3.1.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-masonry-css": "^1.0.16", - "tailwindcss": "^4.0.9" + "tailwindcss": "^4.0.9", + "three": "^0.174.0" }, "devDependencies": { "@tailwindcss/typography": "^0.5.16", diff --git a/src/components/WorldHeatmap.tsx b/src/components/WorldHeatmap.tsx index a83d8cb..fd7c9cb 100644 --- a/src/components/WorldHeatmap.tsx +++ b/src/components/WorldHeatmap.tsx @@ -1,5 +1,7 @@ -import React, { useEffect, useRef } from 'react'; -import * as echarts from 'echarts'; +import React, { useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; +import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer.js'; import worldData from '@/assets/world.zh.json'; import chinaData from '@/assets/china.json'; @@ -8,231 +10,1048 @@ interface WorldHeatmapProps { } const WorldHeatmap: React.FC = ({ visitedPlaces }) => { - const chartRef = useRef(null); - const chartInstanceRef = useRef(null); + const containerRef = useRef(null); + const [hoveredCountry, setHoveredCountry] = useState(null); + const sceneRef = useRef<{ + scene: THREE.Scene; + camera: THREE.PerspectiveCamera; + renderer: THREE.WebGLRenderer; + labelRenderer: CSS2DRenderer; + controls: OrbitControls; + earth: THREE.Mesh; + bgSphere: THREE.Mesh; + countries: Map; + raycaster: THREE.Raycaster; + mouse: THREE.Vector2; + animationId: number | null; + lastCameraPosition: THREE.Vector3 | null; + lastMouseEvent: MouseEvent | null; + lastClickedCountry: string | null; + } | null>(null); useEffect(() => { - if (!chartRef.current) return; + if (!containerRef.current) return; - // 确保之前的实例被正确销毁 - if (chartInstanceRef.current) { - chartInstanceRef.current.dispose(); + // 清理之前的场景 + if (sceneRef.current) { + if (sceneRef.current.animationId !== null) { + cancelAnimationFrame(sceneRef.current.animationId); + } + sceneRef.current.renderer.dispose(); + sceneRef.current.labelRenderer.domElement.remove(); + sceneRef.current.scene.clear(); + containerRef.current.innerHTML = ''; } - // 初始化图表并保存实例引用 - const chart = echarts.init(chartRef.current, null, { - renderer: 'canvas', - useDirtyRect: false - }); - chartInstanceRef.current = chart; - - const mergedWorldData = { - ...worldData, - features: worldData.features.map((feature: any) => { - if (feature.properties.name === '中国') { - return { - ...feature, - geometry: { - type: 'MultiPolygon', - coordinates: [] - } - }; - } - return feature; - }).concat( - chinaData.features.map((feature: any) => ({ - ...feature, - properties: { - ...feature.properties, - name: feature.properties.name - } - })) - ) - }; - - echarts.registerMap('merged-world', mergedWorldData as any); - // 检查当前是否为暗色模式 const isDarkMode = document.documentElement.classList.contains('dark'); // 根据当前模式设置颜色 - const getChartColors = () => { + const getColors = () => { return { - textColor: isDarkMode ? '#ffffff' : '#374151', - borderColor: isDarkMode ? '#374151' : '#e5e7eb', - unvisitedColor: isDarkMode ? '#1f2937' : '#e5e7eb', - visitedColor: isDarkMode ? '#059669' : '#10b981', - emphasisColor: isDarkMode ? '#059669' : '#10b981', - tooltipBgColor: isDarkMode ? '#111827' : '#ffffff', + background: 'transparent', // 改为透明背景 + earthBase: isDarkMode ? '#1f2937' : '#e5e7eb', + visited: isDarkMode ? '#059669' : '#10b981', + highlight: isDarkMode ? '#f59e0b' : '#f59e0b', + border: isDarkMode ? '#4b5563' : '#9ca3af', + visitedBorder: isDarkMode ? '#059669' : '#10b981', + chinaBorder: isDarkMode ? '#ef4444' : '#ef4444', // 中国边界使用红色 + text: isDarkMode ? '#ffffff' : '#374151', + bgSphere: isDarkMode ? '#111827' : '#f3f4f6', // 背景球体颜色 }; }; - const colors = getChartColors(); + const colors = getColors(); - // 使用动态颜色方案 - const option = { - title: { - text: '我的旅行足迹', - left: 'center', - top: 20, - textStyle: { - color: colors.textColor, - fontWeight: 'bold' - } - }, - tooltip: { - trigger: 'item', - formatter: ({name}: {name: string}) => { - const visited = visitedPlaces.includes(name); - return `${name}
${visited ? '✓ 已去过' : '尚未去过'}`; - }, - backgroundColor: colors.tooltipBgColor, - borderColor: colors.borderColor, - textStyle: { - color: colors.textColor - } - }, - visualMap: { - show: true, - type: 'piecewise', - pieces: [ - { value: 1, label: '已去过' }, - { value: 0, label: '未去过' } - ], - inRange: { - color: [colors.unvisitedColor, colors.visitedColor] - }, - outOfRange: { - color: [colors.unvisitedColor] - }, - textStyle: { - color: colors.textColor, - fontWeight: 500 - } - }, - series: [{ - name: '旅行足迹', - type: 'map', - map: 'merged-world', - roam: true, - emphasis: { - label: { - show: true, - color: colors.textColor - }, - itemStyle: { - areaColor: colors.emphasisColor - } - }, - itemStyle: { - borderColor: colors.borderColor, - borderWidth: 1, - borderType: 'solid' - }, - data: mergedWorldData.features.map((feature: any) => ({ - name: feature.properties.name, - value: visitedPlaces.includes(feature.properties.name) ? 1 : 0 - })), - nameProperty: 'name' - }] + // 创建场景 + const scene = new THREE.Scene(); + // 将背景设置为透明 + scene.background = null; + + // 创建不透明材质的辅助函数 + const createOpaqueMaterial = (color: string, side: THREE.Side = THREE.FrontSide, renderOrder: number = 0) => { + const material = new THREE.MeshBasicMaterial({ + color: color, + side: side, + transparent: false, + opacity: 1.0, + depthTest: true, + depthWrite: true + }); + return material; }; - chart.setOption(option); + // 创建地球前,先创建一个背景球体 - 设置为对应主题的背景色 + const bgSphereGeometry = new THREE.SphereGeometry(2.1, 64, 64); + const bgSphereMaterial = createOpaqueMaterial(colors.bgSphere, THREE.BackSide); + const bgSphere = new THREE.Mesh(bgSphereGeometry, bgSphereMaterial); + bgSphere.renderOrder = 0; + scene.add(bgSphere); - // 确保图表初始化后立即调整大小以适应容器 - chart.resize(); + // 创建一个中间层球体,防止看透 - 设置为对应主题的背景色 + const midSphereGeometry = new THREE.SphereGeometry(2.0, 64, 64); + const midSphereMaterial = createOpaqueMaterial(colors.bgSphere); + const midSphere = new THREE.Mesh(midSphereGeometry, midSphereMaterial); + midSphere.renderOrder = 1; + scene.add(midSphere); + + // 在地球内部添加一个实心球体 - 设置为对应主题的背景色 + const innerSphereGeometry = new THREE.SphereGeometry(1.97, 32, 32); + const innerSphereMaterial = createOpaqueMaterial(colors.bgSphere); + const innerSphere = new THREE.Mesh(innerSphereGeometry, innerSphereMaterial); + innerSphere.renderOrder = 1.5; + scene.add(innerSphere); + + // 创建相机 + const camera = new THREE.PerspectiveCamera( + 45, + containerRef.current.clientWidth / containerRef.current.clientHeight, + 0.1, + 1000 + ); + camera.position.z = 8; - const handleResize = () => { - if (chartInstanceRef.current) { - chartInstanceRef.current.resize(); + // 创建渲染器 + const renderer = new THREE.WebGLRenderer({ + antialias: true, + alpha: true, // 启用alpha通道 + logarithmicDepthBuffer: true, + preserveDrawingBuffer: true, + precision: "highp" + }); + renderer.sortObjects = true; + // 设置透明背景 + renderer.setClearColor(0x000000, 0); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setSize(containerRef.current.clientWidth, containerRef.current.clientHeight); + containerRef.current.appendChild(renderer.domElement); + + // 创建CSS2D渲染器用于标签 + const labelRenderer = new CSS2DRenderer(); + labelRenderer.setSize(containerRef.current.clientWidth, containerRef.current.clientHeight); + labelRenderer.domElement.style.position = 'absolute'; + labelRenderer.domElement.style.top = '0'; + labelRenderer.domElement.style.pointerEvents = 'none'; + containerRef.current.appendChild(labelRenderer.domElement); + + // 添加控制器 + const controls = new OrbitControls(camera, renderer.domElement); + controls.enableDamping = true; + controls.dampingFactor = 0.05; + controls.rotateSpeed = 0.5; + controls.autoRotate = true; + controls.autoRotateSpeed = 0.5; + controls.minDistance = 5; + controls.maxDistance = 15; + + // 限制上下旋转角度,避免相机翻转 + controls.minPolarAngle = Math.PI * 0.1; + controls.maxPolarAngle = Math.PI * 0.9; + + // 添加控制器事件监听 + controls.addEventListener('change', () => { + if (sceneRef.current) { + renderer.render(scene, camera); + labelRenderer.render(scene, camera); } + }); + + // 创建地球几何体,注意减小尺寸防止Z-fighting + const earthGeometry = new THREE.SphereGeometry(1.95, 64, 64); + + // 使用不透明的基础材质 + const earthMaterial = createOpaqueMaterial(colors.earthBase, THREE.FrontSide); + + const earth = new THREE.Mesh(earthGeometry, earthMaterial); + earth.matrixAutoUpdate = false; + earth.updateMatrix(); + earth.renderOrder = 2; + scene.add(earth); + + // 添加光源 + const ambientLight = new THREE.AmbientLight(0xffffff, 0.8); + scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2); + directionalLight.position.set(5, 3, 5); + scene.add(directionalLight); + + // 创建国家边界 + const countries = new Map(); + const countryGroup = new THREE.Group(); + earth.add(countryGroup); + + // 创建一个辅助函数,用于将经纬度转换为三维坐标 + const latLongToVector3 = (lat: number, lon: number, radius: number): THREE.Vector3 => { + // 调整经度范围,确保它在[-180, 180]之间 + while (lon > 180) lon -= 360; + while (lon < -180) lon += 360; + + const phi = (90 - lat) * Math.PI / 180; + const theta = (lon + 180) * Math.PI / 180; + + const x = -radius * Math.sin(phi) * Math.cos(theta); + const y = radius * Math.cos(phi); + const z = radius * Math.sin(phi) * Math.sin(theta); + + return new THREE.Vector3(x, y, z); + }; + + // 省份边界和中心点数据结构 + const provinceBoundaries = new Map(); + const provinceCenters = new Map(); + + // 创建一个通用函数,用于处理地理特性(国家或省份) + const processGeoFeature = ( + feature: any, + parent: THREE.Group, + options: { + regionType: 'country' | 'province', + parentName?: string, + scale?: number, + borderColor?: string, + visitedBorderColor?: string + } + ) => { + const { regionType, parentName, scale = 2.01, borderColor, visitedBorderColor } = options; + + const regionName = regionType === 'province' && parentName + ? `${parentName}-${feature.properties.name}` + : feature.properties.name; + + const isRegionVisited = visitedPlaces.includes(regionName); + + // 为每个地区创建一个组 + const regionObject = new THREE.Group(); + regionObject.userData = { name: regionName, isVisited: isRegionVisited }; + + // 计算地区中心点 + let centerLon = 0; + let centerLat = 0; + let pointCount = 0; + let largestPolygonArea = 0; + let largestPolygonCenter = { lon: 0, lat: 0 }; + + // 首先检查GeoJSON特性中是否有预定义的中心点 + let hasPreDefinedCenter = false; + let centerVector; + + if (feature.properties.cp && Array.isArray(feature.properties.cp) && feature.properties.cp.length === 2) { + const [cpLon, cpLat] = feature.properties.cp; + hasPreDefinedCenter = true; + centerVector = latLongToVector3(cpLat, cpLon, scale + 0.005); + centerLon = cpLon; + centerLat = cpLat; + + // 保存预定义中心点 + provinceCenters.set(regionName, centerVector); + } + + // 存储区域边界 + const boundaries: THREE.Vector3[][] = []; + + // 计算多边形面积的辅助函数 + const calculatePolygonArea = (coords: number[][]) => { + let area = 0; + for (let i = 0, j = coords.length - 1; i < coords.length; j = i++) { + area += coords[i][0] * coords[j][1]; + area -= coords[i][1] * coords[j][0]; + } + return Math.abs(area / 2); + }; + + // 处理多边形坐标 + const processPolygon = (polygonCoords: any) => { + const points: THREE.Vector3[] = []; + + // 收集多边形的点 + polygonCoords.forEach((point: number[]) => { + const lon = point[0]; + const lat = point[1]; + centerLon += lon; + centerLat += lat; + pointCount++; + + // 使用辅助函数将经纬度转换为3D坐标 + points.push(latLongToVector3(lat, lon, scale)); + }); + + // 计算当前多边形的面积和中心 + if (regionType === 'country') { + const area = calculatePolygonArea(polygonCoords); + if (area > largestPolygonArea) { + largestPolygonArea = area; + + // 计算多边形中心 + let polyLon = 0; + let polyLat = 0; + polygonCoords.forEach((point: number[]) => { + polyLon += point[0]; + polyLat += point[1]; + }); + + largestPolygonCenter = { + lon: polyLon / polygonCoords.length, + lat: polyLat / polygonCoords.length + }; + } + } + + // 保存边界多边形 + if (points.length > 2) { + boundaries.push(points); + } + + // 创建边界线 + if (points.length > 1) { + const lineGeometry = new THREE.BufferGeometry().setFromPoints(points); + const lineMaterial = new THREE.LineBasicMaterial({ + color: isRegionVisited + ? (visitedBorderColor || colors.visitedBorder) + : (borderColor || colors.border), + linewidth: isRegionVisited ? 1.5 : 1, + transparent: true, + opacity: isRegionVisited ? 0.9 : 0.7, + depthTest: false, // 禁用深度测试,解决Z-fighting问题 + polygonOffset: true, // 启用多边形偏移 + polygonOffsetFactor: isRegionVisited ? -2 : -1, // 已访问区域的边界线偏移更多,确保在上层 + polygonOffsetUnits: 1 + }); + + const line = new THREE.Line(lineGeometry, lineMaterial); + line.userData = { + name: regionName, + isVisited: isRegionVisited, + originalColor: isRegionVisited + ? (visitedBorderColor || colors.visitedBorder) + : (borderColor || colors.border) + }; + // 设置已访问区域的边界线渲染顺序更高,确保它们始终绘制在未访问区域边界线的上方 + line.renderOrder = isRegionVisited ? 3 : 2; + regionObject.add(line); + + // 如果是已访问的地区,为这个边界创建一个填充面 + if (isRegionVisited && points.length >= 3) { + try { + // 使用ShapeGeometry创建一个平面填充 + // 首先创建一个2D平面上的形状 + const center = new THREE.Vector3(0, 0, 0); + // 将3D点投影到以该点为中心的平面上 + const projectedPoints = points.map(p => { + // 计算从地球中心到点的方向向量 + const dir = p.clone().normalize(); + // 计算投影平面的法向量(就是该点的方向) + const normal = dir.clone(); + // 创建一个与地球表面近似切线的平面 + const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(normal, p); + + // 计算从中心到各点的投影坐标 + const v1 = new THREE.Vector3(); + const v2 = new THREE.Vector3(); + + // 获取平面上的两个相互垂直的方向作为UV坐标系 + let u = new THREE.Vector3(1, 0, 0); + if (Math.abs(normal.dot(u)) > 0.9) { + u = new THREE.Vector3(0, 1, 0); + } + const v = new THREE.Vector3().crossVectors(normal, u).normalize(); + u = new THREE.Vector3().crossVectors(v, normal).normalize(); + + // 计算点在该平面上的UV坐标 + const projected = p.clone().sub(center); + v1.set(projected.dot(u), projected.dot(v), 0); + return v1; + }); + + // 创建一个Shape对象 + const shape = new THREE.Shape(); + + // 移动到第一个点 + shape.moveTo(projectedPoints[0].x, projectedPoints[0].y); + + // 连接所有其他点 + for (let i = 1; i < projectedPoints.length; i++) { + shape.lineTo(projectedPoints[i].x, projectedPoints[i].y); + } + + // 关闭形状 + shape.closePath(); + + // 创建一个面材质 + const faceMaterial = new THREE.MeshBasicMaterial({ + color: colors.visited, + transparent: true, + opacity: 0.3, + side: THREE.DoubleSide, + depthWrite: false + }); + + // 根据中心点确定方向 + const centerPoint = points.reduce((acc, point) => acc.add(point), new THREE.Vector3()).multiplyScalar(1 / points.length); + const direction = centerPoint.clone().normalize(); + + // 根据曲面方向创建网格 + const geometry = new THREE.ShapeGeometry(shape); + const mesh = new THREE.Mesh(geometry, faceMaterial); + + // 放置并旋转网格以匹配多边形位置 + mesh.position.copy(centerPoint); + mesh.lookAt(center); + + // 设置网格属性 + mesh.userData = { + name: regionName, + isVisited: isRegionVisited, + originalColor: colors.visited + }; + mesh.renderOrder = 1; + + // 添加到区域对象 + regionObject.add(mesh); + } catch (error) { + console.error("填充区域时出错:", error); + // 如果填充区域出错,回退到简单的圆盘标记 + const centerPoint = points.reduce((acc, point) => acc.add(point), new THREE.Vector3()).multiplyScalar(1 / points.length); + const diskGeometry = new THREE.CircleGeometry(0.1, 32); + const diskMaterial = new THREE.MeshBasicMaterial({ + color: colors.visited, + transparent: true, + opacity: 0.3, + side: THREE.DoubleSide + }); + + const disk = new THREE.Mesh(diskGeometry, diskMaterial); + disk.position.copy(centerPoint); + disk.lookAt(0, 0, 0); + disk.rotateX(Math.PI / 2); + disk.userData = { name: regionName, isVisited: true }; + disk.renderOrder = 1; + regionObject.add(disk); + } + } + } + }; + + // 处理不同类型的几何体 + if (feature.geometry && (feature.geometry.type === 'Polygon' || feature.geometry.type === 'MultiPolygon')) { + if (feature.geometry.type === 'Polygon') { + feature.geometry.coordinates.forEach((ring: any) => { + processPolygon(ring); + }); + } else if (feature.geometry.type === 'MultiPolygon') { + feature.geometry.coordinates.forEach((polygon: any) => { + polygon.forEach((ring: any) => { + processPolygon(ring); + }); + }); + } + + // 保存省份边界 + if (regionType === 'province' && boundaries.length > 0) { + provinceBoundaries.set(regionName, boundaries); + } + + if (pointCount > 0 && !hasPreDefinedCenter) { + // 如果是国家且有最大多边形 + if (regionType === 'country' && largestPolygonArea > 0) { + centerLon = largestPolygonCenter.lon; + centerLat = largestPolygonCenter.lat; + } else { + // 回退到平均中心点 + centerLon /= pointCount; + centerLat /= pointCount; + } + + // 将中心点经纬度转换为3D坐标 + centerVector = latLongToVector3(centerLat, centerLon, scale + 0.005); + + // 保存计算的中心点 + if (regionType === 'province') { + provinceCenters.set(regionName, centerVector); + } + } + + if (pointCount > 0) { + // 添加地区对象到父组 + parent.add(regionObject); + countries.set(regionName, regionObject); + } + } + + return regionObject; + }; + + // 处理世界GeoJSON数据 + worldData.features.forEach((feature: any) => { + const countryName = feature.properties.name; + + // 跳过中国,因为我们将使用更详细的中国地图数据 + if (countryName === '中国') return; + + processGeoFeature(feature, countryGroup, { + regionType: 'country', + scale: 2.01 + }); + }); + + // 处理中国的省份 + const chinaObject = new THREE.Group(); + chinaObject.userData = { name: '中国', isVisited: visitedPlaces.includes('中国') }; + + chinaData.features.forEach((feature: any) => { + processGeoFeature(feature, chinaObject, { + regionType: 'province', + parentName: '中国', + scale: 2.015, + borderColor: colors.chinaBorder, + visitedBorderColor: colors.visitedBorder + }); + }); + + // 添加中国对象到国家组 + countryGroup.add(chinaObject); + countries.set('中国', chinaObject); + + // 创建射线投射器用于鼠标交互 + const raycaster = new THREE.Raycaster(); + const mouse = new THREE.Vector2(); + + // 添加节流函数,限制鼠标移动事件的触发频率 + const throttle = (func: Function, limit: number) => { + let inThrottle: boolean = false; + return function(this: any, ...args: any[]) { + if (!inThrottle) { + func.apply(this, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; + }; + + // 计算鼠标射线和检测交互的通用函数 + const calculateMouseRay = (event: MouseEvent) => { + if (!containerRef.current || !sceneRef.current) return null; + + // 计算鼠标在画布中的归一化坐标 + const rect = containerRef.current.getBoundingClientRect(); + const x = ((event.clientX - rect.left) / rect.width) * 2 - 1; + const y = -((event.clientY - rect.top) / rect.height) * 2 + 1; + + // 应用坐标,确保值在合理范围内 + mouse.x = Math.max(-1, Math.min(1, x)); + mouse.y = Math.max(-1, Math.min(1, y)); + + // 设置射线投射器的精度 + raycaster.params.Line = { threshold: 0.2 }; + raycaster.setFromCamera(mouse, sceneRef.current.camera); + + return raycaster; + }; + + // 通用函数,查找与射线相交的国家 + const findIntersectedCountry = () => { + if (!sceneRef.current) return null; + + // 首先检测是否与地球相交 + const sphereCenter = new THREE.Vector3(0, 0, 0); + const sphereRadius = 2; // 地球半径 + const sphere = new THREE.Sphere(sphereCenter, sphereRadius); + + // 检测射线与球体是否相交,并获取交点 + const earthIntersectionPoint = new THREE.Vector3(); + const hasEarthIntersection = raycaster.ray.intersectSphere(sphere, earthIntersectionPoint); + + // 如果射线没有与地球相交,返回null + if (!hasEarthIntersection) return null; + + // 计算射线与地球交点的距离 + const distanceToEarthIntersection = earthIntersectionPoint.distanceTo(sceneRef.current.camera.position); + + // 检测与国家组的交叉 + let intersects = raycaster.intersectObject(countryGroup, true); + + // 过滤出只在地球前表面的交点 + const tolerance = 0.1; + intersects = intersects.filter(intersect => { + return intersect.distance <= (distanceToEarthIntersection + tolerance); + }); + + if (intersects.length === 0) return null; + + // 寻找相交的国家 + // 先尝试找到面对象(Mesh类型) + for (const intersect of intersects) { + if (intersect.object instanceof THREE.Mesh && + intersect.object.userData && + intersect.object.userData.name) { + return intersect.object.userData.name; + } + } + + // 如果没有找到面对象,尝试查找线对象 + for (const intersect of intersects) { + let countryObject = intersect.object; + + // 如果对象本身有userData,优先使用 + if (countryObject.userData && countryObject.userData.name) { + return countryObject.userData.name; + } + + // 向上遍历对象层次结构,找到有userData的父对象 + while (countryObject.parent) { + countryObject = countryObject.parent as THREE.Object3D; + if (countryObject.userData && countryObject.userData.name) { + return countryObject.userData.name; + } + // 如果已经到达地球对象,则停止遍历 + if (countryObject === earth) break; + } + } + + return null; + }; + + // 鼠标移动事件 - 使用节流函数包装 + const onMouseMove = throttle((event: MouseEvent) => { + if (!containerRef.current || !sceneRef.current) return; + + // 如果是通过点击选中的国家,不要通过移动鼠标改变它 + if (sceneRef.current.lastClickedCountry !== null) return; + + // 保存最后的鼠标事件,用于相机变化时重新检测 + sceneRef.current.lastMouseEvent = event; + + // 计算射线 + calculateMouseRay(event); + + // 查找相交的国家 + const countryName = findIntersectedCountry(); + + if (countryName) { + setHoveredCountry(countryName); + controls.autoRotate = false; + } else { + setHoveredCountry(null); + controls.autoRotate = true; + } + }, 60); // 60毫秒的节流时间 + + // 添加清除选择的函数 + const clearSelection = () => { + setHoveredCountry(null); + if (sceneRef.current) { + sceneRef.current.lastClickedCountry = null; + } + controls.autoRotate = true; + }; + + // 添加鼠标点击事件处理 + const onClick = (event: MouseEvent) => { + if (!containerRef.current || !sceneRef.current) return; + + // 计算射线 + calculateMouseRay(event); + + // 查找相交的国家 + const countryName = findIntersectedCountry(); + + if (countryName) { + setHoveredCountry(countryName); + controls.autoRotate = false; + sceneRef.current.lastClickedCountry = countryName; + } else { + clearSelection(); + } + }; + + // 添加鼠标双击事件处理 + const onDoubleClick = () => { + clearSelection(); + }; + + containerRef.current.addEventListener('mousemove', onMouseMove); + containerRef.current.addEventListener('click', onClick); + containerRef.current.addEventListener('dblclick', onDoubleClick); + + // 创建一个辅助函数,强制所有球体使用完全不透明的材质 + const enforceOpaqueMaterials = () => { + // 修复地球材质 + if (earth && earth.material) { + const mat = earth.material as THREE.MeshBasicMaterial; + mat.transparent = false; + mat.opacity = 1.0; + mat.depthTest = true; + mat.depthWrite = true; + mat.side = THREE.FrontSide; + mat.needsUpdate = true; + } + + // 修复所有背景球体材质 + [bgSphere, midSphere, innerSphere].forEach(sphere => { + if (sphere && sphere.material instanceof THREE.MeshBasicMaterial) { + sphere.material.transparent = false; + sphere.material.opacity = 1.0; + sphere.material.depthTest = true; + sphere.material.depthWrite = true; + sphere.material.needsUpdate = true; + } + }); + }; + + // 应用一次以确保初始状态正确 + enforceOpaqueMaterials(); + + // 动画循环 + const animate = () => { + if (!sceneRef.current) return; + + // 获取当前帧计数 + const frameCount = sceneRef.current.animationId || 0; + + // 每5帧检查一次材质状态 + if (frameCount % 5 === 0) { + // 使用统一函数更新材质状态 - 仅处理球体 + [bgSphere, midSphere, innerSphere, earth].forEach(sphere => { + if (sphere && sphere.material instanceof THREE.MeshBasicMaterial) { + sphere.material.transparent = false; + sphere.material.opacity = 1.0; + sphere.material.depthTest = true; + sphere.material.depthWrite = true; + sphere.material.needsUpdate = true; + } + }); + } + + // 更新控制器 + sceneRef.current.controls.update(); + + // 相机变化检测 + const cameraPosition = sceneRef.current.camera.position.clone(); + let cameraChanged = false; + + if (sceneRef.current.lastCameraPosition) { + const distance = cameraPosition.distanceTo(sceneRef.current.lastCameraPosition); + cameraChanged = distance > 0.35; + } + + // 只有当相机移动且有最后鼠标事件时,才重新触发鼠标事件 + if (cameraChanged && sceneRef.current.lastMouseEvent && !sceneRef.current.lastClickedCountry) { + onMouseMove(sceneRef.current.lastMouseEvent); + + // 确保已访问区域的边界线始终在未访问区域之上 + sceneRef.current.countries.forEach((object) => { + object.traverse((child) => { + if (child instanceof THREE.Line) { + const childIsVisited = child.userData?.isVisited === true; + if (childIsVisited) { + child.renderOrder = 3; + + if (child.material instanceof THREE.LineBasicMaterial) { + child.material.depthTest = false; + child.material.polygonOffset = true; + child.material.polygonOffsetFactor = -2; + child.material.polygonOffsetUnits = 1; + child.material.needsUpdate = true; + } + } + } + }); + }); + } + + // 降低相机位置保存频率 + if (frameCount % 20 === 0) { + sceneRef.current.lastCameraPosition = cameraPosition.clone(); + } + + // 渲染 + sceneRef.current.renderer.render(scene, camera); + sceneRef.current.labelRenderer.render(scene, camera); + + // 请求下一帧 + sceneRef.current.animationId = requestAnimationFrame(animate); + }; + + // 覆盖渲染器的render方法,确保每次渲染前重设材质状态 + const originalRender = renderer.render; + renderer.render = function(scene, camera) { + // 在渲染前仅对球体对象强制更新材质状态 + [bgSphere, midSphere, innerSphere, earth].forEach(sphere => { + if (sphere && sphere.material instanceof THREE.MeshBasicMaterial) { + sphere.material.transparent = false; + sphere.material.opacity = 1.0; + sphere.material.needsUpdate = true; + } + }); + + // 调用原始渲染方法 + originalRender.call(this, scene, camera); + }; + + // 保存场景引用,添加中间层球体引用 + sceneRef.current = { + scene, + camera, + renderer, + labelRenderer, + controls, + earth, + bgSphere, + countries, + raycaster, + mouse, + animationId: null, + lastCameraPosition: null, + lastMouseEvent: null, + lastClickedCountry: null + }; + + // 将视图旋转到中国位置 + const positionCameraToFaceChina = () => { + // 中国的中心点经纬度 + const centerLon = 104.195397; + const centerLat = 35.86166; + + // 检查是否为小屏幕 + const isSmallScreen = containerRef.current && containerRef.current.clientWidth < 640; + + // 根据屏幕大小设置不同的相机初始位置 + let fixedPosition; + if (isSmallScreen) { + // 小屏幕显示距离更远,以便看到更多地球 + fixedPosition = new THREE.Vector3(-2.10, 3.41, -7.5); + } else { + // 大屏幕使用原来的位置 + fixedPosition = new THREE.Vector3(-2.10, 3.41, -6.08); + } + + // 应用位置 + camera.position.copy(fixedPosition); + camera.lookAt(0, 0, 0); + controls.update(); + + // 禁用自动旋转一段时间 + controls.autoRotate = false; + + // 保存相机位置 + const chinaCameraInitialPosition = camera.position.clone(); + + // 3秒后恢复旋转 + setTimeout(() => { + if (sceneRef.current) { + sceneRef.current.controls.autoRotate = true; + } + }, 3000); + + // 渲染 + renderer.render(scene, camera); + labelRenderer.render(scene, camera); + + // 将初始相机位置保存在sceneRef中 + if (sceneRef.current) { + sceneRef.current.lastCameraPosition = chinaCameraInitialPosition; + } + }; + + // 应用初始相机位置 + positionCameraToFaceChina(); + + // 确保已访问区域的边界线优先显示 + setTimeout(() => { + ensureVisitedBordersOnTop(); + }, 100); // 稍微延迟确保所有元素都已创建完成 + + // 开始动画 + sceneRef.current.animationId = requestAnimationFrame(animate); + + // 处理窗口大小变化 + const handleResize = () => { + if (!containerRef.current || !sceneRef.current) return; + + const { camera, renderer, labelRenderer } = sceneRef.current; + const width = containerRef.current.clientWidth; + const height = containerRef.current.clientHeight; + + camera.aspect = width / height; + camera.updateProjectionMatrix(); + renderer.setSize(width, height); + labelRenderer.setSize(width, height); + + // 在调整大小后立即渲染一次,以防止闪烁或偏移 + renderer.render(sceneRef.current.scene, camera); + labelRenderer.render(sceneRef.current.scene, camera); + + // 确保已访问区域的边界线总是显示在顶层 + ensureVisitedBordersOnTop(); }; window.addEventListener('resize', handleResize); - // 监听暗色模式变化并更新图表 + // 监听暗色模式变化 const darkModeObserver = new MutationObserver(() => { - if (!chartRef.current) return; + if (!sceneRef.current) return; - // 检查当前是否为暗色模式 - const newIsDarkMode = document.documentElement.classList.contains('dark'); + const isDark = document.documentElement.classList.contains('dark'); + const newColors = getColors(); - if (chartInstanceRef.current) { - // 更新颜色设置 - const newColors = { - textColor: newIsDarkMode ? '#ffffff' : '#374151', - borderColor: newIsDarkMode ? '#4b5563' : '#d1d5db', - unvisitedColor: newIsDarkMode ? '#1f2937' : '#e5e7eb', - visitedColor: newIsDarkMode ? '#059669' : '#10b981', - emphasisColor: newIsDarkMode ? '#059669' : '#10b981', - tooltipBgColor: newIsDarkMode ? '#111827' : '#ffffff', - }; - - // 更新图表选项 - const newOption = { - title: { - textStyle: { - color: newColors.textColor - } - }, - tooltip: { - backgroundColor: newColors.tooltipBgColor, - borderColor: newColors.borderColor, - textStyle: { - color: newColors.textColor - } - }, - visualMap: { - inRange: { - color: [newColors.unvisitedColor, newColors.visitedColor] - }, - outOfRange: { - color: [newColors.unvisitedColor] - }, - textStyle: { - color: newColors.textColor - } - }, - series: [{ - emphasis: { - label: { - show: true, - color: newColors.textColor - }, - itemStyle: { - areaColor: newColors.emphasisColor - } - }, - itemStyle: { - borderColor: newColors.borderColor, - borderWidth: 1, - borderType: 'solid' - } - }] - }; - - // 应用新选项 - chartInstanceRef.current.setOption(newOption); + // 保持场景背景透明 + sceneRef.current.scene.background = null; + + // 设置透明背景 + sceneRef.current.renderer.setClearColor(0x000000, 0); + + // 更新材质颜色函数 + const updateMeshColor = (obj: THREE.Object3D, color: string) => { + if (obj instanceof THREE.Mesh && obj.material instanceof THREE.MeshBasicMaterial) { + obj.material.color.set(color); + obj.material.transparent = false; + obj.material.opacity = 1.0; + obj.material.needsUpdate = true; + } + }; + + // 更新背景相关球体颜色 + const bgColor = newColors.bgSphere; + + // 直接更新三个球体对象,而不是遍历整个场景 + [bgSphere, midSphere, innerSphere].forEach(sphere => { + if (sphere && sphere.material instanceof THREE.MeshBasicMaterial) { + updateMeshColor(sphere, bgColor); + } + }); + + // 更新地球颜色 + if (sceneRef.current.earth && sceneRef.current.earth.material) { + const earthMat = sceneRef.current.earth.material as THREE.MeshBasicMaterial; + earthMat.color.set(new THREE.Color(newColors.earthBase)); + earthMat.side = THREE.DoubleSide; + earthMat.needsUpdate = true; } + + // 更新国家颜色 + sceneRef.current.countries.forEach((object, name) => { + const isVisited = visitedPlaces.includes(name); + + object.traverse((child) => { + if (child instanceof THREE.Line) { + const childIsVisited = child.userData?.isVisited === true; + + let color; + if (name.startsWith('中国-')) { + color = childIsVisited ? newColors.visitedBorder : newColors.chinaBorder; + } else if (name === '中国') { + color = newColors.chinaBorder; + } else { + color = isVisited ? newColors.visitedBorder : newColors.border; + } + + if (child.material instanceof THREE.LineBasicMaterial) { + child.material.color.set(new THREE.Color(color)); + child.userData.originalColor = color; + } + } + }); + }); + + // 暗色模式变化后重新应用渲染优先级 + setTimeout(() => { + ensureVisitedBordersOnTop(); + }, 50); }); darkModeObserver.observe(document.documentElement, { attributes: true }); + // 添加一个函数来确保已访问区域的边界线总是显示在顶层 + const ensureVisitedBordersOnTop = () => { + if (!sceneRef.current) return; + + // 优化边界线的渲染优先级和材质属性 + const updateLineMaterial = (child: THREE.Object3D) => { + if (!(child instanceof THREE.Line)) return; + + const childIsVisited = child.userData?.isVisited === true; + child.renderOrder = childIsVisited ? 3 : 2; + + if (child.material instanceof THREE.LineBasicMaterial) { + child.material.depthTest = false; + + if (childIsVisited) { + child.material.polygonOffset = true; + child.material.polygonOffsetFactor = -2; + child.material.polygonOffsetUnits = 1; + } + + child.material.needsUpdate = true; + } + }; + + // 遍历所有国家对象 + sceneRef.current.countries.forEach(object => { + object.traverse(updateLineMaterial); + }); + + // 立即渲染一次以应用更改 + sceneRef.current.renderer.render(sceneRef.current.scene, sceneRef.current.camera); + sceneRef.current.labelRenderer.render(sceneRef.current.scene, sceneRef.current.camera); + }; + return () => { - if (chartInstanceRef.current) { - chartInstanceRef.current.dispose(); - chartInstanceRef.current = null; + // 清理资源和事件监听器 + if (sceneRef.current) { + // 取消动画帧 + if (sceneRef.current.animationId !== null) { + cancelAnimationFrame(sceneRef.current.animationId); + } + + // 处理渲染器的处理 + sceneRef.current.renderer.dispose(); + sceneRef.current.renderer.forceContextLoss(); + sceneRef.current.renderer.domElement.remove(); + + // 移除标签渲染器 + if (sceneRef.current.labelRenderer) { + sceneRef.current.labelRenderer.domElement.remove(); + } + + // 释放控制器 + if (sceneRef.current.controls) { + sceneRef.current.controls.dispose(); + } } + + // 移除事件监听器 + if (containerRef.current) { + containerRef.current.removeEventListener('mousemove', onMouseMove); + containerRef.current.removeEventListener('click', onClick); + containerRef.current.removeEventListener('dblclick', onDoubleClick); + } + + // 移除窗口事件监听器 window.removeEventListener('resize', handleResize); + + // 移除观察器 darkModeObserver.disconnect(); }; }, [visitedPlaces]); return ( -
+
+
+ {hoveredCountry && ( +
+
+

+ {hoveredCountry} + {hoveredCountry && visitedPlaces.includes(hoveredCountry) ? + ' ✓ 已去过' : ' 尚未去过'} +

+
+
+ )} +
); }; diff --git a/src/consts.ts b/src/consts.ts index 5efb308..6684779 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -15,7 +15,7 @@ export const ICP = '渝ICP备2022009272号'; export const PSB_ICP = '渝公网安备50011902000520号'; export const PSB_ICP_URL = 'http://www.beian.gov.cn/portal/registerSystemInfo'; -export const VISITED_PLACES = ['黑龙江', '吉林', '辽宁', '北京', '天津', '广东', '西藏', '河北', '山东', '湖南', '重庆', '四川', "马来西亚", "印度尼西亚", "泰国"]; +export const VISITED_PLACES = ['中国-黑龙江', '中国-吉林', '中国-辽宁', '中国-北京', '中国-天津', '中国-广东', '中国-西藏', '中国-河北', '中国-山东', '中国-湖南', '中国-重庆', '中国-四川', "马来西亚", "印度尼西亚", "泰国"]; export const DOUBAN_ID = 'lsy22'; diff --git a/src/pages/other.astro b/src/pages/other.astro index bac01f2..a466a1d 100644 --- a/src/pages/other.astro +++ b/src/pages/other.astro @@ -5,7 +5,7 @@ import WorldHeatmap from '@/components/WorldHeatmap'; import { VISITED_PLACES } from '@/consts'; --- - +

距离退休还有