This commit is contained in:
feie9456 2025-07-12 16:43:26 +08:00
parent 075fb02263
commit cb8d35f4b6
22 changed files with 1375 additions and 1098 deletions

169
bun.lock
View File

@ -5,6 +5,9 @@
"name": "loreal-game", "name": "loreal-game",
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@types/express": "^4.17.21",
"@types/node": "^24.0.13",
"express": "^4.19.2",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
@ -175,8 +178,30 @@
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="], "@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="],
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/express": ["@types/express@4.17.23", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ=="],
"@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.6", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A=="],
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
"@types/node": ["@types/node@24.0.13", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="],
"@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
"@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
"@types/send": ["@types/send@0.17.5", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w=="],
"@types/serve-static": ["@types/serve-static@1.15.8", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "*" } }, "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg=="],
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.0", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.19" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^3.2.25" } }, "sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.0", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.19" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^3.2.25" } }, "sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ=="],
"@volar/language-core": ["@volar/language-core@2.4.15", "", { "dependencies": { "@volar/source-map": "2.4.15" } }, "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA=="], "@volar/language-core": ["@volar/language-core@2.4.15", "", { "dependencies": { "@volar/source-map": "2.4.15" } }, "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA=="],
@ -209,6 +234,8 @@
"@vue/tsconfig": ["@vue/tsconfig@0.7.0", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg=="], "@vue/tsconfig": ["@vue/tsconfig@0.7.0", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg=="],
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"alien-signals": ["alien-signals@1.0.13", "", {}, "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg=="], "alien-signals": ["alien-signals@1.0.13", "", {}, "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg=="],
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
@ -217,6 +244,8 @@
"ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="],
"autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="], "autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
"b4a": ["b4a@1.6.7", "", {}, "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg=="], "b4a": ["b4a@1.6.7", "", {}, "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg=="],
@ -237,6 +266,8 @@
"bl": ["bl@5.1.0", "", { "dependencies": { "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ=="], "bl": ["bl@5.1.0", "", { "dependencies": { "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ=="],
"body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="],
"boxen": ["boxen@7.1.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^7.0.1", "chalk": "^5.2.0", "cli-boxes": "^3.0.0", "string-width": "^5.1.2", "type-fest": "^2.13.0", "widest-line": "^4.0.1", "wrap-ansi": "^8.1.0" } }, "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog=="], "boxen": ["boxen@7.1.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^7.0.1", "chalk": "^5.2.0", "cli-boxes": "^3.0.0", "string-width": "^5.1.2", "type-fest": "^2.13.0", "widest-line": "^4.0.1", "wrap-ansi": "^8.1.0" } }, "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog=="],
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
@ -247,6 +278,12 @@
"buffer-builder": ["buffer-builder@0.2.0", "", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="], "buffer-builder": ["buffer-builder@0.2.0", "", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"camelcase": ["camelcase@7.0.1", "", {}, "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw=="], "camelcase": ["camelcase@7.0.1", "", {}, "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw=="],
"caniuse-lite": ["caniuse-lite@1.0.30001726", "", {}, "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw=="], "caniuse-lite": ["caniuse-lite@1.0.30001726", "", {}, "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw=="],
@ -271,6 +308,14 @@
"colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="], "colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="],
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
"cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
@ -279,58 +324,104 @@
"de-indent": ["de-indent@1.0.2", "", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="], "de-indent": ["de-indent@1.0.2", "", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="],
"debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electron-to-chromium": ["electron-to-chromium@1.5.179", "", {}, "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ=="], "electron-to-chromium": ["electron-to-chromium@1.5.179", "", {}, "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ=="],
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="], "enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="], "esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
"express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
"fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], "fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
"filesize": ["filesize@10.1.6", "", {}, "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w=="], "filesize": ["filesize@10.1.6", "", {}, "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w=="],
"finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="], "fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"gsap": ["gsap@3.13.0", "", {}, "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw=="], "gsap": ["gsap@3.13.0", "", {}, "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
@ -341,6 +432,8 @@
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
@ -391,6 +484,20 @@
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
"merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
"methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="],
"mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
@ -407,12 +514,16 @@
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
"ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"node-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="], "node-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="],
"node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], "node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="],
@ -423,6 +534,10 @@
"normalize.css": ["normalize.css@8.0.1", "", {}, "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="], "normalize.css": ["normalize.css@8.0.1", "", {}, "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
@ -433,12 +548,16 @@
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
@ -451,8 +570,16 @@
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
"qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="],
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
@ -463,7 +590,9 @@
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"sass-embedded": ["sass-embedded@1.89.2", "", { "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-android-arm": "1.89.2", "sass-embedded-android-arm64": "1.89.2", "sass-embedded-android-riscv64": "1.89.2", "sass-embedded-android-x64": "1.89.2", "sass-embedded-darwin-arm64": "1.89.2", "sass-embedded-darwin-x64": "1.89.2", "sass-embedded-linux-arm": "1.89.2", "sass-embedded-linux-arm64": "1.89.2", "sass-embedded-linux-musl-arm": "1.89.2", "sass-embedded-linux-musl-arm64": "1.89.2", "sass-embedded-linux-musl-riscv64": "1.89.2", "sass-embedded-linux-musl-x64": "1.89.2", "sass-embedded-linux-riscv64": "1.89.2", "sass-embedded-linux-x64": "1.89.2", "sass-embedded-win32-arm64": "1.89.2", "sass-embedded-win32-x64": "1.89.2" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-Ack2K8rc57kCFcYlf3HXpZEJFNUX8xd8DILldksREmYXQkRHI879yy8q4mRDJgrojkySMZqmmmW1NxrFxMsYaA=="], "sass-embedded": ["sass-embedded@1.89.2", "", { "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-android-arm": "1.89.2", "sass-embedded-android-arm64": "1.89.2", "sass-embedded-android-riscv64": "1.89.2", "sass-embedded-android-x64": "1.89.2", "sass-embedded-darwin-arm64": "1.89.2", "sass-embedded-darwin-x64": "1.89.2", "sass-embedded-linux-arm": "1.89.2", "sass-embedded-linux-arm64": "1.89.2", "sass-embedded-linux-musl-arm": "1.89.2", "sass-embedded-linux-musl-arm64": "1.89.2", "sass-embedded-linux-musl-riscv64": "1.89.2", "sass-embedded-linux-musl-x64": "1.89.2", "sass-embedded-linux-riscv64": "1.89.2", "sass-embedded-linux-x64": "1.89.2", "sass-embedded-win32-arm64": "1.89.2", "sass-embedded-win32-x64": "1.89.2" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-Ack2K8rc57kCFcYlf3HXpZEJFNUX8xd8DILldksREmYXQkRHI879yy8q4mRDJgrojkySMZqmmmW1NxrFxMsYaA=="],
@ -501,14 +630,28 @@
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
"serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="],
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="], "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"sharp": ["sharp@0.32.6", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", "semver": "^7.5.4", "simple-get": "^4.0.1", "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" } }, "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w=="], "sharp": ["sharp@0.32.6", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", "semver": "^7.5.4", "simple-get": "^4.0.1", "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" } }, "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
@ -519,6 +662,8 @@
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"stdin-discarder": ["stdin-discarder@0.1.0", "", { "dependencies": { "bl": "^5.0.0" } }, "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ=="], "stdin-discarder": ["stdin-discarder@0.1.0", "", { "dependencies": { "bl": "^5.0.0" } }, "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ=="],
"streamx": ["streamx@2.22.1", "", { "dependencies": { "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA=="], "streamx": ["streamx@2.22.1", "", { "dependencies": { "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA=="],
@ -555,20 +700,32 @@
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="], "varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"vite": ["vite@7.0.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw=="], "vite": ["vite@7.0.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw=="],
"vite-plugin-minipic": ["vite-plugin-minipic@1.3.0", "", { "dependencies": { "@rollup/pluginutils": "^5.0.3", "boxen": "^7.1.1", "chalk": "^5.3.0", "filesize": "^10.0.12", "glob": "^10.3.10", "lodash-es": "^4.17.21", "ora": "^7.0.1", "rollup": "^4.40.0", "sharp": "^0.32.4" } }, "sha512-iSfqsXqxDg0NZW+3UrSG9PkLjL2DbleDoXeI5jd48kze770bOuJa4WhH4/OYhaDvy8jaCTSVeV5jmDksIcXh+w=="], "vite-plugin-minipic": ["vite-plugin-minipic@1.3.0", "", { "dependencies": { "@rollup/pluginutils": "^5.0.3", "boxen": "^7.1.1", "chalk": "^5.3.0", "filesize": "^10.0.12", "glob": "^10.3.10", "lodash-es": "^4.17.21", "ora": "^7.0.1", "rollup": "^4.40.0", "sharp": "^0.32.4" } }, "sha512-iSfqsXqxDg0NZW+3UrSG9PkLjL2DbleDoXeI5jd48kze770bOuJa4WhH4/OYhaDvy8jaCTSVeV5jmDksIcXh+w=="],
@ -611,14 +768,24 @@
"prebuild-install/tar-fs": ["tar-fs@2.1.3", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg=="], "prebuild-install/tar-fs": ["tar-fs@2.1.3", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg=="],
"readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
"send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"string_decoder/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"tunnel-agent/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],

View File

@ -4,9 +4,19 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> <meta name="viewport"
<link rel="stylesheet" href="https://registry.npmmirror.com/@fontsource-variable/noto-sans-sc/5.2.5/files/index.css"> content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<link rel="stylesheet"
href="https://registry.npmmirror.com/@fontsource-variable/noto-sans-sc/5.2.5/files/index.css">
<title>弹性福利平台</title> <title>弹性福利平台</title>
<style>
#app {
height: 100vh;
height: 100dvh;
width: 100vw;
overflow: hidden;
}
</style>
</head> </head>
<body> <body>

View File

@ -6,10 +6,16 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc -b && vite build", "build": "vue-tsc -b && vite build",
"preview": "vite preview" "preview": "vite preview",
"server": "bun run server.ts",
"server:dev": "bun --watch server.ts",
"start": "vue-tsc -b && vite build && bun run server.ts"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@types/express": "^4.17.21",
"@types/node": "^24.0.13",
"express": "^4.19.2",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",

51
scores.json Normal file
View File

@ -0,0 +1,51 @@
[
{
"name": "Mike",
"time": 45.5,
"region": "大北区",
"store": "大连恒隆广场",
"timestamp": 1752309222243
},
{
"name": "小明",
"time": 98.2,
"region": "大西区",
"store": "太原万象城",
"timestamp": 1752309242794
},
{
"name": "小白",
"time": 83.2,
"region": "大西区",
"store": "太原万象城",
"timestamp": 1752309242794
},
{
"name": "兔宝",
"time": 138.2,
"region": "奥莱东区",
"store": "广州太古汇",
"timestamp": 1752309242794
},
{
"name": "Elena",
"time": 122.2,
"region": "大南区",
"store": "深圳湾万象城",
"timestamp": 1752309242794
},
{
"name": "Tom",
"time": 133.2,
"region": "免税",
"store": "海口新海港",
"timestamp": 1752309242794
},
{
"name": "阿迪斯",
"time": 1.9645,
"region": "奥莱",
"store": "北京斯普瑞斯",
"timestamp": 1752309530750
}
]

177
server.ts Normal file
View File

@ -0,0 +1,177 @@
import express from 'express';
import { promises as fs } from 'fs';
import path from 'path';
const app = express();
const PORT = process.env.PORT || 3001;
const SCORES_FILE = path.join(__dirname, 'scores.json');
// 分数数据类型
interface ScoreEntry {
name: string;
time: number;
region: string;
store: string;
timestamp: number;
}
// 中间件
app.use(express.json());
app.use(express.static('dist')); // 服务静态文件
// 初始化分数文件
async function initScoresFile() {
try {
await fs.access(SCORES_FILE);
} catch {
// 文件不存在,创建空数组
await fs.writeFile(SCORES_FILE, JSON.stringify([]));
}
}
// 读取分数数据
async function readScores(): Promise<ScoreEntry[]> {
try {
const data = await fs.readFile(SCORES_FILE, 'utf-8');
return JSON.parse(data);
} catch {
return [];
}
}
// 写入分数数据
async function writeScores(scores: ScoreEntry[]): Promise<void> {
await fs.writeFile(SCORES_FILE, JSON.stringify(scores, null, 2));
}
// POST /api/score - 提交分数
app.post('/api/score', async (req, res) => {
try {
const { region, store, name, time } = req.body;
// 验证必要字段
if (!region || !store || !name || typeof time !== 'number') {
return res.status(400).json({
error: '缺少必要字段: region, store, name, time'
});
}
// 验证时间值
if (time <= 0) {
return res.status(400).json({
error: '时间必须大于0'
});
}
const scores = await readScores();
// 创建新的分数记录
const newScore: ScoreEntry = {
name: String(name).trim(),
time: Number(time),
region: String(region).trim(),
store: String(store).trim(),
timestamp: Date.now()
};
// 检查是否已有相同用户的记录
const existingIndex = scores.findIndex(
score => score.name === newScore.name &&
score.region === newScore.region &&
score.store === newScore.store
);
if (existingIndex !== -1) {
// 如果新时间更好(更短),则更新记录
if (newScore.time < scores[existingIndex].time) {
scores[existingIndex] = newScore;
}
} else {
// 添加新记录
scores.push(newScore);
}
await writeScores(scores);
res.json({
success: true,
message: '分数提交成功',
score: newScore
});
} catch (error) {
console.error('提交分数时出错:', error);
res.status(500).json({
error: '服务器内部错误'
});
}
});
// GET /api/score - 获取排行榜前6名
app.get('/api/score', async (req, res) => {
try {
const scores = await readScores();
// 按时间排序时间短的在前并取前6名
const leaderBoard = scores
.sort((a, b) => a.time - b.time)
.slice(0, 6)
.map(score => ({
name: score.name,
time: score.time,
region: score.region,
store: score.store
}));
res.json(leaderBoard);
} catch (error) {
console.error('获取排行榜时出错:', error);
res.status(500).json({
error: '服务器内部错误'
});
}
});
// GET /api/score/all - 获取所有分数(可选,用于管理)
app.get('/api/score/all', async (req, res) => {
try {
const scores = await readScores();
res.json(scores.sort((a, b) => a.time - b.time));
} catch (error) {
console.error('获取所有分数时出错:', error);
res.status(500).json({
error: '服务器内部错误'
});
}
});
// 健康检查端点
app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString()
});
});
// 处理 SPA 路由 - 将所有未匹配的路由返回 index.html
app.get('/*', (req, res) => {
try {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
} catch (error) {
res.status(404).send('Page not found');
}
});
// 启动服务器
async function startServer() {
await initScoresFile();
app.listen(PORT, () => {
console.log(`🚀 服务器运行在 http://localhost:${PORT}`);
console.log(`📊 API 端点:`);
console.log(` POST /api/score - 提交分数`);
console.log(` GET /api/score - 获取排行榜`);
console.log(` GET /api/health - 健康检查`);
});
}
startServer().catch(console.error);

View File

@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref, watch } from 'vue';
import Game from './pages/Game.vue'; import Game from './pages/Game.vue';
import Page1 from './pages/Page1.vue'; import Page1 from './pages/Page1.vue';
import assets from './assets'; import assets from './assets';
import Loader from './pages/Loader.vue';
const stage = ref(0); const stage = ref(0);
@ -15,26 +16,174 @@ const userData = ref({
function startExploration(payload: { region: string; store: string; username: string }) { function startExploration(payload: { region: string; store: string; username: string }) {
userData.value = payload; userData.value = payload;
stage.value = 1; stage.value = 1;
setTimeout(() => {
showPage1.value = false;
}, 1000);
} }
function cloudUp() {
document.querySelectorAll('.cloud').forEach((ele) => {
ele.animate([
{ top: '300vw' },
], {
duration: 1000,
easing: 'ease-in',
fill: 'forwards',
});
});
}
function cloudDown() {
document.querySelectorAll('.cloud.all').forEach((ele) => {
ele.animate([
{ top: '-50vw' },
// @ts-ignore
{ top: ele.style.top },
], {
duration: 1000,
easing: 'ease-in',
fill: 'forwards',
});
});
}
let audio = new Audio(assets.bgm);
audio.loop = true;
audio.volume = 0.5;
const playingBgm = ref(false);
watch(playingBgm, (val) => {
if (val) {
audio.play();
} else {
audio.pause();
}
});
//std radio is 2462 / 1179
const scale = ref(1);
const onResize = () => {
let radio = window.innerHeight / window.innerWidth;
let stdRadio = 2462 / 1179;
if (radio < stdRadio * 0.88) {
scale.value = radio / stdRadio;
} else {
scale.value = 1;
}
};
window.addEventListener('resize', onResize);
onResize();
const showPage1 = ref(true);
</script> </script>
<template> <template>
<link v-for="ani in Object.values(assets.ani)" rel="preload" :href="ani" as="fetch"> <main :style="{ scale: scale }">
<main> <div class="bg"> </div>
<Page1 class="page" v-if="stage == 0" @start-exploration="startExploration"/> <img class="cloud cloud-top" src="./assets/game/云.png" v-for="c in [
<Game class="page" v-if="stage == 1" :userdata="userData"/> { w: 60, t: 10, l: 75, all: true },
{ w: 75, t: 55, l: -45 },
{ w: 40, t: 105, l: 35 },
]" :style="{ width: `${c.w}vw`, top: `${c.t}vw`, left: `${c.l}vw` }"
:class="{ all: c.all, left: c.l < 0, right: c.l > 0 }"></img>
<img class="cloud cloud-bottom" src="./assets/game/云.png" v-for="c in [
{ w: 75, t: 165, l: -35, all: true },
{ w: 100, t: 144, l: 60, all: true },
]" :style="{ width: `${c.w}vw`, top: `${c.t}vw`, left: `${c.l}vw` }"
:class="{ all: c.all, left: c.l < 0, right: c.l > 0 }"></img>
<Loader class="page" v-if="stage == -1" @loaded="stage = 0" />
<Page1 class="page page-1" v-if="stage >= 0&&showPage1" @start-exploration="startExploration"
:style="{ opacity: stage === 0 ? 1 : 0 }" />
<Game class="page" v-if="stage == 1" :userdata="userData"
@cloud-down="cloudDown" @cloud-up="cloudUp" />
<div class="overlay page">
<div class="bgm"
style="position: absolute; top: 16vw;right: 8vw;width: 8vw; cursor: pointer;pointer-events: all;">
<img src="./assets/开启音乐.png" @click="playingBgm = false" alt=""
v-if="playingBgm">
<img src="./assets/关掉音乐.png" @click="playingBgm = true" alt="" v-else>
</div>
</div>
</main> </main>
</template> </template>
<style scoped> <style scoped>
.overlay {
pointer-events: none;
}
main { main {
height: 100vh; height: 100vh;
height: 100dvh; height: 100dvh;
width: 100vw; width: 100vw;
overflow: hidden; overflow: visible;
position: relative; position: relative;
} }
.bgm {
img {
width: 100%;
}
}
@keyframes left-slide-in {
from {
transform: translateX(-300%);
}
to {
transform: translateX(0);
}
}
@keyframes right-slide-in {
from {
transform: translateX(300%);
}
to {
transform: translateX(0);
}
}
.cloud {
position: absolute;
&.left {
animation: left-slide-in 0.8s forwards;
}
&.right {
animation: right-slide-in 0.8s forwards;
}
}
.bg {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
height: calc(2462 / 1179 * 100vw);
width: 100%;
z-index: -1;
background-image: url('./assets/game/蓝天.png');
background-size: cover;
background-position: center;
}
.page { .page {
position: absolute; position: absolute;
top: 50%; top: 50%;
@ -42,6 +191,10 @@ main {
transform: translateY(-50%); transform: translateY(-50%);
width: 100%; width: 100%;
height: calc(2462 / 1179 * 100vw); height: calc(2462 / 1179 * 100vw);
}
.page-1{
transition: 0.7s all;
} }
</style> </style>

BIN
src/assets/bgm.mp3 Normal file

Binary file not shown.

View File

@ -42,5 +42,6 @@ export default {
[new URL('./game/metal_0.png', import.meta.url).href,], [new URL('./game/metal_0.png', import.meta.url).href,],
[new URL('./game/metal_1.png', import.meta.url).href,], [new URL('./game/metal_1.png', import.meta.url).href,],
[new URL('./game/metal_2.png', import.meta.url).href,] [new URL('./game/metal_2.png', import.meta.url).href,]
] ],
bgm: new URL('./bgm.mp3', import.meta.url).href,
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
src/assets/关掉音乐.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
src/assets/开启音乐.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
src/assets/返回按钮.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -35,7 +35,7 @@ const props = withDefaults(defineProps<Props>(), {
autoPlay: true autoPlay: true
}) })
// () //
const loading = ref(false) const loading = ref(false)
const error = ref('') const error = ref('')
const progress = ref(0) const progress = ref(0)
@ -53,6 +53,9 @@ const currentLoopCount = ref(0)
const animationId = ref<number>() const animationId = ref<number>()
const lastFrameTime = ref(0) const lastFrameTime = ref(0)
const pendingJumpTo = ref<string>() const pendingJumpTo = ref<string>()
// : soft jump Promise resolve
const pendingJumpResolver = ref<((success: boolean) => void) | null>(null)
// loadImageSequence () // loadImageSequence ()
const loadImageSequence = async (zipUrl: string) => { const loadImageSequence = async (zipUrl: string) => {
@ -68,7 +71,7 @@ const loadImageSequence = async (zipUrl: string) => {
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`) throw new Error(`HTTP error! status: ${response.status}`)
} }
111
const zipBlob = await response.blob() const zipBlob = await response.blob()
progress.value = 30 progress.value = 30
console.log(`加载压缩包: ${zipUrl}, 大小: ${(zipBlob.size / 1024).toFixed(2)} KB`); console.log(`加载压缩包: ${zipUrl}, 大小: ${(zipBlob.size / 1024).toFixed(2)} KB`);
@ -144,10 +147,6 @@ const drawFrame = (frameIndex: number) => {
if (!canvasRef.value || !loadedImages.value.length) return if (!canvasRef.value || !loadedImages.value.length) return
const safeIndex = Math.max(0, Math.min(frameIndex, loadedImages.value.length - 1)) const safeIndex = Math.max(0, Math.min(frameIndex, loadedImages.value.length - 1))
if (props.log) {
// console.log(`: ${safeIndex}, : ${loadedImages.value.length}`);
}
const canvas = canvasRef.value const canvas = canvasRef.value
const ctx = canvas.getContext('2d') const ctx = canvas.getContext('2d')
if (!ctx) return if (!ctx) return
@ -160,12 +159,17 @@ const drawFrame = (frameIndex: number) => {
} }
// ================================================================= // =================================================================
// 1: jumpToRule // 1: jumpToRule
// ================================================================= // =================================================================
const jumpToRule = (ruleName: string) => { const jumpToRule = (ruleName: string): boolean => {
const ruleIndex = findRuleIndex(ruleName) const ruleIndex = findRuleIndex(ruleName)
if (ruleIndex === -1) { if (ruleIndex === -1) {
console.warn(`未找到名为 "${ruleName}" 的规则`) console.warn(`未找到名为 "${ruleName}" 的规则`)
// Promise
if (pendingJumpResolver.value) {
pendingJumpResolver.value(false); // resolve(false)
pendingJumpResolver.value = null;
}
return false return false
} }
if (props.log) console.log(`跳转到规则 "${ruleName}" (索引: ${ruleIndex})`); if (props.log) console.log(`跳转到规则 "${ruleName}" (索引: ${ruleIndex})`);
@ -185,7 +189,6 @@ const jumpToRule = (ruleName: string) => {
} }
} }
// :
const endFrame = targetRule.endFrame ?? (startFrame + targetRule.frame - 1); const endFrame = targetRule.endFrame ?? (startFrame + targetRule.frame - 1);
currentFrame.value = targetRule.reverse ? endFrame : startFrame; currentFrame.value = targetRule.reverse ? endFrame : startFrame;
@ -201,12 +204,16 @@ const jumpToRule = (ruleName: string) => {
lastFrameTime.value = performance.now() lastFrameTime.value = performance.now()
animate() animate()
// : Promise
if (pendingJumpResolver.value) {
pendingJumpResolver.value(true);
pendingJumpResolver.value = null;
}
return true return true
} }
// ================================================================= // startAnimation ()
// 2: startAnimation
// =================================================================
const startAnimation = () => { const startAnimation = () => {
if (!props.rules.length || !loadedImages.value.length || isPlaying.value) return if (!props.rules.length || !loadedImages.value.length || isPlaying.value) return
@ -225,7 +232,6 @@ const startAnimation = () => {
} }
} }
// :
const endFrame = currentRule.endFrame ?? (startFrame + currentRule.frame - 1); const endFrame = currentRule.endFrame ?? (startFrame + currentRule.frame - 1);
currentFrame.value = currentRule.reverse ? endFrame : startFrame; currentFrame.value = currentRule.reverse ? endFrame : startFrame;
@ -235,9 +241,7 @@ const startAnimation = () => {
animate() animate()
} }
// ================================================================= // animate ()
// 3: animate
// =================================================================
const animate = () => { const animate = () => {
if (!isPlaying.value || currentRuleIndex.value >= props.rules.length) { if (!isPlaying.value || currentRuleIndex.value >= props.rules.length) {
animationId.value = undefined; animationId.value = undefined;
@ -253,7 +257,6 @@ const animate = () => {
if (now - lastFrameTime.value >= frameDuration) { if (now - lastFrameTime.value >= frameDuration) {
lastFrameTime.value = now - (now - lastFrameTime.value) % frameDuration; lastFrameTime.value = now - (now - lastFrameTime.value) % frameDuration;
// 1.
let startFrame = currentRule.startFrame ?? 0; let startFrame = currentRule.startFrame ?? 0;
if (currentRule.startFrame === undefined) { if (currentRule.startFrame === undefined) {
for (let i = 0; i < currentRuleIndex.value; i++) { for (let i = 0; i < currentRuleIndex.value; i++) {
@ -262,78 +265,61 @@ const animate = () => {
} }
const endFrame = currentRule.endFrame ?? (startFrame + currentRule.frame - 1); const endFrame = currentRule.endFrame ?? (startFrame + currentRule.frame - 1);
// 2.
let nextFrame = currentFrame.value + (currentRule.reverse ? -1 : 1); let nextFrame = currentFrame.value + (currentRule.reverse ? -1 : 1);
// 3.
const isEndOfRuleSegment = currentRule.reverse ? (nextFrame < startFrame) : (nextFrame > endFrame); const isEndOfRuleSegment = currentRule.reverse ? (nextFrame < startFrame) : (nextFrame > endFrame);
if (isEndOfRuleSegment) { if (isEndOfRuleSegment) {
//
currentLoopCount.value++; currentLoopCount.value++;
if (pendingJumpTo.value) {
const targetRule = pendingJumpTo.value;
pendingJumpTo.value = undefined;
if (props.log) console.log(`软跳转触发,目标规则 "${targetRule}"`);
jumpToRule(targetRule);
return;
}
const isRuleFinished = currentRule.loop > 0 && currentLoopCount.value >= currentRule.loop; const isRuleFinished = currentRule.loop > 0 && currentLoopCount.value >= currentRule.loop;
if (isRuleFinished) { if (isRuleFinished) {
//
if (pendingJumpTo.value) {
const targetRule = pendingJumpTo.value;
pendingJumpTo.value = undefined;
jumpToRule(targetRule);
return; // jumpToRuleanimate,
}
if (currentRule.pauseAfter) { if (currentRule.pauseAfter) {
isPaused.value = true; isPaused.value = true;
isPlaying.value = false; isPlaying.value = false;
if(props.log) console.log(`规则 "${currentRule.name}" 完成,暂停等待跳转指令`); if(props.log) console.log(`规则 "${currentRule.name}" 完成,暂停等待跳转指令`);
//
drawFrame(currentRule.reverse ? startFrame : endFrame); drawFrame(currentRule.reverse ? startFrame : endFrame);
return; return;
} }
//
currentRuleIndex.value++; currentRuleIndex.value++;
currentLoopCount.value = 0; currentLoopCount.value = 0;
if (currentRuleIndex.value >= props.rules.length) { if (currentRuleIndex.value >= props.rules.length) {
//
isPlaying.value = false; isPlaying.value = false;
if(props.log) console.log('所有规则播放完毕'); if(props.log) console.log('所有规则播放完毕');
//
drawFrame(currentRule.reverse ? startFrame : endFrame); drawFrame(currentRule.reverse ? startFrame : endFrame);
return; return;
} }
// :
const nextRule = props.rules[currentRuleIndex.value]; const nextRule = props.rules[currentRuleIndex.value];
let nextRuleStartFrame = nextRule.startFrame ?? 0; let nextRuleStartFrame = nextRule.startFrame ?? (endFrame + 1);
if (nextRule.startFrame === undefined) {
// 使
nextRuleStartFrame = endFrame + 1;
}
const nextRuleEndFrame = nextRule.endFrame ?? (nextRuleStartFrame + nextRule.frame - 1); const nextRuleEndFrame = nextRule.endFrame ?? (nextRuleStartFrame + nextRule.frame - 1);
// reverse currentFrame
currentFrame.value = nextRule.reverse ? nextRuleEndFrame : nextRuleStartFrame; currentFrame.value = nextRule.reverse ? nextRuleEndFrame : nextRuleStartFrame;
if(props.log) console.log(`进入下一规则 "${nextRule.name}", 实际开始帧: ${currentFrame.value}`); if(props.log) console.log(`进入下一规则 "${nextRule.name}", 实际开始帧: ${currentFrame.value}`);
} else { } else {
//
// : reverse
currentFrame.value = currentRule.reverse ? endFrame : startFrame; currentFrame.value = currentRule.reverse ? endFrame : startFrame;
} }
} else { } else {
//
currentFrame.value = nextFrame; currentFrame.value = nextFrame;
} }
// 4.
drawFrame(currentFrame.value); drawFrame(currentFrame.value);
} }
}; };
// stopAnimation () // stopAnimation ()
const stopAnimation = () => { const stopAnimation = () => {
isPlaying.value = false isPlaying.value = false
isPaused.value = false isPaused.value = false
@ -341,21 +327,27 @@ const stopAnimation = () => {
cancelAnimationFrame(animationId.value) cancelAnimationFrame(animationId.value)
animationId.value = undefined animationId.value = undefined
} }
// :
if (pendingJumpResolver.value) {
pendingJumpResolver.value(false); //
pendingJumpResolver.value = null;
}
pendingJumpTo.value = undefined;
} }
// resetAnimation () // resetAnimation ()
const resetAnimation = () => { const resetAnimation = () => {
stopAnimation() stopAnimation()
currentRuleIndex.value = 0 currentRuleIndex.value = 0
currentFrame.value = 0 currentFrame.value = 0
currentLoopCount.value = 0 currentLoopCount.value = 0
pendingJumpTo.value = undefined // pendingJumpTo pendingJumpResolver stopAnimation
if (loadedImages.value.length > 0) { if (loadedImages.value.length > 0) {
drawFrame(0) drawFrame(0)
} }
} }
// togglePlayback, setJumpTarget, jumpToRuleImmediately, resumeFromPause () // togglePlayback, resumeFromPause ()
const togglePlayback = () => { const togglePlayback = () => {
if (isPlaying.value) { if (isPlaying.value) {
stopAnimation() stopAnimation()
@ -364,18 +356,6 @@ const togglePlayback = () => {
} }
} }
const setJumpTarget = (ruleName: string) => {
if (isPaused.value) {
jumpToRule(ruleName)
} else {
pendingJumpTo.value = ruleName
}
}
const jumpToRuleImmediately = (ruleName: string) => {
return jumpToRule(ruleName)
}
const resumeFromPause = () => { const resumeFromPause = () => {
if (isPaused.value) { if (isPaused.value) {
isPaused.value = false isPaused.value = false
@ -385,12 +365,55 @@ const resumeFromPause = () => {
} }
} }
// =================================================================
// 2:
// =================================================================
/**
* 立即跳转到指定规则会打断当前动画
* @param {string} ruleName 要跳转到的规则名
*/
const jumpTo = (ruleName: string) => {
return jumpToRule(ruleName)
}
// watch onMounted () /**
* 跳转该跳转会等到当前规则循环结束后再执行
* 如果动画已暂停或停止则立即跳转
* @param {string} ruleName 要跳转到的规则名
* @returns {Promise<boolean>} 一个Promise当成功跳转到目标规则时 resolve(true)否则 resolve(false)
*/
const jumpToSoftly = (ruleName: string): Promise<boolean> => {
return new Promise((resolve) => {
const ruleIndex = findRuleIndex(ruleName);
if (ruleIndex === -1) {
console.warn(`无法设置软跳转:未找到规则 "${ruleName}"。`);
resolve(false);
return;
}
//
if (pendingJumpResolver.value) {
pendingJumpResolver.value(false);
}
// Promise resolver
pendingJumpResolver.value = resolve;
if (isPaused.value || !isPlaying.value) {
if(props.log) console.log(`动画已暂停或停止。立即执行跳转到 "${ruleName}"。`);
// jumpToRule pendingJumpResolver
jumpToRule(ruleName);
} else if (isPlaying.value) {
if(props.log) console.log(`已计划软跳转到 "${ruleName}"。将在当前循环结束后触发。`);
pendingJumpTo.value = ruleName;
}
});
}
// watch ()
watch(() => props.url, (newUrl) => { watch(() => props.url, (newUrl) => {
if (newUrl) { if (newUrl) {
stopAnimation() resetAnimation() // 使 reset
//
images.value.forEach(url => URL.revokeObjectURL(url)); images.value.forEach(url => URL.revokeObjectURL(url));
images.value = []; images.value = [];
loadedImages.value = []; loadedImages.value = [];
@ -408,16 +431,16 @@ watch(() => props.rules, () => {
}, { deep: true }) }, { deep: true })
onBeforeUnmount(() => { onBeforeUnmount(() => {
stopAnimation(); resetAnimation(); // 使 reset
// Blob URL
images.value.forEach(url => URL.revokeObjectURL(url)); images.value.forEach(url => URL.revokeObjectURL(url));
}); });
// findRuleIndex defineExpose () // findRuleIndex ()
const findRuleIndex = (ruleName: string): number => { const findRuleIndex = (ruleName: string): number => {
return props.rules.findIndex(rule => rule.name === ruleName) return props.rules.findIndex(rule => rule.name === ruleName)
} }
// defineExpose ()
defineExpose({ defineExpose({
images, images,
loadedImages, loadedImages,
@ -434,8 +457,10 @@ defineExpose({
reset: resetAnimation, reset: resetAnimation,
toggle: togglePlayback, toggle: togglePlayback,
resume: resumeFromPause, resume: resumeFromPause,
jumpTo: jumpToRuleImmediately, /** 立即跳转,会打断当前动画。 */
setJumpTarget, jumpTo,
/** 在当前动画循环结束后跳转返回一个Promise。 */
jumpToSoftly,
drawFrame: (frameIndex: number) => drawFrame(frameIndex), drawFrame: (frameIndex: number) => drawFrame(frameIndex),
findRuleIndex, findRuleIndex,
getRules: () => props.rules, getRules: () => props.rules,

View File

@ -1,168 +0,0 @@
<!-- AniLoader.vue -->
<template>
<!-- This component is renderless; it just provides context -->
<slot :is-all-loaded="isAllLoaded" :progress="overallProgress"></slot>
</template>
<script setup lang="ts">
import { ref, provide, onMounted, computed, watch } from 'vue'
import JSZip from 'jszip'
import { animationStore, AnimationStoreKey, type AnimationData } from './animation-store'
interface Asset {
id: string; // A unique identifier for the animation, e.g., 'character-jump'
url: string; // The URL to the .zip file
}
interface Props {
assets: Asset[];
autoLoad?: boolean; // Start loading immediately on mount
}
const props = withDefaults(defineProps<Props>(), {
autoLoad: true,
})
const totalAssets = ref(props.assets.length);
const loadedAssetsCount = ref(0);
// Provide the reactive store to all descendant components
provide(AnimationStoreKey, animationStore)
const isAllLoaded = computed(() => {
if (totalAssets.value === 0) return true;
// Check if every asset in the store that should be loaded is actually loaded.
return props.assets.every(asset => animationStore.get(asset.id)?.status === 'loaded');
});
const overallProgress = computed(() => {
if (totalAssets.value === 0) return 100;
let totalProgress = 0;
for (const asset of props.assets) {
totalProgress += animationStore.get(asset.id)?.progress ?? 0;
}
return totalProgress / totalAssets.value;
});
const loadAndProcessAnimation = async (id: string, url: string) => {
// Prevent re-loading if already loaded or loading
if (animationStore.has(id) && animationStore.get(id)?.status !== 'error') {
if(animationStore.get(id)?.status === 'loaded') {
loadedAssetsCount.value++;
}
return;
}
// Set initial state in the store
animationStore.set(id, {
status: 'loading',
frames: [],
width: 0,
height: 0,
totalFrames: 0,
progress: 0,
});
const updateProgress = (p: number) => {
const data = animationStore.get(id);
if (data) data.progress = p;
};
try {
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
updateProgress(15);
const zipBlob = await response.blob()
const zip = new JSZip()
const zipData = await zip.loadAsync(zipBlob)
updateProgress(30);
const imageFiles = Object.keys(zipData.files)
.filter(name => !zipData.files[name].dir && /\.(webp|png|jpg)$/i.test(name))
.sort((a, b) => {
const numA = parseInt(a.match(/(\d+)\.\w+$/)?.[1] || '0')
const numB = parseInt(b.match(/(\d+)\.\w+$/)?.[1] || '0')
return numA - numB
})
if (imageFiles.length === 0) throw new Error('No supported image files found in ZIP.')
const blobUrls = await Promise.all(
imageFiles.map(name => zipData.files[name].async('blob').then(URL.createObjectURL))
);
updateProgress(50);
const imageDataArray: ImageData[] = [];
const offscreenCanvas = document.createElement('canvas');
const offscreenCtx = offscreenCanvas.getContext('2d', { willReadFrequently: true });
if (!offscreenCtx) throw new Error("Failed to create offscreen canvas context.");
let canvasWidth = 0, canvasHeight = 0;
for (let i = 0; i < blobUrls.length; i++) {
const img = new Image();
img.src = blobUrls[i];
await new Promise<void>((resolve, reject) => {
img.onload = () => {
if (i === 0) {
canvasWidth = img.width;
canvasHeight = img.height;
offscreenCanvas.width = canvasWidth;
offscreenCanvas.height = canvasHeight;
}
offscreenCtx.clearRect(0, 0, canvasWidth, canvasHeight);
offscreenCtx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
imageDataArray.push(offscreenCtx.getImageData(0, 0, canvasWidth, canvasHeight));
URL.revokeObjectURL(img.src);
resolve();
};
img.onerror = reject;
});
updateProgress(50 + (i + 1) / blobUrls.length * 50);
}
// Update store with final data
animationStore.set(id, {
status: 'loaded',
frames: imageDataArray,
width: canvasWidth,
height: canvasHeight,
totalFrames: imageDataArray.length,
progress: 100
});
loadedAssetsCount.value++;
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown loading error';
console.error(`Failed to load animation "${id}":`, err);
animationStore.set(id, {
status: 'error',
error: message,
frames: [], width: 0, height: 0, totalFrames: 0, progress: 0
});
}
}
const loadAll = () => {
props.assets.forEach(asset => loadAndProcessAnimation(asset.id, asset.url));
}
// Watch for changes in assets prop to dynamically load new ones
watch(() => props.assets, (newAssets) => {
totalAssets.value = newAssets.length;
if (props.autoLoad) {
loadAll();
}
}, { deep: true, immediate: true });
// Expose the loader function for manual triggering
defineExpose({
loadAll,
isAllLoaded,
animationStore
});
</script>

View File

@ -1,302 +0,0 @@
<!-- AniPlayer.vue -->
<template>
<div class="ani-ele">
<div v-if="loading">Loading Animation...</div>
<div v-else-if="error">{{ error }}</div>
<canvas v-else ref="canvasRef" :width="canvasWidth" :height="canvasHeight"></canvas>
</div>
</template>
<script setup lang="ts">
import { ref, watch, onBeforeUnmount, inject, computed, nextTick } from 'vue'
import { AnimationStoreKey } from './animation-store'
// Rules interface remains the same
interface AnimationRule {
name: string; frame: number; duration?: number; loop: number;
startFrame?: number; endFrame?: number; reverse?: boolean;
pauseAfter?: boolean;
}
interface Props {
animationId: string; // The ID to look up in the store
rules: AnimationRule[];
width?: number; // Optional override for width
height?: number; // Optional override for height
autoPlay?: boolean;
log?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
autoPlay: true,
});
// Inject the central store
const animationStore = inject(AnimationStoreKey);
if (!animationStore) {
throw new Error("AniPlayer must be used within an AniLoader provider.");
}
// --- Reactive state derived from the store ---
const animationData = computed(() => animationStore.get(props.animationId));
const loading = computed(() => !animationData.value || animationData.value.status === 'loading');
const error = computed(() => animationData.value?.status === 'error' ? animationData.value.error : '');
const loadedImages = computed(() => animationData.value?.frames ?? []);
// Canvas dimensions are derived from loaded data but can be overridden by props
const canvasWidth = computed(() => props.width ?? animationData.value?.width ?? 300);
const canvasHeight = computed(() => props.height ?? animationData.value?.height ?? 150);
// --- Playback State (mostly unchanged) ---
const canvasRef = ref<HTMLCanvasElement>()
const isPlaying = ref(false)
const isPaused = ref(false)
const currentRuleIndex = ref(0)
const currentFrame = ref(0)
const currentLoopCount = ref(0)
const animationId = ref<number>()
const lastFrameTime = ref(0)
const pendingJumpTo = ref<string>()
// --- Core Logic (unchanged, but now operates on computed refs) ---
const drawFrame = (frameIndex: number) => {
if (!canvasRef.value || loadedImages.value.length === 0) return;
const safeIndex = Math.max(0, Math.min(frameIndex, loadedImages.value.length - 1));
const ctx = canvasRef.value.getContext('2d');
if (!ctx) return;
const imageData = loadedImages.value[safeIndex];
if (imageData) {
ctx.putImageData(imageData, 0, 0);
}
};
const jumpToRule = (ruleName: string) => { /* ... (code is identical to previous version) ... */
const ruleIndex = findRuleIndex(ruleName)
if (ruleIndex === -1) {
console.warn(`未找到名为 "${ruleName}" 的规则`)
return false
}
if (props.log) console.log(`跳转到规则 "${ruleName}" (索引: ${ruleIndex})`);
if (isPlaying.value) {
stopAnimation()
}
currentRuleIndex.value = ruleIndex
currentLoopCount.value = 0
const targetRule = props.rules[ruleIndex]
let startFrame = targetRule.startFrame ?? 0
if (targetRule.startFrame === undefined) {
for (let i = 0; i < ruleIndex; i++) {
startFrame += props.rules[i].frame
}
}
const endFrame = targetRule.endFrame ?? (startFrame + targetRule.frame - 1);
currentFrame.value = targetRule.reverse ? endFrame : startFrame;
if (props.log) {
console.log(`跳转到规则 "${ruleName}", 实际开始帧: ${currentFrame.value}, 规则帧范围: ${startFrame} - ${endFrame}`);
}
drawFrame(currentFrame.value)
isPaused.value = false
pendingJumpTo.value = undefined
isPlaying.value = true
lastFrameTime.value = performance.now()
animate()
return true
};
const startAnimation = () => { /* ... (code is identical to previous version) ... */
if (!props.rules.length || loadedImages.value.length === 0 || isPlaying.value) return
isPlaying.value = true
isPaused.value = false
if (currentRuleIndex.value >= props.rules.length) {
resetAnimation()
}
const currentRule = props.rules[currentRuleIndex.value]
let startFrame = currentRule.startFrame ?? 0
if (currentRule.startFrame === undefined) {
for (let i = 0; i < currentRuleIndex.value; i++) {
startFrame += props.rules[i].frame
}
}
const endFrame = currentRule.endFrame ?? (startFrame + currentRule.frame - 1);
currentFrame.value = currentRule.reverse ? endFrame : startFrame;
if (props.log) console.log(`开始播放规则 "${currentRule.name}", 实际开始帧: ${currentFrame.value}`);
lastFrameTime.value = performance.now()
animate()
};
const animate = () => { /* ... (code is identical to previous version) ... */
if (!isPlaying.value || currentRuleIndex.value >= props.rules.length) {
animationId.value = undefined;
return;
}
animationId.value = requestAnimationFrame(animate);
const now = performance.now();
const currentRule = props.rules[currentRuleIndex.value];
const frameDuration = currentRule.duration ?? 33;
if (now - lastFrameTime.value >= frameDuration) {
lastFrameTime.value = now - (now - lastFrameTime.value) % frameDuration;
let startFrame = currentRule.startFrame ?? 0;
if (currentRule.startFrame === undefined) {
for (let i = 0; i < currentRuleIndex.value; i++) {
startFrame += props.rules[i].frame;
}
}
const endFrame = currentRule.endFrame ?? (startFrame + currentRule.frame - 1);
let nextFrame = currentFrame.value + (currentRule.reverse ? -1 : 1);
const isEndOfRuleSegment = currentRule.reverse ? (nextFrame < startFrame) : (nextFrame > endFrame);
if (isEndOfRuleSegment) {
currentLoopCount.value++;
const isRuleFinished = currentRule.loop > 0 && currentLoopCount.value >= currentRule.loop;
if (isRuleFinished) {
if (pendingJumpTo.value) {
const targetRule = pendingJumpTo.value;
pendingJumpTo.value = undefined;
jumpToRule(targetRule);
return;
}
if (currentRule.pauseAfter) {
isPaused.value = true;
isPlaying.value = false;
if (props.log) console.log(`规则 "${currentRule.name}" 完成,暂停等待跳转指令`);
drawFrame(currentRule.reverse ? startFrame : endFrame);
return;
}
currentRuleIndex.value++;
currentLoopCount.value = 0;
if (currentRuleIndex.value >= props.rules.length) {
isPlaying.value = false;
if (props.log) console.log('所有规则播放完毕');
drawFrame(currentRule.reverse ? startFrame : endFrame);
return;
}
const nextRule = props.rules[currentRuleIndex.value];
let nextRuleStartFrame = nextRule.startFrame ?? 0;
if (nextRule.startFrame === undefined) {
nextRuleStartFrame = endFrame + 1;
}
const nextRuleEndFrame = nextRule.endFrame ?? (nextRuleStartFrame + nextRule.frame - 1);
currentFrame.value = nextRule.reverse ? nextRuleEndFrame : nextRuleStartFrame;
if (props.log) console.log(`进入下一规则 "${nextRule.name}", 实际开始帧: ${currentFrame.value}`);
} else {
currentFrame.value = currentRule.reverse ? endFrame : startFrame;
}
} else {
currentFrame.value = nextFrame;
}
drawFrame(currentFrame.value);
}
};
const stopAnimation = () => { /* ... (code is identical) ... */
isPlaying.value = false
isPaused.value = false
if (animationId.value) {
cancelAnimationFrame(animationId.value)
animationId.value = undefined
}
};
const resetAnimation = () => { /* ... (code is identical) ... */
stopAnimation()
currentRuleIndex.value = 0
currentFrame.value = 0
currentLoopCount.value = 0
pendingJumpTo.value = undefined
if (loadedImages.value.length > 0) {
drawFrame(0)
}
};
const findRuleIndex = (ruleName: string): number => { /* ... (code is identical) ... */
return props.rules.findIndex(rule => rule.name === ruleName)
};
// Other exposed methods...
const togglePlayback = () => { if (isPlaying.value) { stopAnimation() } else { startAnimation() } };
const setJumpTarget = (ruleName: string) => { if (isPaused.value) { jumpToRule(ruleName) } else { pendingJumpTo.value = ruleName } };
const jumpToRuleImmediately = (ruleName: string) => { return jumpToRule(ruleName) };
const resumeFromPause = () => { if (isPaused.value) { isPaused.value = false; isPlaying.value = true; lastFrameTime.value = performance.now(); animate() } };
// Watch for the data to become available to start the animation
watch(loadedImages, (newFrames, oldFrames) => {
// Only trigger if we're transitioning from 'no frames' to 'having frames'
if (newFrames.length > 0 && oldFrames.length === 0) {
resetAnimation();
// Use nextTick to ensure canvas is rendered before drawing
nextTick(() => {
drawFrame(0); // Draw the first frame immediately
if (props.autoPlay) {
startAnimation();
}
});
}
});
// Watch for rules changing
watch(() => props.rules, () => {
if (loadedImages.value.length > 0) {
resetAnimation();
if (props.autoPlay) {
startAnimation();
}
}
}, { deep: true });
onBeforeUnmount(() => {
stopAnimation();
});
defineExpose({
// Expose control methods
play: startAnimation,
pause: stopAnimation,
reset: resetAnimation,
toggle: togglePlayback,
resume: resumeFromPause,
jumpTo: jumpToRuleImmediately,
setJumpTarget,
drawFrame,
// Expose state
isPlaying,
isPaused,
currentFrame,
getCurrentRule: () => props.rules[currentRuleIndex.value],
});
</script>
<style scoped>
.ani-ele {
/* Add styling for loading/error states if desired */
min-height: 100px;
/* Example */
display: flex;
justify-content: center;
align-items: center;
}
canvas {
width: 100%;
height: auto;
object-fit: contain;
}
</style>

View File

@ -1,23 +0,0 @@
// src/composables/animation-store.ts (or wherever you prefer)
import { reactive } from 'vue'
import type { InjectionKey } from 'vue'
// Defines the data structure for a single loaded animation
export interface AnimationData {
status: 'idle' | 'loading' | 'loaded' | 'error';
frames: ImageData[];
width: number;
height: number;
totalFrames: number;
error?: string;
progress: number;
}
// Defines the store, which is a map from a unique animation ID to its data
export type AnimationStore = Map<string, AnimationData>
// Create a reactive store instance
export const animationStore = reactive<AnimationStore>(new Map())
// Create a unique Symbol as the injection key. This is best practice.
export const AnimationStoreKey: InjectionKey<AnimationStore> = Symbol('AnimationStore')

View File

@ -16,15 +16,15 @@ const sunEle = useTemplateRef('sun-ani');
const sunEndEle = useTemplateRef('sun-ani-end'); const sunEndEle = useTemplateRef('sun-ani-end');
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const emit = defineEmits(['cloudUp', 'cloudDown', 'restart']);
async function shoot() { async function shoot() {
//sunEle.value?.jumpTo('') //sunEle.value?.jumpTo('')
sunEle.value?.jumpTo('蓄力飞') sunEle.value?.jumpTo('蓄力飞')
await document.querySelector('.sun-ani-wrapper')?.animate([ await document.querySelector('.sun-ani-wrapper')?.animate([
{ transform: 'translateY(0)' }, { transform: 'translateY(0) translateX(-50%)' },
{ transform: 'translateY(8%)' }, { transform: 'translateY(8%) translateX(-50%)' },
], { ], {
duration: 900, duration: 900,
easing: 'linear', easing: 'linear',
@ -41,15 +41,7 @@ async function shoot() {
}).finished.then(() => { }).finished.then(() => {
document.querySelector('.arrow')?.remove(); document.querySelector('.arrow')?.remove();
}); });
document.querySelectorAll('.cloud').forEach((ele) => { emit('cloudUp');
ele.animate([
{ top: '300vw' },
], {
duration: 1000,
easing: 'ease-in',
fill: 'forwards',
});
});
document.querySelector('.bed')?.animate([ document.querySelector('.bed')?.animate([
{ transform: 'translateY(0)' }, { transform: 'translateY(0)' },
{ transform: 'translateY(150%)' }, { transform: 'translateY(150%)' },
@ -61,38 +53,30 @@ async function shoot() {
}); });
await document.querySelector('.sun-ani-wrapper')?.animate([ await document.querySelector('.sun-ani-wrapper')?.animate([
{ transform: 'translateY(8%)' }, { transform: 'translateY(8%) translateX(-50%)', width: '60vw' },
{ transform: 'translateY(-130%)' }, { transform: 'translateY(-180%) translateX(-50%)', width: '50vw' },
], { ], {
duration: 3000, duration: 3200,
easing: 'ease-in-out', easing: 'cubic-bezier(0.99, 0.13, 0.35, 0.74)',
fill: 'forwards', fill: 'forwards',
}).finished }).finished
document.querySelectorAll('.cloud.all').forEach((ele) => { emit('cloudDown');
ele.animate([
{ top: '-50vw' },
// @ts-ignore
{ top: ele.style.top },
], {
duration: 1000,
easing: 'ease-in',
fill: 'forwards',
});
});
canAction.value = true;
gameLoop();
await document.querySelector('.sun-ani-wrapper')?.animate([ await document.querySelector('.sun-ani-wrapper')?.animate([
{ transform: 'translateY(-130%)' }, { transform: 'translateY(-180%) translateX(-50%)' },
{ transform: 'translateY(-70%)' }, { transform: 'translateY(-70%) translateX(-50%)' },
], { ], {
duration: 2200, duration: 1200,
easing: 'ease', easing: 'cubic-bezier(0.22, 0.61, 0.36, 1)',
fill: 'forwards', fill: 'forwards',
}).finished }).finished
/* await wait(1000) */
canAction.value = true;
gameLoop();
} }
const gPos: Ref<'left' | 'center' | 'right'> = ref('center'); const gPos: Ref<'left' | 'center' | 'right'> = ref('center');
const gDeg = computed(() => ({ const gDeg = computed(() => ({
@ -101,7 +85,7 @@ const gDeg = computed(() => ({
right: 8 right: 8
})[gPos.value]); })[gPos.value]);
const SPEED = 1.2; const SPEED = 1.8;
const transitionOn = ref(true); const transitionOn = ref(true);
let lastTime = 0; let lastTime = 0;
@ -118,7 +102,9 @@ const calcGiftDis = () => {
} else return 0; } else return 0;
} }
const collisionThreshold = 20; // vw const collisionThreshold = 10; // vw
const isPlayingDropAni = ref(false);
function gameLoop(currentTime = 0) { function gameLoop(currentTime = 0) {
let pauseFlag = false; let pauseFlag = false;
@ -127,7 +113,7 @@ function gameLoop(currentTime = 0) {
giftPos.value = [null, null]; giftPos.value = [null, null];
giftShow.value = false; giftShow.value = false;
lastTime = currentTime; lastTime = currentTime;
if (giftProgress.value >= giftList.length ) { if (giftProgress.value >= giftList.length) {
// //
console.log('游戏结束!耗时:', timeSpent.value / 1000, '秒'); console.log('游戏结束!耗时:', timeSpent.value / 1000, '秒');
gameEnd() gameEnd()
@ -141,55 +127,94 @@ function gameLoop(currentTime = 0) {
timeSpent.value += deltaTime timeSpent.value += deltaTime
// = * / 16.67 (60fps) // = * / 16.67 (60fps)
sunPos.value[1] -= SPEED * 0.3 * (deltaTime / 16.67); if (sunPos.value[1] < -50) {
if (sunPos.value[1] < -100) {
transitionOn.value = false; transitionOn.value = false;
sunPos.value[1] = 154; if (!isPlayingDropAni.value)
} else { (async () => {
isPlayingDropAni.value = true;
// sunPos.y to -150, than y to 174 and drop to 124
transitionOn.value = true;
while (sunPos.value[1] > -150) {
//
sunPos.value[1] -= SPEED * (deltaTime / 16.67) * 4;
await wait(16.67 / 2);
}
sunPos.value[1] = -150;
// 0.5
transitionOn.value = false;
await wait(500);
sunPos.value[1] = 124;
await wait(0);
transitionOn.value = true;
while (sunPos.value[1] > 66) {
sunPos.value[1] -= SPEED * (deltaTime / 16.67) * 6;
await wait(16.67 / 2);
}
// y = 66
sunPos.value[1] = 66;
isPlayingDropAni.value = false;
})();
} else if (!isPlayingDropAni.value) {
transitionOn.value = true; transitionOn.value = true;
sunPos.value[1] -= SPEED * 0.3 * (deltaTime / 16.67);
} }
if (giftPos.value[0] === null) {
// collisionThreshold * 1.5
while (true) {
// randomly generate gift
// x: -30 - 30
// y: -20 - 60
giftPos.value[0] = Math.random() * 60 - 30;
giftPos.value[1] = Math.random() * 80 - 20;
if (calcGiftDis() > collisionThreshold * 1.5) { const moveGift = () => {
break; // 退 if (giftPos.value[0] === null) {
} // collisionThreshold * 1.5
} while (true) {
} //
giftStartPos.value[0] = Math.random() * 60 - 30;
giftStartPos.value[1] = Math.random() * 80 - 20;
// gift slowly fly to the edge //
if (giftPos.value[0] !== null && giftPos.value[1] !== null) { giftEndPos.value[0] = Math.random() * 60 - 30;
const giftSpeed = 0.3 * (deltaTime / 16.67); giftEndPos.value[1] = Math.random() * 80 - 20;
// //
if (giftPos.value[0] < 0) { giftPos.value[0] = giftStartPos.value[0];
// giftPos.value[1] = giftStartPos.value[1];
giftPos.value[0] -= giftSpeed * 0.2;
if (giftPos.value[0] < -45) { //
giftPos.value[0] = -45; // giftMoveProgress.value = 0;
} giftMoveDirection.value = 1;
} else {
// if (calcGiftDis() > collisionThreshold * 1.5) {
giftPos.value[0] += giftSpeed * 0.2; break; // 退
if (giftPos.value[0] > 45) { }
giftPos.value[0] = 45; //
} }
} }
// y // gift move between two points
giftPos.value[1] += giftSpeed * 0.3; if (giftPos.value[0] !== null && giftPos.value[1] !== null) {
if (giftPos.value[1] > 60) { const giftSpeed = 0.8 * (deltaTime / 16.67); // (0.3)
giftPos.value[1] = 60; //
//
giftMoveProgress.value += giftSpeed * giftMoveDirection.value * 0.01;
//
if (giftMoveProgress.value >= 1) {
giftMoveProgress.value = 1;
giftMoveDirection.value = -1;
} else if (giftMoveProgress.value <= 0) {
giftMoveProgress.value = 0;
giftMoveDirection.value = 1;
}
// 使线
giftPos.value[0] = giftStartPos.value[0] + (giftEndPos.value[0] - giftStartPos.value[0]) * giftMoveProgress.value;
giftPos.value[1] = giftStartPos.value[1] + (giftEndPos.value[1] - giftStartPos.value[1]) * giftMoveProgress.value;
} }
} }
moveGift();
// //
const distance = calcGiftDis() const distance = calcGiftDis()
@ -199,9 +224,7 @@ function gameLoop(currentTime = 0) {
console.log('太阳收集到礼物!', { sunPos: sunPos.value, giftPos: giftPos.value, distance }); console.log('太阳收集到礼物!', { sunPos: sunPos.value, giftPos: giftPos.value, distance });
pauseFlag = true; // pauseFlag = true; //
// TODO: giftShow.value = true; //
//
giftShow.value = true; //
document.querySelector('.gift-item.show')?.animate([ document.querySelector('.gift-item.show')?.animate([
{ transform: 'scale(1)' }, { transform: 'scale(1)' },
{ transform: 'scale(0)' }, { transform: 'scale(0)' },
@ -210,7 +233,7 @@ function gameLoop(currentTime = 0) {
easing: 'cubic-bezier(0.42,-0.98, 0.4, 1)', easing: 'cubic-bezier(0.42,-0.98, 0.4, 1)',
fill: 'forwards', fill: 'forwards',
}).finished.then(() => { }).finished.then(() => {
giftPos.value = [null, null]; // giftPos.value = [null, null]; //
}); });
} }
@ -230,6 +253,18 @@ function gToggle(pos: 'left' | 'center' | 'right') {
fill: 'forwards', fill: 'forwards',
}); });
if (isPlayingDropAni.value) {
//
return;
}
if (sunPos.value[0] < -36
|| sunPos.value[0] > 36
|| sunPos.value[1] > 70) {
//
return;
}
const windAniMap: Record<'left' | 'center' | 'right', [number, [number, number], [number, number]]> = { const windAniMap: Record<'left' | 'center' | 'right', [number, [number, number], [number, number]]> = {
left: [ left: [
-60, -60,
@ -270,6 +305,12 @@ function gToggle(pos: 'left' | 'center' | 'right') {
})[pos].forEach((v, i) => { })[pos].forEach((v, i) => {
sunPos.value[i] += v * SPEED * 2; sunPos.value[i] += v * SPEED * 2;
}); });
if (sunPos.value[0] < -36) {
sunPos.value[0] = -36;
} else if (sunPos.value[0] > 36) {
sunPos.value[0] = 36;
}
} }
const giftShow = ref(false); const giftShow = ref(false);
@ -280,6 +321,10 @@ const canAction = ref(false);
const sunPos = ref([0, 0]) // x, y const sunPos = ref([0, 0]) // x, y
const giftPos: Ref<[number | null, number | null]> = ref([null, null]); // x, y const giftPos: Ref<[number | null, number | null]> = ref([null, null]); // x, y
const giftStartPos: Ref<[number, number]> = ref([0, 0]); //
const giftEndPos: Ref<[number, number]> = ref([0, 0]); //
const giftMoveProgress = ref(0); // 0-1
const giftMoveDirection = ref(1); // 1 -1
const giftList = assets.icons; const giftList = assets.icons;
@ -469,6 +514,30 @@ const showEndAni = ref(false);
async function gameEnd() { async function gameEnd() {
fetch('/api/score', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
region: props.userdata.region,
store: props.userdata.store,
name: props.userdata.username,
time: timeSpent.value / 1000,
})
}).then(_ => {
fetch('/api/score', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(data => {
leaderBoard.value = data;
});
})
document.querySelector('.bed')?.animate([ document.querySelector('.bed')?.animate([
{ transform: 'translateY(150%)' }, { transform: 'translateY(150%)' },
{ transform: 'translateY(0)' }, { transform: 'translateY(0)' },
@ -485,14 +554,15 @@ async function gameEnd() {
img.style.animationDirection = 'reverse'; img.style.animationDirection = 'reverse';
}); });
await document.querySelector('.sun-ani-wrapper')?.animate([ document.querySelector('.sun-ani-wrapper')?.animate([
{ transform: 'translateY(8%)', left: '19%', bottom: '23%' }, { transform: 'translateY(8%) translateX(-50%)', left: '50%', bottom: '23%', width: '60vw' },
], { ], {
duration: 700, duration: 1800,
easing: 'ease-in-out', easing: 'ease-in-out',
fill: 'forwards', fill: 'forwards',
}).finished }).finished
isGameEnd.value = true; isGameEnd.value = true;
// //
@ -501,13 +571,7 @@ async function gameEnd() {
await wait(0) await wait(0)
floatIcons.value = finalPositions; floatIcons.value = finalPositions;
const element = document.querySelector('.sun-ani-wrapper'); await wait(1800)
if (!element) return;
await element.animate(
{ transform: 'translateY(8%)' },
{ duration: 2500, easing: 'ease-in-out', fill: 'forwards' }
).finished;
showEndAni.value = true; showEndAni.value = true;
sunEndEle.value?.jumpTo('all'); sunEndEle.value?.jumpTo('all');
@ -530,12 +594,6 @@ async function gameEnd() {
} }
/* setTimeout(() => {
canAction.value = true
gameEnd()
}, 100); */
const leaderBoard = ref([ const leaderBoard = ref([
{ name: '始祖鸟', time: 50, region: '奥莱北区', store: "上海始祖鸟阿尔法中心" }, { name: '始祖鸟', time: 50, region: '奥莱北区', store: "上海始祖鸟阿尔法中心" },
@ -559,15 +617,13 @@ const showScore = ref(false)
const showLastPage = ref(false); const showLastPage = ref(false);
const sunAniEndRules = ref([
{ name: 'all', frame: 165, loop: 1, pauseAfter: false, duration: 33 },
{ name: '左右跳', frame: 66, loop: 0, pauseAfter: true, duration: 33 },
{ name: '落下', frame: 32, loop: 1, pauseAfter: false, duration: 33 },
{ name: 'loop', frame: 33, loop: 0, pauseAfter: true, duration: 33 },
])
async function finishCollect() { async function finishCollect() {
document.querySelector('.bar-container')?.animate([
{ transform: 'translateY(300%)' }], {
duration: 500,
easing: 'ease-in-out',
fill: 'forwards',
})
document.querySelector('.finish-collect')?.animate([ document.querySelector('.finish-collect')?.animate([
{ transform: 'scale(1)' }, { transform: 'scale(1)' },
{ transform: 'scale(0)' }, { transform: 'scale(0)' },
@ -591,48 +647,98 @@ async function finishCollect() {
easing: 'ease-in-out', easing: 'ease-in-out',
fill: 'forwards', fill: 'forwards',
}).finished }).finished
await sunEndEle.value?.jumpToSoftly('落下');
document.querySelector('.bar-container')?.animate([
{ transform: 'translateY(300%)' }], {
duration: 500,
easing: 'ease-in-out',
fill: 'forwards',
})
showLastPage.value = true; showLastPage.value = true;
} }
//
function resetGame() {
return
//
gPos.value = 'center';
transitionOn.value = true;
lastTime = 0;
giftShow.value = false;
canAction.value = false;
sunPos.value = [0, 0];
giftPos.value = [null, null];
giftStartPos.value = [0, 0];
giftEndPos.value = [0, 0];
giftMoveProgress.value = 0;
giftMoveDirection.value = 1;
giftProgress.value = 0;
isGameEnd.value = false;
timeSpent.value = 0;
showEndAni.value = false;
showScore.value = false;
showLastPage.value = false;
//
floatIcons.value = generateIconPositions();
//
sunEle.value?.jumpTo('起飞前');
sunEndEle.value?.jumpTo('loop');
//
const elementsToReset = [
'.bed', '.sun-ani-wrapper', '.p-machine', '.action img',
'.gift-item', '.wind', '.bar-container', '.finish-collect',
'.wecare-title', '.lines'
];
elementsToReset.forEach(selector => {
const element = document.querySelector(selector);
if (element instanceof HTMLElement) {
element.getAnimations().forEach(animation => animation.cancel());
element.style.animation = '';
element.style.transform = '';
element.style.opacity = '';
}
});
}
</script> </script>
<template> <template>
<div class="page game"> <div class="page game">
<div class="public"> <div class="public">
<div class="bg"> </div>
<img class="cloud" src="../assets/game/云.png" v-for="c in [
{ w: 60, t: 10, l: 75, all: true },
{ w: 75, t: 55, l: -45 },
{ w: 40, t: 105, l: 35 },
]" :style="{ width: `${c.w}vw`, top: `${c.t}vw`, left: `${c.l}vw` }" :class="{ all: c.all }"></img>
<img src="../assets/game/床.png" alt="" class="abs bed" style="width: 66vw;bottom: 12%;left: 17%;"> <img src="../assets/game/床.png" alt="" class="abs bed"
style="width: 66vw;bottom: 12%;left: 17%;">
<AniEle :url="assets.ani.下拉蓄力提示" ref="aniEle" :height="334" :width="337" :rules="[ <AniEle :url="assets.ani.下拉蓄力提示" ref="aniEle" :height="334"
{ name: '蓄力', frame: 241, loop: 2 }, :width="337" :rules="[
]" class="abs arrow" style="bottom: 7%;width: 30vw;left: 35%;" @pointerdown.once="shoot" /> { name: '蓄力', frame: 241, loop: 2 },
]" class="abs arrow" style="bottom: 7%;width: 30vw;left: 35%;"
@pointerdown.once="shoot" />
<img class="cloud" src="../assets/game/云.png" v-for="c in [
{ w: 75, t: 165, l: -35, all: true },
{ w: 100, t: 144, l: 60, all: true },
]" :style="{ width: `${c.w}vw`, top: `${c.t}vw`, left: `${c.l}vw` }" :class="{ all: c.all }"></img>
<div class="sun-ani-wrapper abs" v-if="!showEndAni" :style="{ <div class="sun-ani-wrapper abs" v-if="!showEndAni" :style="{
width: '60vw', width: '60vw',
zIndex: 99,
pointerEvents: 'none',
bottom: `calc(21% + ${sunPos[1]}vw)`, bottom: `calc(21% + ${sunPos[1]}vw)`,
left: `calc(19% + ${sunPos[0]}vw)`, left: `calc(49% + ${sunPos[0]}vw)`,
transform: `translateX(-50%)`,
transition: transitionOn ? 'all 0.1s ease' : undefined transition: transitionOn ? 'all 0.1s ease' : undefined
}"> }">
<!-- AniEle 组件现在只关心自己的内容不再有复杂的外部样式 --> <!-- AniEle 组件现在只关心自己的内容不再有复杂的外部样式 -->
<AniEle :url="assets.ani.小太阳总" ref="sun-ani" class="sun-ani" :height="1800" :width="1600" :rules="[ <AniEle :url="assets.ani.小太阳总" ref="sun-ani" class="sun-ani"
{ name: '起飞前', frame: 90, loop: 0, pauseAfter: true, duration: 33 }, :height="1800" :width="1600" :rules="[
{ name: '蓄力飞', frame: 181, loop: 1, pauseAfter: false, duration: 33 }, { name: '起飞前', frame: 90, loop: 0, pauseAfter: true, duration: 33 },
{ name: '天上飞', frame: 90, loop: 0, pauseAfter: true, duration: 33 }, { name: '蓄力飞', frame: 181, loop: 1, pauseAfter: false, duration: 33 },
{ name: '落下', frame: 393, loop: 0, pauseAfter: false, duration: 33 }, { name: '天上飞', frame: 90, loop: 0, pauseAfter: true, duration: 33 },
]" style="width: 100%; height: auto;" /> { name: '落下', frame: 393, loop: 0, pauseAfter: false, duration: 33 },
]" style="width: 100%; height: auto;" />
</div> </div>
</div> </div>
@ -641,48 +747,58 @@ async function finishCollect() {
style="bottom: -40%; width: 66vw; left: 17%; transform-origin: 50% 63%; transition: all 0.2s;" style="bottom: -40%; width: 66vw; left: 17%; transform-origin: 50% 63%; transition: all 0.2s;"
:style="{ transform: `rotate(${gDeg}deg)` }" /> :style="{ transform: `rotate(${gDeg}deg)` }" />
<div class="action"> <div class="action">
<img draggable="false" :style="{ animationDelay: '0.1s' }" src="../assets/game/左.png" alt="" <img draggable="false" :style="{ animationDelay: '0.1s' }"
@click="gToggle('left')"> src="../assets/game/左.png" alt="" @click="gToggle('left')">
<img draggable="false" :style="{ animationDelay: '0.2s' }" src="../assets/game/中.png" alt="" <img draggable="false" :style="{ animationDelay: '0.2s' }"
src="../assets/game/中.png" alt=""
@click="gToggle('center')"> @click="gToggle('center')">
<img draggable="false" :style="{ animationDelay: '0.0s' }" src="../assets/game/右.png" alt="" <img draggable="false" :style="{ animationDelay: '0.0s' }"
@click="gToggle('right')"> src="../assets/game/右.png" alt="" @click="gToggle('right')">
</div> </div>
<img class="abs gift-item" v-for="(gift, index) in giftList" :src="gift[0]" style="width: 30vw;" <img class="abs gift-item" v-for="(gift, index) in giftList"
v-if="!isGameEnd" :style="{ :src="gift[0]" style="width: 20vw;" v-if="!isGameEnd" :style="{
display: giftProgress == index ? 'block' : 'none', display: giftProgress == index ? 'block' : 'none',
bottom: `calc(48% + ${giftPos[1] ?? 0}vw)`, left: `calc(34.5% + ${giftPos[0] ?? 0}vw)`, transition: transitionOn ? `all 0.1s ease` : undefined bottom: `calc(45% + ${giftPos[1] ?? 0}vw)`, left: `calc(39.5% + ${giftPos[0] ?? 0}vw)`, transition: transitionOn ? `all 0.1s ease` : undefined
}" alt="" :class="{ show: giftProgress == index, hide: giftProgress != index }"> }" alt=""
:class="{ show: giftProgress == index, hide: giftProgress != index }">
<img src="../assets//game/操作提示.png" class="abs prompt" alt="" style="width: 31%; bottom: 18%; left: 34%; "> <img src="../assets//game/操作提示.png" class="abs prompt" alt=""
style="width: 31%; bottom: 18%; left: 34%; ">
<img src="../assets/game/风.png" alt="" class="abs wind"> <img src="../assets/game/风.png" alt="" class="abs wind">
<img src="../assets/game/wecare.png" alt="" class="abs wecare" style="width: 24vw; <img src="../assets/game/wecare.png" alt="" class="abs wecare"
style="width: 24vw;
left: 50%; top: 6%; transform: translateX(-50%);"> left: 50%; top: 6%; transform: translateX(-50%);">
<div class="dot" v-if="!isGameEnd"> <div class="dot" v-if="!isGameEnd">
<div v-for="_ in Array(giftProgress)" class="dot-item finished"></div> <div v-for="(_, index) in giftList" :key="index"
<div v-for="_ in Array(giftList.length - giftProgress)" class="dot-item"></div> class="dot-item" :class="{ finished: index < giftProgress }"
@click="giftProgress = index">
</div>
</div> </div>
</div> </div>
<div class="game-end abs" v-if="isGameEnd" style="z-index: 2; inset: 0; height: 100%; width: 100%;"> <div class="game-end abs" v-if="isGameEnd"
<div class="icon-cloud" style="position: absolute; left: 50%; top: 20%;"> style="z-index: 2; inset: 0; height: 100%; width: 100%;">
<div class="icon-cloud"
style="position: absolute; left: 50%; top: 20%;">
<img v-for="icon in floatIcons" :src="icon.src" alt="" :style="{ <img v-for="icon in floatIcons" :src="icon.src" alt="" :style="{
top: `${icon.top}vw`, top: `${icon.top}vw`,
left: `${icon.left}vw`, left: `${icon.left}vw`,
animationDelay: `${icon.delay}ms`, animationDelay: `${icon.delay}ms`,
}"> }">
</div> </div>
<img src="../assets/game/wecare.png" alt="" class="abs wecare" style="width: 24vw; <img src="../assets/game/wecare.png" alt="" class="abs wecare"
style="width: 24vw;
left: 50%; top: 6%; transform: translateX(-50%);"> left: 50%; top: 6%; transform: translateX(-50%);">
<img src="../assets/game/wecare标题.png" alt="" class="abs wecare-title" <img src="../assets/game/wecare标题.png" alt=""
style="width: 72%;left: 14%;top: 12%;"> class="abs wecare-title" style="width: 72%;left: 14%;top: 12%;">
<div class="scoreboard" v-if="showScore"> <div class="scoreboard" v-if="showScore">
<div class="bar-container"> <div class="bar-container">
<div class="bar" v-for="(item, index) in [leaderBoard[1], leaderBoard[0], leaderBoard[2]]" <div class="bar"
v-for="(item, index) in [leaderBoard[1], leaderBoard[0], leaderBoard[2]]"
:key="index" :class="{ :key="index" :class="{
'first': index === 1, 'second': index === 0, 'third': index === 2 'first': index === 1, 'second': index === 0, 'third': index === 2
}"> }">
@ -693,7 +809,9 @@ async function finishCollect() {
</div> </div>
</div> </div>
<div class="lines"> <div class="lines">
<div class="line" v-for="(item, index) in leaderBoard.slice(3, 7)" :key="index" <div class="line"
v-for="(item, index) in leaderBoard.slice(3, 7)"
:key="index"
:style="{ animationDelay: index * 100 + 400 + 'ms' }"> :style="{ animationDelay: index * 100 + 400 + 'ms' }">
<div class="rank">{{ index + 4 }}</div> <div class="rank">{{ index + 4 }}</div>
<div class="name">{{ item.name }}</div> <div class="name">{{ item.name }}</div>
@ -703,46 +821,53 @@ async function finishCollect() {
</div> </div>
</div> </div>
<div class="sun-ani-end-wrapper abs" style="pointer-events: none; inset: 0; width: 100%;z-index: 2;" <div class="sun-ani-end-wrapper abs"
style="pointer-events: none; inset: 0; width: 100%;z-index: 2;"
:style="{ display: showEndAni ? 'block' : 'none' }"> :style="{ display: showEndAni ? 'block' : 'none' }">
<!-- AniEle 组件现在只关心自己的内容不再有复杂的外部样式 --> <!-- AniEle 组件现在只关心自己的内容不再有复杂的外部样式 -->
<AniEle :url="assets.ani.后端效果" ref="sun-ani-end" class="sun-ani-end" :height="2462" :width="1179" <AniEle :url="assets.ani.后端效果" ref="sun-ani-end"
:rules="[ class="sun-ani-end" :height="2462" :width="1179"
{ name: 'all', frame: 263, loop: 1, pauseAfter: false, duration: 33 }, :rules="sunAniEndRules"
{ name: 'loop', frame: 33, loop: 0, pauseAfter: true, duration: 33 }, style="width: 100%; height: auto;" />
]" style="width: 100%; height: auto;" />
</div> </div>
<img src="../assets/game/完成收集.png" alt="" v-if="showScore" class="finish-collect abs" @click.once="finishCollect" <img src="../assets/game/完成收集.png" alt="" v-if="showScore"
class="finish-collect abs" @click.once="finishCollect"
style="bottom: 8%;left: 33%;width: 34vw;animation: scale-in 0.3s ease-out;"> style="bottom: 8%;left: 33%;width: 34vw;animation: scale-in 0.3s ease-out;">
</div> </div>
<div class="last-page" v-if="showLastPage" <div class="last-page" v-if="showLastPage"
style="z-index: 0;position: absolute; inset: 0;height: 100%; width: 100%;"> style="z-index: 0;position: absolute; inset: 0;height: 100%; width: 100%; pointer-events: none;">
<img src="../assets/game/end_right_bg.png" alt="" style="position: absolute;top: 29.3%;left: 37.2%;width: 57.4%; height: 38.1%; <img src="../assets/game/end_right_bg.png" alt="" style="position: absolute;top: 29.3%;left: 37.2%;width: 57.4%; height: 38.1%;
animation: fade-in 3s ease-in-out forwards; opacity: 0;"> animation: fade-in 3s ease-in-out forwards; opacity: 0;">
">
<AniEle :url="assets.ani.结尾" ref="gui-ani-end" class="gui-ani-end" :height="2462" :width="1179" :rules="[ <AniEle :url="assets.ani.结尾" ref="gui-ani-end" class="gui-ani-end"
{ name: 'all', frame: 105, loop: 1, pauseAfter: false, duration: 16 }, :height="2462" :width="1179" :rules="[
]" style="width: 100%; height: auto;position: absolute;inset: 0;pointer-events: none;" /> { name: 'all', frame: 105, loop: 1, pauseAfter: false, duration: 16 },
]"
style="width: 100%; height: auto;position: absolute;inset: 0;pointer-events: none;" />
<AniEle :url="assets.ani.主标出现" :width="1015" :height="336" <AniEle :url="assets.ani.主标出现" :width="1015" :height="336"
:rules="[{ name: 'main', frame: 41, loop: 1, pauseAfter: true, duration: 33, reverse: false }]" :rules="[{ name: 'main', frame: 41, loop: 1, pauseAfter: true, duration: 33, reverse: false }]"
style="position: absolute;top: 13%;width: 84%;left: 8%; pointer-events:none;" ref="main-logo" /> style="position: absolute;top: 13%;width: 84%;left: 8%; pointer-events:none;"
<img src="../assets/game/床.png" alt="" class="abs last-bed" style="width: 66vw;bottom: -19%;left: 17%;"> ref="main-logo" />
<img src="../assets/game/床.png" alt="" class="abs last-bed"
style="width: 66vw;bottom: -19%;left: 17%;">
<img src="../assets/game/分享海报.png" alt="" class="abs" <img src="../assets/game/分享海报.png" alt="" class="abs"
style="width: 34vw;bottom: -19%;left: 15%;animation: last-btn-in 0.3s ease-out forwards;"> style="width: 34vw;bottom: -19%;left: 15%;animation: last-btn-in 0.3s ease-out forwards;">
<img src="../assets/game/再玩一次.png" alt="" class="abs" <img src="../assets/game/再玩一次.png" alt="" class="abs"
style="width: 34vw;bottom: -19%;left: 51%;animation: last-btn-in 0.3s ease-out forwards; animation-delay: 200ms;"> @click="resetGame"
style="width: 34vw;bottom: -19%;left: 51%;animation: last-btn-in 0.3s ease-out forwards; animation-delay: 200ms; cursor: pointer;">
<div class="time-spent abs" style="left: 10%; top:44%;width: 24%; display: flex; <div class="time-spent abs" style="left: 10%; top:44%;width: 24%; display: flex;
align-items: center; flex-direction: column;"> align-items: center; flex-direction: column;">
<div class="line-1" <div class="line-1"
style="display: flex; align-items: center; gap: 1vw; animation: line-in 0.5s ease-out 0.6s forwards;transform: translateX(-210%);;"> style="display: flex; align-items: center; gap: 1vw; animation: line-in 0.5s ease-out 0.6s forwards;transform: translateX(-210%);">
<div class="username" style="font-size: 2.9vw;">{{ userdata.username }}</div> <div class="username" style="font-size: 2.9vw;">{{
userdata.username }}</div>
<div class="cost-info" <div class="cost-info"
style="font-size: 2.9vw; color: white; display: flex;align-items: center;justify-content: center;background-color: black;padding: 0 1.2vw; height: 4.5vw; border-radius: 4vw;"> style="font-size: 2.9vw; color: white; display: flex;align-items: center;justify-content: center;background-color: black;padding: 0 1.2vw; height: 4.5vw; border-radius: 4vw;">
用时</div> 用时</div>
@ -750,13 +875,17 @@ async function finishCollect() {
<div class="time" <div class="time"
style="font-size: 9vw;animation: line-in 0.5s ease-out 0.8s forwards;transform: translateX(-210%);"> style="font-size: 9vw;animation: line-in 0.5s ease-out 0.8s forwards;transform: translateX(-210%);">
{{ fmtTime(timeSpent/1000) }}</div> {{ fmtTime(timeSpent / 1000) }}</div>
</div> </div>
<div class="region-area abs" style="left: 10%; top:30%;width: 24%;height: 10%;gap:2vw; display: flex;animation: line-in 0.5s ease-out 0.4s forwards;transform: translateX(-210%); <div class="region-area abs"
style="left: 10%; top:30%;width: 24%;height: 10%;gap:2vw; display: flex;animation: line-in 0.5s ease-out 0.4s forwards;transform: translateX(-210%);
align-items: center; flex-direction: column;justify-content: center;"> align-items: center; flex-direction: column;justify-content: center;">
<div class="region" style="width: 20vw; background-color: white;font-size: 3vw;text-align: center;height: 4.5vw;line-height: 4.5vw; border-radius: 4.5vw;">{{ userdata.region }}</div> <div class="region"
<div class="store" style="font-size: 2.8vw; color: gray;">{{ userdata.store }}</div> style="width: 20vw; background-color: white;font-size: 3vw;text-align: center;height: 4.5vw;line-height: 4.5vw; border-radius: 4.5vw;">
{{ userdata.region }}</div>
<div class="store" style="font-size: 2.8vw; color: gray;">{{
userdata.store }}</div>
</div> </div>
</div> </div>
@ -764,9 +893,10 @@ async function finishCollect() {
<div v-if="giftShow" class="gift-popup"> <div v-if="giftShow" class="gift-popup">
<img :src="giftList[giftProgress][1]" alt="" class="abs" style="width: 82%; left: 9%; top: 11%;"> <img :src="giftList[giftProgress][1]" alt="" class="abs"
<img src="../assets/game/收下福利.png" alt="" class="abs" style="width: 14%; left: 43%; top: 54%;" style="width: 82%; left: 9%; top: 11%;">
@click="gameLoop()"> <img src="../assets/game/收下福利.png" alt="" class="abs"
style="width: 14%; left: 43%; top: 54%;" @click="gameLoop()">
</div> </div>
@ -1111,17 +1241,4 @@ async function finishCollect() {
animation: scale-in 0.4s ease-in-out forwards; animation: scale-in 0.4s ease-in-out forwards;
} }
} }
.bg {
position: absolute;
inset: 0;
height: 100%;
width: 100%;
z-index: -1;
background-image: url('../assets/game/蓝天.png');
}
.cloud {
position: absolute;
}
</style> </style>

99
src/pages/Loader.vue Normal file
View File

@ -0,0 +1,99 @@
<script setup lang="ts">
import { ref } from 'vue';
import assets from '../assets';
const progress = ref(0);
const total = Object.keys(assets.ani).length;
let completedFiles = 0;
// 使 XMLHttpRequest
const downloadFile = (url: string, key: string): Promise<void> => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
//
xhr.onprogress = (event) => {
if (event.lengthComputable) {
const fileProgress = (event.loaded / event.total) * 100;
// +
const totalProgress = (completedFiles + fileProgress / 100) / total * 100;
progress.value = Math.min(100, totalProgress);
}
};
xhr.onload = () => {
if (xhr.status === 200) {
const blob = xhr.response;
const objectURL = URL.createObjectURL(blob);
assets.ani[key as keyof typeof assets.ani] = objectURL;
completedFiles++;
progress.value = Math.min(100, completedFiles / total * 100);
resolve();
} else {
reject(new Error(`资源加载失败: ${url}, 状态码: ${xhr.status}`));
}
};
xhr.onerror = () => {
reject(new Error(`网络错误,无法加载: ${url}`));
};
xhr.send();
});
};
const emit = defineEmits(['loaded']);
//
(async () => {
try {
for (const key in assets.ani) {
if (Object.prototype.hasOwnProperty.call(assets.ani, key)) {
const url = assets.ani[key as keyof typeof assets.ani];
await downloadFile(url, key);
}
}
console.log('所有资源加载完成:', assets);
//
await document.querySelector('.loader-container')?.animate([
{ opacity: 1 },
{ opacity: 0 }
], {
duration: 200,
easing: 'ease-in',
fill: 'forwards',
}).finished;
emit('loaded');
} catch (error) {
console.error('资源加载失败:', error);
if (confirm('资源加载失败,是否重试?' + error)) {
window.location.reload();
}
}
})();
</script>
<template>
<div class="loader-container">
<img src="../assets/loader/wecare.png" alt=""
style="position: absolute; left: 50%; top:40%; transform: translateX(-50%); width: 40vw; ">
<div class="progress-container"
style="position: absolute; left: 50%; top: 48%; transform: translateX(-50%); width: 40vw; background-color: white; height: 2vw; border-radius: 2vw; overflow: hidden;">
<div class="progress"
style="height: 100%; background-color: #FF7B1D;transition: all 0.1s;"
:style="{ width: progress + '%' }"></div>
</div>
<div class="info"
style="position: absolute; left: 50%; top: 52%; transform: translateX(-50%); font-size: 3vw;">
加载中...</div>
</div>
</template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import assets from '../assets'; import assets from '../assets';
import { regions, type RegionData, type Store } from '../data'; import { regions, type RegionData, type Store } from '../data';
@ -19,14 +19,16 @@ const showStoreSelector = ref(false);
const selectedRegion = ref<string>(''); const selectedRegion = ref<string>('');
const selectedStores = ref<Store[]>([]); const selectedStores = ref<Store[]>([]);
const selectedStoreIndex = ref(0); const selectedStoreIndex = ref(0);
const selectedBubbleEl = ref<HTMLElement | null>(null);
const selectionInProgress = ref(false);
// //
const regionKeyMap: { [key: string]: keyof RegionData | 'combined' } = { const regionKeyMap: { [key: string]: keyof RegionData | 'combined' } = {
'大北区': '大北区', '大北区': '大北区',
'大东区': '大东区', '大东区': '大东区',
'大南区&免税': 'combined', // '大南区&免税': 'combined',
'大西区': '大西区', '大西区': '大西区',
'奥莱': 'combined', // '奥莱': 'combined',
'博物馆': '博物馆', '博物馆': '博物馆',
}; };
@ -49,17 +51,13 @@ function getRegionStores(regionKey: string): Store[] {
let observer: IntersectionObserver | null = null; let observer: IntersectionObserver | null = null;
function initializeStoreSelector() { function initializeStoreSelector() {
// DOM
setTimeout(() => { setTimeout(() => {
const storeItems = document.querySelectorAll('.store-item'); const storeItems = document.querySelectorAll('.store-item');
if (storeItems.length === 0) return; if (storeItems.length === 0) return;
// Intersection Observer
observer = new IntersectionObserver((entries) => { observer = new IntersectionObserver((entries) => {
entries.forEach(entry => { entries.forEach(entry => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
//
const index = Array.from(storeItems).indexOf(entry.target); const index = Array.from(storeItems).indexOf(entry.target);
if (index !== -1 && index !== selectedStoreIndex.value) { if (index !== -1 && index !== selectedStoreIndex.value) {
selectedStoreIndex.value = index; selectedStoreIndex.value = index;
@ -68,11 +66,10 @@ function initializeStoreSelector() {
}); });
}, { }, {
root: document.querySelector('.store-list'), root: document.querySelector('.store-list'),
rootMargin: '-45% 0px -45% 0px', // 10% rootMargin: '-45% 0px -45% 0px',
threshold: 0.5 threshold: 0.5
}); });
//
storeItems.forEach(item => { storeItems.forEach(item => {
observer?.observe(item); observer?.observe(item);
}); });
@ -81,7 +78,6 @@ function initializeStoreSelector() {
function selectStore(index: number) { function selectStore(index: number) {
selectedStoreIndex.value = index; selectedStoreIndex.value = index;
// scroll-snap
const storeItems = document.querySelectorAll('.store-item'); const storeItems = document.querySelectorAll('.store-item');
if (storeItems[index]) { if (storeItems[index]) {
storeItems[index].scrollIntoView({ storeItems[index].scrollIntoView({
@ -91,161 +87,79 @@ function selectStore(index: number) {
} }
} }
// observer
function cleanupObserver() { function cleanupObserver() {
if (observer) { if (observer) {
observer.disconnect(); observer.disconnect();
observer = null; observer = null;
} }
} }
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'startExploration', payload: { region: string; store: string, username: string }): void; (e: 'startExploration', payload: { region: string; store: string, username: string }): void;
}>(); }>();
function startExploration() {
// const noticeToInputName = ref(false);
async function startExploration() {
console.log('开始探索:', selectedStores.value[selectedStoreIndex.value]); console.log('开始探索:', selectedStores.value[selectedStoreIndex.value]);
if (!selectedRegion.value || selectedStores.value.length === 0) { if (!selectedRegion.value || selectedStores.value.length === 0) {
alert('请选择一个区域和店铺!'); alert('请选择一个区域和店铺!');
return; return;
} }
if ((document.querySelector('.name-input') as HTMLInputElement).value.trim() === '') { const nameInput = document.querySelector('.name-input') as HTMLInputElement;
alert('请输入你的名字!'); if (nameInput.value.trim() === '') {
noticeToInputName.value = true;
return; return;
} }
startExplorationRules.value[0].reverse = true; startExplorationRules.value[0].reverse = true;
// 2.
let bubble = selectedBubbleEl.value;
if (!bubble) return;
bubble.classList.remove('zoomed');
bubble.style.transform = 'translate3d(-50%, -50%, 0) scale(0)';
document.querySelector('.sun-wrap')?.classList.add('fade-out');
emit('startExploration', { emit('startExploration', {
region: selectedRegion.value, region: selectedRegion.value,
store: selectedStores.value[selectedStoreIndex.value].店铺, store: selectedStores.value[selectedStoreIndex.value].店铺,
username: (document.querySelector('.name-input') as HTMLInputElement).value username: nameInput.value
}); });
} }
//
let fadeInAnimations: Animation[] = [];
let swapInAnimations: Animation[] = [];
let bubbleDropAnimations: Animation[] = [];
//
let allAnimations: Animation[] = [];
//
function createFadeInAnimation(element: Element, delay: number = 0) {
const animation = element.animate([
{ opacity: 0 },
{ opacity: 1 }
], {
duration: 500,
easing: 'ease-in-out',
delay: delay * 1000,
fill: 'forwards'
});
fadeInAnimations.push(animation);
allAnimations.push(animation);
return animation;
}
// Swap in
function createSwapInAnimation(element: Element, delay: number = 0) {
const animation = element.animate([
{ clipPath: 'inset(0 100% 0 0)' },
{ clipPath: 'inset(0 0% 0 0)' }
], {
duration: 500,
easing: 'ease-in-out',
delay: delay * 1000,
fill: 'forwards'
});
swapInAnimations.push(animation);
allAnimations.push(animation);
return animation;
}
//
function createBubbleDropAnimation(element: Element, startTop: string, startLeft: string, endTop: string, endLeft: string, delay: number = 0, duration: number = 600) {
const animation = element.animate([
{
top: startTop,
left: startLeft,
transform: 'translate3d(-50%, -50%, 0) scale(0.1)'
},
{
top: endTop,
left: endLeft,
transform: 'translate3d(-50%, -50%, 0) scale(1)'
}
], {
duration: duration,
easing: 'ease-out',
delay: (delay + 1) * 1000,
fill: 'forwards'
});
bubbleDropAnimations.push(animation);
allAnimations.push(animation);
return animation;
}
// refs
const mainLogoRules = ref([ const mainLogoRules = ref([
{ name: 'main', frame: 41, loop: 1, pauseAfter: true, duration: 33, reverse: false } { name: 'main', frame: 41, loop: 1, pauseAfter: true, duration: 33, reverse: false }
]); ]);
const selectInfoRules = ref([{ name: 'main', frame: 26, loop: 1, duration: 33, reverse: false }]); const selectInfoRules = ref([{ name: 'main', frame: 26, loop: 1, duration: 33, reverse: false }]);
const startExplorationRules = ref([{ name: 'start', frame: 19, loop: 1, duration: 33, reverse: false }]); const startExplorationRules = ref([{ name: 'start', frame: 19, loop: 1, duration: 33, reverse: false }]);
//
function reverseAllAnimations() {
mainLogoRules.value[0].reverse = true;
selectInfoRules.value[0].reverse = true;
allAnimations.forEach(animation => {
//
if (animation.playState === 'finished') {
animation.reverse();
}
});
}
//
function fadeOutAllAnimations() {
const allElements = document.querySelectorAll('.fade-in, .swap-in, .scale-in-ctn, .scale-in-txt, .region-bubble');
allElements.forEach((element) => {
element.animate([
{ opacity: 1 },
{ opacity: 0 }
], {
duration: 1000,
easing: 'ease-in-out',
fill: 'forwards'
});
});
}
// 使
(window as any).reverseAllAnimations = reverseAllAnimations;
(window as any).fadeOutAllAnimations = fadeOutAllAnimations;
const showSelectInfo = ref(false); const showSelectInfo = ref(false);
const showSun = ref(false); const showSun = ref(false);
// Refs for DOM elements to control their styles
const logoEl = ref<HTMLElement | null>(null);
const mainLogoWrapperEl = ref<HTMLElement | null>(null);
const selectInfoContainerEl = ref<HTMLElement | null>(null);
onMounted(() => { onMounted(() => {
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
//
requestAnimationFrame(async () => { requestAnimationFrame(async () => {
// logo // --- ---
const logo = document.querySelector('.logo');
if (logo) {
createFadeInAnimation(logo, 2);
}
// swap-in // 1. Logo
const swapInElements = document.querySelectorAll('.swap-in'); setTimeout(() => {
swapInElements.forEach((element) => { if (logoEl.value) logoEl.value.style.opacity = '1';
const htmlElement = element as HTMLElement; }, 2000);
const delay = parseFloat(htmlElement.style.getPropertyValue('--base-delay')?.replace('s', '') || '0');
createSwapInAnimation(element, delay); // 2. (AniEle)
});
// 3.
const regionBubbles = document.querySelectorAll<HTMLElement>('.region-bubble');
//
const regionBubbles = document.querySelectorAll('.region-bubble');
regionBubbles.forEach((bubble, index) => { regionBubbles.forEach((bubble, index) => {
const region = regionsPos[index]; const region = regionsPos[index];
const startTop = `${region.t + 50 - 200 - (100 - region.t)}%`; const startTop = `${region.t + 50 - 200 - (100 - region.t)}%`;
@ -253,87 +167,136 @@ onMounted(() => {
const endTop = `${region.t + 50}%`; const endTop = `${region.t + 50}%`;
const endLeft = `${region.l + 50}%`; const endLeft = `${region.l + 50}%`;
// //
(bubble as HTMLElement).style.top = startTop; Object.assign(bubble.style, {
(bubble as HTMLElement).style.left = startLeft; top: startTop,
left: startLeft,
transform: 'translate3d(-50%, -50%, 0) scale(0.1)',
transitionDuration: `0ms`
});
createBubbleDropAnimation(bubble, startTop, startLeft, endTop, endLeft, 0, 600 + region.ad * 100); //
setTimeout(() => {
Object.assign(bubble.style, {
top: endTop,
left: endLeft,
transform: 'translate3d(-50%, -50%, 0) scale(1)',
// transitionDuration: `${600 + region.ad * 100}ms`
const pDown = () => {
bubble.animate([
{ transform: 'translate3d(-50%, -50%, 0) scale(1)', filter: 'brightness(1)' },
{ transform: 'translate3d(-50%,-50%, 0) scale(0.92)', filter: 'brightness(0.9)' },
], {
duration: 500,
easing: 'ease-in-out',
fill: 'forwards'
}); });
} }, 1000); //
// --- ---
const pDown = () => {
if (selectionInProgress.value) return;
bubble.style.filter = 'brightness(0.9)';
bubble.style.transform = 'translate3d(-50%,-50%, 0) scale(0.92)';
};
const pUp = () => { const pUp = () => {
// if (selectionInProgress.value) return;
selectionInProgress.value = true;
selectedBubbleEl.value = bubble;
selectedRegion.value = region.tt; selectedRegion.value = region.tt;
selectedStores.value = getRegionStores(region.key); selectedStores.value = getRegionStores(region.key);
selectedStoreIndex.value = 0; selectedStoreIndex.value = 0;
bubble.querySelector('span')?.animate([ //
{ opacity: 1 }, bubble.classList.add('zoomed');
{ opacity: 0 }, bubble.style.filter = 'brightness(1)';
], {
duration: 1000,
easing: 'ease-in-out',
fill: 'forwards'
});
const scaleAnimation = bubble.animate([ //
{ transform: 'translate3d(-50%, -50%, 0) scale(0.92)', filter: 'brightness(0.9)' }, const span = bubble.querySelector('span');
{ transform: 'translate3d(-50%, -50%, 0) scale(4)', filter: 'brightness(1)', top: '53%', left: '50%' }, if (span) span.style.opacity = '0';
], {
duration: 1000,
easing: 'ease-in-out',
fill: 'forwards'
});
// // UI
reverseAllAnimations(); if (logoEl.value) logoEl.value.style.opacity = '0';
mainLogoRules.value[0].reverse = true;
selectInfoRules.value[0].reverse = true;
// //
regionBubbles.forEach((otherBubble, otherIndex) => { regionBubbles.forEach(otherBubble => {
if (otherIndex == index) return if (otherBubble !== bubble) {
otherBubble.animate([ otherBubble.style.transform = 'translate3d(-50%, -50%, 0) scale(0)';
{ opacity: 1 }, otherBubble.style.opacity = '0';
{ opacity: 0 }, } else {
], { otherBubble.style.transitionTimingFunction = 'cubic-bezier(0.71, 0.14, 0.27, 1.55)';
duration: 1000, setTimeout(() => {
easing: 'ease-in-out', otherBubble.style.transitionTimingFunction = 'ease';
fill: 'forwards' }, 1000);
}); }
}); });
// //
scaleAnimation.addEventListener('finish', () => { setTimeout(() => {
showStoreSelector.value = true; showStoreSelector.value = true;
// Intersection Observer
initializeStoreSelector(); initializeStoreSelector();
}); }, 1000); //
};
bubble.removeEventListener('pointerdown', pDown);
}
bubble.addEventListener('pointerdown', pDown); bubble.addEventListener('pointerdown', pDown);
bubble.addEventListener('pointerup', pUp, { once: true }); bubble.addEventListener('pointerup', pUp);
}); });
await wait(1300); await wait(1300);
showSelectInfo.value = true; showSelectInfo.value = true;
// showSelectInfo
await wait(10);
if (selectInfoContainerEl.value) selectInfoContainerEl.value.style.opacity = '1';
await wait(300); await wait(300);
showSun.value = true; showSun.value = true;
}); });
}); });
// observer
function goBackToRegionSelection() {
if (!selectedBubbleEl.value) return;
selectionInProgress.value = true; //
mainLogoRules.value[0].reverse = false;
selectInfoRules.value[0].reverse = false;
// 1.
showStoreSelector.value = false;
cleanupObserver();
const bubble = selectedBubbleEl.value;
// 2.
bubble.classList.remove('zoomed');
// 3.
const span = bubble.querySelector('span');
if (span) span.style.opacity = '1';
// 4. UI
if (logoEl.value) logoEl.value.style.opacity = '1';
if (mainLogoWrapperEl.value) mainLogoWrapperEl.value.style.opacity = '1';
if (selectInfoContainerEl.value) selectInfoContainerEl.value.style.opacity = '1';
// 5.
const allBubbles = document.querySelectorAll<HTMLElement>('.region-bubble');
allBubbles.forEach(otherBubble => {
if (otherBubble !== bubble) {
otherBubble.style.opacity = '1';
otherBubble.style.transform = 'translate3d(-50%, -50%, 0) scale(1)';
}
});
// 6.
setTimeout(() => {
selectedRegion.value = '';
selectedStores.value = [];
selectedStoreIndex.value = 0;
selectedBubbleEl.value = null;
selectionInProgress.value = false; //
}, 1000); //
}
onUnmounted(() => { onUnmounted(() => {
cleanupObserver(); cleanupObserver();
}); });
@ -342,40 +305,41 @@ onUnmounted(() => {
<template> <template>
<section class="p1"> <section class="p1">
<img :src="assets.标准logo" alt="" class="logo" style="top: 6%;left: 8%;width: 12%;opacity: 0;"> <img :src="assets.标准logo" alt="" class="logo" ref="logoEl">
<img :src="assets.p1.蓝天" alt="" class="bg">
<AniEle :url="assets.ani.主标出现" :width="1015" :height="336" :rules="mainLogoRules" <div class="main-logo-wrapper" ref="mainLogoWrapperEl">
style="top: 10%;width: 84%;left: 8%;" /> <AniEle :url="assets.ani.主标出现" :width="1015" :height="336"
:rules="mainLogoRules" style="top: 10%;width: 84%;left: 8%;" />
</div>
<div class="sun-warp" v-if="showSun"> <div class="sun-warp" v-if="showSun">
<AniEle :url="assets.ani.P1太阳总" :width="1000" :height="1000" <AniEle :url="assets.ani.P1太阳总" :width="1000" :height="1000"
:rules="[{ name: '下落', frame: 69, loop: 1, duration: 33 }, { name: '循环', frame: 75, loop: 0, duration: 33, reverse: false }]" /> :rules="[{ name: '下落', frame: 69, loop: 1, duration: 33 }, { name: '循环', frame: 75, loop: 0, duration: 33, reverse: false }]" />
</div> </div>
<!-- <img :src="assets.p1.太阳" alt="" style="top: 30%;left: 5%;width: 26%;"> --> <div
<div style="width: 86vw;height: 86vw;top: 47%; left: 50%;transform: translate3d(-50%, -50%, 0); z-index: -1;"> style="width: 86vw;height: 86vw;top: 47%; left: 50%;transform: translate3d(-50%, -50%, 0); z-index: -1;">
<div v-for="i in regionsPos" :style="{ <div v-for="i in regionsPos" :key="i.key" :style="{
width: i.w + '%', width: i.w + '%',
height: i.h + '%', height: i.h + '%',
transform: 'translate3d(-50%, -50%, 0) scale(0)',
}" class="region-bubble"> }" class="region-bubble">
<img :src="i.src" alt="" style="height: 100%;width: 100%;"> <img :src="i.src" alt="" style="height: 100%;width: 100%;"
draggable="false">
<span <span
style="font-size: 4.5vw;left: 50%;transform: translate(-50%, -50%);font-weight: 400;width: fit-content;"
:style="{ color: i.tc || '#000', top: (i.trt ?? 0) + 50 + '%' }"> :style="{ color: i.tc || '#000', top: (i.trt ?? 0) + 50 + '%' }">
*{{ i.tt }} *{{ i.tt }}
</span> </span>
</div> </div>
</div> </div>
<div style="inset: 0;width: 100%;top: 52%;pointer-events: none;" v-if="showSelectInfo"> <div class="select-info-container" ref="selectInfoContainerEl"
<AniEle :url="assets.ani.点击选择阵营" :width="1179" :height="2462" ref="click-select-ani" v-if="showSelectInfo">
<AniEle :url="assets.ani.点击选择阵营" :width="1179" :height="2462"
:rules="selectInfoRules" /> :rules="selectInfoRules" />
</div> </div>
<!-- 店铺选择界面 --> <!-- 店铺选择界面 -->
<div v-if="showStoreSelector" class="store-selector"> <div v-if="showStoreSelector" class="store-selector">
<div class="store-selector-content"> <div class="store-selector-content">
@ -383,15 +347,16 @@ onUnmounted(() => {
<div class="store-list-container"> <div class="store-list-container">
<div class="store-list relative"> <div class="store-list relative">
<!-- 顶部填充确保第一个元素能滚动到中间 -->
<div class="scroll-padding relative"></div> <div class="scroll-padding relative"></div>
<div v-for="(store, index) in selectedStores" :key="store.店铺号" class="store-item relative" <div v-for="(store, index) in selectedStores"
:class="{ active: index === selectedStoreIndex }" @click="selectStore(index)"> :key="store.店铺号" class="store-item relative"
<div class="store-name relative">{{ store.店铺 }}</div> :class="{ active: index === selectedStoreIndex }"
@click="selectStore(index)">
<div class="store-name relative">{{ store.店铺 }}
</div>
</div> </div>
<!-- 底部填充确保最后一个元素能滚动到中间 -->
<div class="scroll-padding relative"></div> <div class="scroll-padding relative"></div>
</div> </div>
</div> </div>
@ -402,16 +367,80 @@ onUnmounted(() => {
<input type="text" class="name-input" placeholder=""> <input type="text" class="name-input" placeholder="">
</div> </div>
</div> </div>
<img class="input-your-name-notice"
src="../assets/p1/请填写你的名字.png" alt=""
v-if="noticeToInputName">
<AniEle :url="assets.ani.开始探索" :width="548" :height="143" :rules="startExplorationRules" class="start-button relative" <AniEle :url="assets.ani.开始探索" :width="548" :height="143"
:rules="startExplorationRules" class="start-button"
@click="startExploration"> @click="startExploration">
</AniEle> </AniEle>
</div> </div>
<img src="../assets/返回按钮.png" alt="返回"
@click="goBackToRegionSelection" class="back-button" />
</div> </div>
</section> </section>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
/* --- Base Styles and Transitions --- */
.logo {
top: 6%;
left: 8%;
width: 12%;
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.main-logo-wrapper {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.select-info-container {
position: absolute;
inset: 0;
width: 100%;
top: 52%;
pointer-events: none;
opacity: 0;
transition: opacity 1s ease-in-out;
}
.region-bubble {
position: absolute;
cursor: pointer;
// transition-duration is set dynamically in script
transition-property: top, left, transform, opacity;
transition-timing-function: ease;
will-change: top, left, transform, opacity;
&.zoomed {
top: 53% !important;
left: 50% !important;
transform: translate3d(-50%, -50%, 0) scale(4) !important;
transition-duration: 1000ms !important;
}
span {
font-size: 4.5vw;
left: 50%;
transform: translate(-50%, -50%);
font-weight: 400;
width: fit-content;
position: absolute;
opacity: 1;
transition: opacity 1s ease-in-out;
}
}
/* --- Other Styles --- */
@keyframes sun-drop { @keyframes sun-drop {
0% { 0% {
transform: translateY(-90vw); transform: translateY(-90vw);
@ -431,95 +460,45 @@ onUnmounted(() => {
animation: sun-drop 1s cubic-bezier(0.54, 0.46, 0.44, 1.28) forwards; animation: sun-drop 1s cubic-bezier(0.54, 0.46, 0.44, 1.28) forwards;
} }
@mixin center { @keyframes fade-in {
display: flex; from {
justify-content: center;
align-items: center;
}
.tag {
width: max-content;
padding: 0.5vw .8vw;
}
.green {
@include center;
background-color: #85AF4C;
color: transparent;
font-weight: bold;
width: max-content;
&>span,
&>svg {
opacity: 0; opacity: 0;
} }
&.fake { to {
background-color: transparent; opacity: 1;
color: #FFF;
&>span,
&>svg {
opacity: 1;
}
} }
} }
.white {
@include center;
background-color: #FFF;
font-size: 5vw;
font-weight: bold;
color: #000;
}
.black {
@include center;
background-color: #000;
font-size: 11vw;
font-weight: bold;
color: transparent;
&.fake {
background-color: transparent;
color: #FFF;
}
}
.region-bubble {
position: absolute;
transform: translate3d(-50%, -50%, 0);
}
span, span,
div { div,
position: absolute;
}
img { img {
position: absolute; position: absolute;
} }
.bg {
position: absolute;
z-index: -11;
inset: 0;
width: 100%;
object-fit: cover;
}
/* 店铺选择界面样式 */
.store-selector { .store-selector {
position: fixed; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 1000; z-index: 10;
animation: fade-in 0.5s ease-in-out;
}
.back-button {
position: absolute;
top: 16vw;
left: 8vw;
width: 8vw;
animation: fade-in 0.3s ease-in-out;
cursor: pointer;
pointer-events: all;
z-index: 111;
} }
.store-selector-content { .store-selector-content {
top: 15%; top: 10%;
left: 5%; left: 5%;
width: 90%; width: 90%;
text-align: center; text-align: center;
@ -535,19 +514,11 @@ img {
.store-list-container { .store-list-container {
margin-bottom: 12vw; margin-bottom: 12vw;
height: 40vh; height: 35vh;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
mask-image: linear-gradient(to bottom, mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
transparent 0%, -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
black 15%,
black 85%,
transparent 100%);
-webkit-mask-image: linear-gradient(to bottom,
transparent 0%,
black 15%,
black 85%,
transparent 100%);
} }
.store-list { .store-list {
@ -556,11 +527,17 @@ img {
scroll-behavior: smooth; scroll-behavior: smooth;
scroll-snap-type: y mandatory; scroll-snap-type: y mandatory;
padding: 0; padding: 0;
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none;
} }
.scroll-padding { .scroll-padding {
height: calc(20vh - 4vw); height: calc(20vh - 4vw);
/* 容器高度的一半减去项目高度的一半 */
flex-shrink: 0; flex-shrink: 0;
} }
@ -570,7 +547,6 @@ img {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 8vw; border-radius: 8vw;
scroll-snap-align: center; scroll-snap-align: center;
transition: all 0.3s ease; transition: all 0.3s ease;
@ -587,29 +563,32 @@ img {
font-weight: bold; font-weight: bold;
} }
.center-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
height: 10vw;
pointer-events: none;
z-index: 2;
background: rgba(255, 255, 255, 0.05);
}
.name-input-section { .name-input-section {
margin-bottom: 8vw; margin-bottom: 2vw;
.title {
font-size: 5vw;
color: #333;
margin-bottom: 4vw;
font-weight: normal;
}
} }
.name-input-section .title { @keyframes pop-up {
font-size: 5vw; from {
font-weight: 600; scale: 0;
color: #333; }
margin-bottom: 4vw;
font-weight: normal; to {
scale: 1;
}
}
.input-your-name-notice {
width: 40%;
left: 30%;
animation: pop-up 0.3s ease-out forwards;
transform-origin: top center;
} }
.input-container { .input-container {
@ -622,47 +601,22 @@ img {
width: 100%; width: 100%;
padding: 2vw 0; padding: 2vw 0;
font-size: 4.5vw; font-size: 4.5vw;
background: transparent; background: transparent url('../assets/p1/输入框.png') center / contain no-repeat;
border: none; border: none;
outline: none; outline: none;
color: #333; color: #333;
text-align: center; text-align: center;
background: url('../assets/p1/输入框.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.action-buttons {
display: flex;
justify-content: center;
align-items: center;
gap: 4vw;
} }
.start-button { .start-button {
width: 40vw; width: 40vw;
left: 30%; left: 30%;
cursor: pointer; cursor: pointer;
transition: all 0.1s ease; transition: transform 0.1s ease;
} bottom: -30%;
.start-button:hover { &:hover {
transform: scale(1.05); transform: scale(1.05);
} }
.start-button .arrow {
font-size: 5vw;
font-weight: normal;
}
/* 隐藏滚动条 */
.store-list::-webkit-scrollbar {
display: none;
}
.store-list {
-ms-overflow-style: none;
scrollbar-width: none;
} }
</style> </style>

32
todo.md
View File

@ -1,21 +1,31 @@
反馈 反馈
游戏开始: 游戏开始:
1. 开始页面左上角没看到小太阳,等了一会也没有; 1. 开始页面左上角没看到小太阳,等了一会也没有; @solved
2. 请增加“音乐开关按钮”,一直显示。音乐用我接下来这个文件; 2. 请增加“音乐开关按钮”,一直显示。音乐用我接下来这个文件; @solved
弹射演出: 弹射演出:
小太阳的弹射应该是先向上飞,到达高点回落一点,随后开伞,随后下落速度减缓(甚至停一下),现在没有下落速度减缓这个步骤,这个得麻烦你仔细看一下我们的动画参考,找一下这个演出的感觉。 小太阳的弹射应该是先向上飞,到达高点回落一点,随后开伞,随后下落速度减缓(甚至停一下),现在没有下落速度减缓这个步骤,这个得麻烦你仔细看一下我们的动画参考,找一下这个演出的感觉。 @solved
在游戏过程: 在游戏过程:
1. 太阳还要小1倍是长按播放弹出演出上飞的过程中变小的。 1. 太阳还要小1倍是长按播放弹出演出上飞的过程中变小的。 @solved
2. 小太阳整体自然下落速度加快1倍试一下效果 2. 小太阳整体自然下落速度加快1倍试一下效果 @solved
3. 屏幕上方、左右方似乎没有设置空气墙,我可以无限线上弹,也可以一直按左右飞出画面,然后就找不到太阳了(即使从上方重新下落也是在屏幕外部),我希望做到太阳和降落伞不出画面上、左、右框; 3. 屏幕上方、左右方似乎没有设置空气墙,我可以无限线上弹,也可以一直按左右飞出画面,然后就找不到太阳了(即使从上方重新下落也是在屏幕外部),我希望做到太阳和降落伞不出画面上、左、右框; @solved
4. icon设置随机两点间来回移动两点可以远一点icon移动速度加快2倍 4. icon设置随机两点间来回移动两点可以远一点icon移动速度加快2倍 @solved
5. 鼓风机的层级应该在太阳后面; 5. 鼓风机的层级应该在太阳后面; @solved
6. 太阳一旦低于鼓风机就不受控制快速下落快速从屏幕上方正中落回伞到达wecare下方再重新受控制快速完成这个演出 6. 太阳一旦低于鼓风机就不受控制快速下落快速从屏幕上方正中落回伞到达wecare下方再重新受控制快速完成这个演出 @solved
游戏结算: 游戏结算:
1. 动画卡轴不正确,小太阳收脚的那一下应该是落在蹦床上的时间点,收脚是落地缓冲,现在节奏点不对; 1. 动画卡轴不正确,小太阳收脚的那一下应该是落在蹦床上的时间点,收脚是落地缓冲,现在节奏点不对; @solved
2. 在没有点击收集完成的时候,小太阳应该是在排行榜上来回跳,循环,现在是一个循环后直接跳回下部; 2. 在没有点击收集完成的时候,小太阳应该是在排行榜上来回跳,循环,现在是一个循环后直接跳回下部;
3. 排行榜后台是不是还没完成?
164 左上
230
165: 0 - 164
66: loop 165 - 230
32: drop 231 - 262
33: 263 - end
1. 排行榜后台是不是还没完成?