init
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m31s

This commit is contained in:
2025-07-01 09:33:43 +07:00
parent 3a26f09a01
commit 331aba4489
156 changed files with 1206219 additions and 146 deletions
+25
View File
@@ -0,0 +1,25 @@
name: Gitea Auto Deploy
run-name: ${{ gitea.actor }} pushed code 🚀
on: [push]
jobs:
Deploy-Container:
runs-on: ubuntu-latest
steps:
- name: Check out latest code
uses: actions/checkout@v4
- name: Stop and remove old containers
run: |
docker compose down || true
- name: Remove unused Docker resources
run: |
docker system prune -a --volumes -f
- name: Build and restart containers
run: |
docker compose pull
docker compose up -d --build
+59
View File
@@ -0,0 +1,59 @@
FROM oven/bun:canary-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Install dependencies
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Disable telemetry during the build
ENV NEXT_TELEMETRY_DISABLED=1
RUN bun run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# Disable telemetry
ENV NEXT_TELEMETRY_DISABLED=1
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /app/data ./data
COPY --from=builder /app/messages ./messages
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:bun .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:bun /app/.next/standalone ./
COPY --from=builder --chown=nextjs:bun /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
# Set hostname to localhost
ENV HOSTNAME=0.0.0.0
CMD ["bun", "server.js"]
+436 -8
View File
@@ -4,19 +4,31 @@
"": {
"name": "firefly-tools",
"dependencies": {
"axios": "^1.10.0",
"framer-motion": "^12.12.1",
"next": "15.3.2",
"next-intl": "^4.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-select": "^5.10.1",
"react-toastify": "^11.0.5",
"socket.io-client": "^4.8.1",
"zod": "^3.25.67",
"zustand": "^5.0.5",
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/jest": "^29.5.14",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"daisyui": "^5.0.27",
"eslint": "^9",
"eslint-config-next": "15.3.2",
"eslint-config-next": "15.3.1",
"tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4",
"ts-to-zod": "^3.15.0",
"typescript": "^5",
},
},
@@ -26,12 +38,54 @@
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
"@babel/generator": ["@babel/generator@7.27.5", "", { "dependencies": { "@babel/parser": "^7.27.5", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw=="],
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/parser": ["@babel/parser@7.27.7", "", { "dependencies": { "@babel/types": "^7.27.7" }, "bin": "./bin/babel-parser.js" }, "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q=="],
"@babel/runtime": ["@babel/runtime@7.27.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="],
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
"@babel/traverse": ["@babel/traverse@7.27.7", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw=="],
"@babel/types": ["@babel/types@7.27.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw=="],
"@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" } }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
"@emotion/babel-plugin": ["@emotion/babel-plugin@11.13.5", "", { "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" } }, "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ=="],
"@emotion/cache": ["@emotion/cache@11.14.0", "", { "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA=="],
"@emotion/hash": ["@emotion/hash@0.9.2", "", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="],
"@emotion/memoize": ["@emotion/memoize@0.9.0", "", {}, "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="],
"@emotion/react": ["@emotion/react@11.14.0", "", { "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA=="],
"@emotion/serialize": ["@emotion/serialize@1.3.3", "", { "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA=="],
"@emotion/sheet": ["@emotion/sheet@1.4.0", "", {}, "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="],
"@emotion/unitless": ["@emotion/unitless@0.10.0", "", {}, "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="],
"@emotion/use-insertion-effect-with-fallbacks": ["@emotion/use-insertion-effect-with-fallbacks@1.2.0", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg=="],
"@emotion/utils": ["@emotion/utils@1.4.2", "", {}, "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="],
"@emotion/weak-memoize": ["@emotion/weak-memoize@0.4.0", "", {}, "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
@@ -50,6 +104,22 @@
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="],
"@floating-ui/core": ["@floating-ui/core@1.7.2", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw=="],
"@floating-ui/dom": ["@floating-ui/dom@1.7.2", "", { "dependencies": { "@floating-ui/core": "^1.7.2", "@floating-ui/utils": "^0.2.10" } }, "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA=="],
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
"@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.4", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.1", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA=="],
"@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="],
"@formatjs/icu-messageformat-parser": ["@formatjs/icu-messageformat-parser@2.11.2", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "@formatjs/icu-skeleton-parser": "1.8.14", "tslib": "^2.8.0" } }, "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA=="],
"@formatjs/icu-skeleton-parser": ["@formatjs/icu-skeleton-parser@1.8.14", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "tslib": "^2.8.0" } }, "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ=="],
"@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.5.10", "", { "dependencies": { "tslib": "2" } }, "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
@@ -102,6 +172,12 @@
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="],
"@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
@@ -116,7 +192,7 @@
"@next/env": ["@next/env@15.3.2", "", {}, "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.3.2", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.3.1", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-oEs4dsfM6iyER3jTzMm4kDSbrQJq8wZw5fmT6fg2V3SMo+kgG+cShzLfEV20senZzv8VF+puNLheiGPlBGsv2A=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g=="],
@@ -142,10 +218,18 @@
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
"@oclif/core": ["@oclif/core@4.4.0", "", { "dependencies": { "ansi-escapes": "^4.3.2", "ansis": "^3.17.0", "clean-stack": "^3.0.1", "cli-spinners": "^2.9.2", "debug": "^4.4.0", "ejs": "^3.1.10", "get-package-type": "^0.1.0", "indent-string": "^4.0.0", "is-wsl": "^2.2.0", "lilconfig": "^3.1.3", "minimatch": "^9.0.5", "semver": "^7.6.3", "string-width": "^4.2.3", "supports-color": "^8", "tinyglobby": "^0.2.14", "widest-line": "^3.1.0", "wordwrap": "^1.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-wH5g3SLmbRutnr7UzQBSozRFEAZ7U9YGB/wFuBRr0ZghTgv5DE+KQaf6ZtU7iFb9pvkvoVRnT5XheNAtbjRDaQ=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
"@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.11.0", "", {}, "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ=="],
"@schummar/icu-type-parser": ["@schummar/icu-type-parser@1.21.5", "", {}, "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="],
"@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
@@ -184,16 +268,36 @@
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
"@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
"@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
"@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
"@types/node": ["@types/node@20.17.50", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A=="],
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
"@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="],
"@types/react": ["@types/react@19.1.5", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g=="],
"@types/react-dom": ["@types/react-dom@19.1.5", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg=="],
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="],
"@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
"@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="],
"@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.32.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/type-utils": "8.32.1", "@typescript-eslint/utils": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.32.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg=="],
@@ -210,6 +314,8 @@
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w=="],
"@typescript/vfs": ["@typescript/vfs@1.6.1", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA=="],
"@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.7.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg=="],
"@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.7.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ=="],
@@ -250,7 +356,15 @@
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
"ansis": ["ansis@3.17.0", "", {}, "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
@@ -274,20 +388,36 @@
"ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="],
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
"axios": ["axios@1.10.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
@@ -300,12 +430,32 @@
"caniuse-lite": ["caniuse-lite@1.0.30001718", "", {}, "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw=="],
"case": ["case@1.6.3", "", {}, "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="],
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
"clean-stack": ["clean-stack@3.0.1", "", { "dependencies": { "escape-string-regexp": "4.0.0" } }, "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg=="],
"cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
"cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
"cli-width": ["cli-width@3.0.0", "", {}, "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw=="],
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@@ -314,12 +464,20 @@
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"daisyui": ["daisyui@5.0.37", "", {}, "sha512-PLc+MhWAqTwolygEGPDi+ac+OsFqIt9nZylTIiyVlEx8loYL7Pt7hNWb8cp5pQQ9dhjYnda1ERiuM6OsJmvPGw=="],
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
@@ -330,22 +488,40 @@
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="],
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
"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=="],
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"engine.io-client": ["engine.io-client@6.6.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w=="],
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
"es-abstract": ["es-abstract@1.23.10", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
@@ -366,7 +542,7 @@
"eslint": ["eslint@9.27.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.27.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q=="],
"eslint-config-next": ["eslint-config-next@15.3.2", "", { "dependencies": { "@next/eslint-plugin-next": "15.3.2", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-FerU4DYccO4FgeYFFglz0SnaKRe1ejXQrDb8kWUkTAg036YWi+jUsgg4sIGNCDhAsDITsZaL4MzBWKB6f4G1Dg=="],
"eslint-config-next": ["eslint-config-next@15.3.1", "", { "dependencies": { "@next/eslint-plugin-next": "15.3.1", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-GnmyVd9TE/Ihe3RrvcafFhXErErtr2jS0JDeCSp3vWvy86AXwHsRBt0E3MqP/m8ACS1ivcsi5uaqjbhsG18qKw=="],
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
@@ -386,6 +562,8 @@
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
"esm": ["esm@3.2.25", "", {}, "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="],
"espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
@@ -396,6 +574,10 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="],
"external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
@@ -408,18 +590,34 @@
"fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
"figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
"framer-motion": ["framer-motion@12.12.1", "", { "dependencies": { "motion-dom": "^12.12.1", "motion-utils": "^12.12.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-PFw4/GCREHI2suK/NlPSUxd+x6Rkp80uQsfCRFSOQNrm5pZif7eGtmG1VaD/UF1fW9tRBy5AaS77StatB3OJDg=="],
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
@@ -428,6 +626,8 @@
"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-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
@@ -460,14 +660,28 @@
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
"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=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"inquirer": ["inquirer@8.2.6", "", { "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", "cli-cursor": "^3.1.0", "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.21", "mute-stream": "0.0.8", "ora": "^5.4.1", "run-async": "^2.4.0", "rxjs": "^7.5.5", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6", "wrap-ansi": "^6.0.1" } }, "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg=="],
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
"intl-messageformat": ["intl-messageformat@10.7.16", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "@formatjs/fast-memoize": "2.2.7", "@formatjs/icu-messageformat-parser": "2.11.2", "tslib": "^2.8.0" } }, "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug=="],
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
@@ -476,6 +690,8 @@
"is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
"is-bun-module": ["is-bun-module@2.0.0", "", { "dependencies": { "semver": "^7.7.1" } }, "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ=="],
@@ -488,20 +704,28 @@
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-generator-function": ["is-generator-function@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="],
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
"is-observable": ["is-observable@2.1.0", "", {}, "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw=="],
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
@@ -514,32 +738,54 @@
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
"is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
"jake": ["jake@10.9.2", "", { "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", "filelist": "^1.0.4", "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" } }, "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA=="],
"jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
"jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
"jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="],
"jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
"jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
@@ -572,20 +818,36 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"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=="],
"memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"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=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -596,16 +858,28 @@
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
"motion-dom": ["motion-dom@12.12.1", "", { "dependencies": { "motion-utils": "^12.12.1" } }, "sha512-GXq/uUbZBEiFFE+K1Z/sxdPdadMdfJ/jmBALDfIuHGi0NmtealLOfH9FqT+6aNPgVx8ilq0DtYmyQlo6Uj9LKQ=="],
"motion-utils": ["motion-utils@12.12.1", "", {}, "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mute-stream": ["mute-stream@0.0.8", "", {}, "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"napi-postinstall": ["napi-postinstall@0.2.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"next": ["next@15.3.2", "", { "dependencies": { "@next/env": "15.3.2", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.2", "@next/swc-darwin-x64": "15.3.2", "@next/swc-linux-arm64-gnu": "15.3.2", "@next/swc-linux-arm64-musl": "15.3.2", "@next/swc-linux-x64-gnu": "15.3.2", "@next/swc-linux-x64-musl": "15.3.2", "@next/swc-win32-arm64-msvc": "15.3.2", "@next/swc-win32-x64-msvc": "15.3.2", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ=="],
"next-intl": ["next-intl@4.1.0", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.5.4", "negotiator": "^1.0.0", "use-intl": "^4.1.0" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-JNJRjc7sdnfUxhZmGcvzDszZ60tQKrygV/VLsgzXhnJDxQPn1cN2rVpc53adA1SvBJwPK2O6Sc6b4gYSILjCzw=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -622,8 +896,16 @@
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
"observable-fns": ["observable-fns@0.6.1", "", {}, "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg=="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
"os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
@@ -632,15 +914,19 @@
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
@@ -648,8 +934,16 @@
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.0.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg=="],
"pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
"prism-react-renderer": ["prism-react-renderer@2.4.1", "", { "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": ">=16.0.0" } }, "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig=="],
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
@@ -658,7 +952,17 @@
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"react-select": ["react-select@5.10.1", "", { "dependencies": { "@babel/runtime": "^7.12.0", "@emotion/cache": "^11.4.0", "@emotion/react": "^11.8.1", "@floating-ui/dom": "^1.0.1", "@types/react-transition-group": "^4.4.0", "memoize-one": "^6.0.0", "prop-types": "^15.6.0", "react-transition-group": "^4.3.0", "use-isomorphic-layout-effect": "^1.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-roPEZUL4aRZDx6DcsD+ZNreVl+fM8VsKn0Wtex1v4IazH60ILp5xhdlp464IsEAlJdXeD+BhDAFsBVMfvLQueA=="],
"react-toastify": ["react-toastify@11.0.5", "", { "dependencies": { "clsx": "^2.1.1" }, "peerDependencies": { "react": "^18 || ^19", "react-dom": "^18 || ^19" } }, "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA=="],
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
@@ -670,16 +974,26 @@
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"run-async": ["run-async@2.4.1", "", {}, "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -704,14 +1018,28 @@
"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@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"socket.io-client": ["socket.io-client@4.8.1", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ=="],
"socket.io-parser": ["socket.io-parser@4.2.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew=="],
"source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
"streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
"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=="],
"string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="],
"string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
@@ -724,34 +1052,56 @@
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"tailwind-scrollbar": ["tailwind-scrollbar@4.0.2", "", { "dependencies": { "prism-react-renderer": "^2.4.1" }, "peerDependencies": { "tailwindcss": "4.x" } }, "sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA=="],
"tailwindcss": ["tailwindcss@4.1.7", "", {}, "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg=="],
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
"threads": ["threads@1.7.0", "", { "dependencies": { "callsites": "^3.1.0", "debug": "^4.2.0", "is-observable": "^2.1.0", "observable-fns": "^0.6.1" }, "optionalDependencies": { "tiny-worker": ">= 2" } }, "sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ=="],
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
"tiny-worker": ["tiny-worker@2.3.0", "", { "dependencies": { "esm": "^3.2.25" } }, "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g=="],
"tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="],
"tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
"ts-to-zod": ["ts-to-zod@3.15.0", "", { "dependencies": { "@oclif/core": ">=3.26.0", "@typescript/vfs": "^1.5.0", "case": "^1.6.3", "chokidar": "^3.5.1", "fs-extra": "^11.1.1", "inquirer": "^8.2.0", "lodash": "^4.17.21", "ora": "^5.4.0", "prettier": "3.0.3", "rxjs": "^7.4.0", "slash": "^3.0.0", "threads": "^1.7.0", "tslib": "^2.3.1", "tsutils": "^3.21.0", "typescript": "^5.2.2", "zod": "^3.23.8" }, "bin": { "ts-to-zod": "bin/run" } }, "sha512-Lu5ITqD8xCIo4JZp4Cg3iSK3J2x3TGwwuDtNHfAIlx1mXWKClRdzqV+x6CFEzhKtJlZzhyvJIqg7DzrWfsdVSg=="],
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tsutils": ["tsutils@3.21.0", "", { "dependencies": { "tslib": "^1.8.1" }, "peerDependencies": { "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
"typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
@@ -766,10 +1116,20 @@
"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"unrs-resolver": ["unrs-resolver@1.7.2", "", { "dependencies": { "napi-postinstall": "^0.2.2" }, "optionalDependencies": { "@unrs/resolver-binding-darwin-arm64": "1.7.2", "@unrs/resolver-binding-darwin-x64": "1.7.2", "@unrs/resolver-binding-freebsd-x64": "1.7.2", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.2", "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.2", "@unrs/resolver-binding-linux-arm64-gnu": "1.7.2", "@unrs/resolver-binding-linux-arm64-musl": "1.7.2", "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.2", "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.2", "@unrs/resolver-binding-linux-riscv64-musl": "1.7.2", "@unrs/resolver-binding-linux-s390x-gnu": "1.7.2", "@unrs/resolver-binding-linux-x64-gnu": "1.7.2", "@unrs/resolver-binding-linux-x64-musl": "1.7.2", "@unrs/resolver-binding-wasm32-wasi": "1.7.2", "@unrs/resolver-binding-win32-arm64-msvc": "1.7.2", "@unrs/resolver-binding-win32-ia32-msvc": "1.7.2", "@unrs/resolver-binding-win32-x64-msvc": "1.7.2" } }, "sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"use-intl": ["use-intl@4.1.0", "", { "dependencies": { "@formatjs/fast-memoize": "^2.2.0", "@schummar/icu-type-parser": "1.21.5", "intl-messageformat": "^10.5.14" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" } }, "sha512-mQvDYFvoGn+bm/PWvlQOtluKCknsQ5a9F1Cj0hMfBjMBVTwnOqLPd6srhjvVdEQEQFVyHM1PfyifKqKYb11M9Q=="],
"use-isomorphic-layout-effect": ["use-isomorphic-layout-effect@1.2.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
@@ -780,16 +1140,44 @@
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
"widest-line": ["widest-line@3.1.0", "", { "dependencies": { "string-width": "^4.0.0" } }, "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
"zustand": ["zustand@5.0.5", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg=="],
"@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@formatjs/ecma402-abstract/@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.1", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg=="],
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
"@oclif/core/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@oclif/core/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@oclif/core/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"@oclif/core/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
@@ -810,6 +1198,14 @@
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"engine.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
@@ -820,16 +1216,48 @@
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"is-bun-module/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"fdir/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"figures/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
"filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"inquirer/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
"is-bun-module/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"sharp/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"socket.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"socket.io-parser/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
"string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"tinyglobby/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
"tsutils/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@oclif/core/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"@oclif/core/tinyglobby/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
"@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"filelist/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"inquirer/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
}
}
+261882
View File
File diff suppressed because it is too large Load Diff
+261882
View File
File diff suppressed because it is too large Load Diff
+261882
View File
File diff suppressed because it is too large Load Diff
+261882
View File
File diff suppressed because it is too large Load Diff
+873
View File
@@ -0,0 +1,873 @@
{
"AS": {
"3001": {
"buff_1": [
3111008,
3111010,
3111011
],
"buff_2": [
3111008,
3111009,
3111012
],
"maze_buff": 3110001
},
"3002": {
"buff_1": [
3111015,
3111013,
3111014
],
"buff_2": [
3111015,
3111016,
3111017
],
"maze_buff": 3110002
},
"3003": {
"buff_1": [
3111021,
3111022,
3111023
],
"buff_2": [
3111018,
3111019,
3111020
],
"maze_buff": 3110003
},
"3004": {
"buff_1": [
3111028,
3111027,
3111021
],
"buff_2": [
3111024,
3111025,
3111026
],
"maze_buff": 3110004
},
"3005": {
"buff_1": [
3111030,
3111033,
3111034
],
"buff_2": [
3111029,
3111032,
3111031
],
"maze_buff": 3110005
},
"3006": {
"buff_1": [
3111035,
3111036,
3111037
],
"buff_2": [
3111038,
3111039,
3111040
],
"maze_buff": 3110006
},
"3007": {
"buff_1": [
3111041,
3111042,
3111043
],
"buff_2": [
3111044,
3111045,
3111046
],
"maze_buff": 3110007
},
"3008": {
"buff_1": [
3111047,
3111048,
3111049
],
"buff_2": [
3111050,
3111051,
3111052
],
"maze_buff": 3110008
},
"3009": {
"buff_1": [
3111053,
3111018,
3111020
],
"buff_2": [
3111055,
3111021,
3111030
],
"maze_buff": 3110009
},
"3010": {
"buff_1": [
3111056,
3111057,
3111058
],
"buff_2": [
3111046,
3111014,
3111021
],
"maze_buff": 3110010
},
"3011": {
"buff_1": [
3111056,
3111057,
3111058
],
"buff_2": [
3111046,
3111014,
3111021
],
"maze_buff": 3110010
}
},
"PF": {
"2001": {
"buff": [
3031301,
3031302,
3031303
],
"maze_buff": 3031001
},
"2002": {
"buff": [
3031304,
3031305,
3031306
],
"maze_buff": 3031002
},
"2003": {
"buff": [
3031307,
3031308,
3031309
],
"maze_buff": 3031003
},
"2004": {
"buff": [
3031310,
3031311,
3031312
],
"maze_buff": 3031001
},
"2005": {
"buff": [
3031313,
3031314,
3031315
],
"maze_buff": 3031003
},
"2006": {
"buff": [
3031316,
3031317,
3031318
],
"maze_buff": 3031002
},
"2007": {
"buff": [
3031319,
3031320,
3031321
],
"maze_buff": 3031004
},
"2008": {
"buff": [
3031303,
3031322,
3031323
],
"maze_buff": 3031001
},
"2009": {
"buff": [
3031324,
3031325,
3031326
],
"maze_buff": 3031005
},
"2010": {
"buff": [
3031330,
3031331,
3031305
],
"maze_buff": 3031002
},
"2011": {
"buff": [
3031327,
3031328,
3031329
],
"maze_buff": 3031200
},
"2012": {
"buff": [
3031333,
3031332,
3031334
],
"maze_buff": 3031205
},
"2013": {
"buff": [
3031335,
3031336,
3031337
],
"maze_buff": 3031210
},
"2014": {
"buff": [
3031327,
3031338,
3031329
],
"maze_buff": 3031200
},
"2015": {
"buff": [
3031333,
3031343,
3031339
],
"maze_buff": 3031205
},
"2016": {
"buff": [
3031340,
3031341,
3031342
],
"maze_buff": 3031215
}
},
"MOC": {
"100": {
"maze_buff": 3030001
},
"900": {
"maze_buff": 3030036
},
"101": {
"maze_buff": 3030104
},
"102": {
"maze_buff": 3030104
},
"103": {
"maze_buff": 3030104
},
"104": {
"maze_buff": 3030104
},
"105": {
"maze_buff": 3030104
},
"106": {
"maze_buff": 3030104
},
"107": {
"maze_buff": 3030104
},
"108": {
"maze_buff": 3030104
},
"109": {
"maze_buff": 3030104
},
"110": {
"maze_buff": 3030107
},
"111": {
"maze_buff": 3030108
},
"112": {
"maze_buff": 3030109
},
"113": {
"maze_buff": 3030110
},
"114": {
"maze_buff": 3030111
},
"115": {
"maze_buff": 3030112
},
"116": {
"maze_buff": 3030104
},
"117": {
"maze_buff": 3030104
},
"118": {
"maze_buff": 3030104
},
"119": {
"maze_buff": 3030104
},
"1001": {
"maze_buff": 3030113
},
"1002": {
"maze_buff": 3030114
},
"1003": {
"maze_buff": 3030115
},
"1004": {
"maze_buff": 3030116
},
"1005": {
"maze_buff": 3030117
},
"1006": {
"maze_buff": 3030120
},
"1007": {
"maze_buff": 3030119
},
"1008": {
"maze_buff": 3030118
},
"1009": {
"maze_buff": 3030121
},
"1010": {
"maze_buff": 3030123
},
"1011": {
"maze_buff": 3030124
},
"1012": {
"maze_buff": 3030125
},
"1013": {
"maze_buff": 3030126
},
"1014": {
"maze_buff": 3030127
},
"1015": {
"maze_buff": 3030128
},
"1016": {
"maze_buff": 3030129
},
"1017": {
"maze_buff": 3030130
},
"1018": {
"maze_buff": 3030131
},
"1019": {
"maze_buff": 3030132
},
"1020": {
"maze_buff": 3030133
},
"1021": {
"maze_buff": 3030134
},
"1022": {
"maze_buff": 3030135
},
"1023": {
"maze_buff": 3030136
},
"1024": {
"maze_buff": 3030137
},
"1025": {
"maze_buff": 3030138
}
},
"Avatar": {
"1001": {
"maze_buff": [
100101
]
},
"1002": {
"maze_buff": [
100201
]
},
"1003": {
"maze_buff": [
100301
]
},
"1004": {
"maze_buff": [
100401
]
},
"1005": {
"maze_buff": [
100501
]
},
"1006": {
"maze_buff": [
100601
]
},
"1008": {
"maze_buff": [
100801
]
},
"1009": {
"maze_buff": [
100901
]
},
"1013": {
"maze_buff": [
101301
]
},
"1101": {
"maze_buff": [
110101
]
},
"1102": {
"maze_buff": [
110201,
110202,
110203
]
},
"1103": {
"maze_buff": [
110301
]
},
"1104": {
"maze_buff": [
110401
]
},
"1105": {
"maze_buff": [
110501
]
},
"1106": {
"maze_buff": [
110601
]
},
"1107": {
"maze_buff": [
110701
]
},
"1108": {
"maze_buff": [
110801
]
},
"1109": {
"maze_buff": [
110901
]
},
"1110": {
"maze_buff": [
111001
]
},
"1111": {
"maze_buff": [
111101
]
},
"1112": {
"maze_buff": [
111201
]
},
"1201": {
"maze_buff": [
120101
]
},
"1202": {
"maze_buff": []
},
"1203": {
"maze_buff": [
120301
]
},
"1204": {
"maze_buff": [
120401
]
},
"1205": {
"maze_buff": [
120501
]
},
"1206": {
"maze_buff": [
120601
]
},
"1207": {
"maze_buff": [
120701,
120702
]
},
"1208": {
"maze_buff": [
120801,
120802
]
},
"1209": {
"maze_buff": [
120901
]
},
"1210": {
"maze_buff": [
121001
]
},
"1211": {
"maze_buff": [
121101
]
},
"1212": {
"maze_buff": [
121201,
121202,
121203
]
},
"1213": {
"maze_buff": [
121301,
121302,
121303
]
},
"1214": {
"maze_buff": [
121401
]
},
"1215": {
"maze_buff": [
121501
]
},
"1217": {
"maze_buff": [
121701
]
},
"1218": {
"maze_buff": [
121801
]
},
"1220": {
"maze_buff": [
122001,
122002,
122003,
122004
]
},
"1221": {
"maze_buff": [
122101
]
},
"1222": {
"maze_buff": [
122201
]
},
"1223": {
"maze_buff": [
122304,
122301,
122302,
122303
]
},
"1224": {
"maze_buff": [
122401,
122402,
122403
]
},
"1225": {
"maze_buff": [
122501
]
},
"1301": {
"maze_buff": [
130101
]
},
"1302": {
"maze_buff": [
130201
]
},
"1303": {
"maze_buff": [
130301,
130302,
130303
]
},
"1304": {
"maze_buff": [
130401,
130402,
130403,
130404,
130405,
130406
]
},
"1305": {
"maze_buff": [
130501
]
},
"1306": {
"maze_buff": [
130601,
130602
]
},
"1307": {
"maze_buff": [
130701
]
},
"1308": {
"maze_buff": [
130801,
130802,
130803
]
},
"1309": {
"maze_buff": [
130901,
130902,
130903
]
},
"1310": {
"maze_buff": [
131001,
131002
]
},
"1312": {
"maze_buff": [
131201
]
},
"1313": {
"maze_buff": [
131301
]
},
"1314": {
"maze_buff": [
131401
]
},
"1315": {
"maze_buff": [
131501,
131502,
131503
]
},
"1317": {
"maze_buff": [
131701,
131702
]
},
"1401": {
"maze_buff": [
140101,
140102
]
},
"1402": {
"maze_buff": [
140201,
140202
]
},
"1403": {
"maze_buff": [
140301
]
},
"1404": {
"maze_buff": [
140401
]
},
"1405": {
"maze_buff": [
140501,
140502,
140503
]
},
"1406": {
"maze_buff": [
140601,
140602,
140603,
140604
]
},
"1407": {
"maze_buff": [
140704,
140701,
140702,
140703
]
},
"1408": {
"maze_buff": [
140801,
140802
]
},
"1409": {
"maze_buff": [
140901
]
},
"8001": {
"maze_buff": []
},
"8002": {
"maze_buff": []
},
"8003": {
"maze_buff": [
800301
]
},
"8004": {
"maze_buff": [
800301
]
},
"8005": {
"maze_buff": [
800501
]
},
"8006": {
"maze_buff": [
800501
]
},
"8007": {
"maze_buff": [
800701
]
},
"8008": {
"maze_buff": [
800701
]
},
"1014": {
"maze_buff": [
101401
]
},
"1015": {
"maze_buff": [
101501
]
},
"7205": {
"maze_buff": []
},
"7212": {
"maze_buff": []
},
"7213": {
"maze_buff": []
},
"7005": {
"maze_buff": []
},
"7006": {
"maze_buff": []
},
"6023": {
"maze_buff": []
},
"8901": {
"maze_buff": []
},
"8902": {
"maze_buff": []
},
"6022": {
"maze_buff": []
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+762
View File
@@ -0,0 +1,762 @@
{
"21": {
"1": {
"property": "HPDelta",
"base": 45.15840148925781,
"step": 15.805439949035645,
"step_num": null
}
},
"22": {
"1": {
"property": "AttackDelta",
"base": 22.579200744628903,
"step": 7.902719974517822,
"step_num": null
}
},
"23": {
"1": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.012096000835299492,
"step_num": null
},
"4": {
"property": "CriticalChanceBase",
"base": 0.020736001431941983,
"step": 0.00725799985229969,
"step_num": null
},
"5": {
"property": "CriticalDamageBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"6": {
"property": "HealRatioBase",
"base": 0.02211800031363964,
"step": 0.0077410005033016205,
"step_num": null
},
"7": {
"property": "StatusProbabilityBase",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
}
},
"24": {
"1": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.012096000835299492,
"step_num": null
},
"4": {
"property": "SpeedDelta",
"base": 1.6128000020980835,
"step": 1.0,
"step_num": null
}
},
"25": {
"1": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.012096000835299492,
"step_num": null
},
"4": {
"property": "PhysicalAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"5": {
"property": "FireAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"6": {
"property": "IceAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"7": {
"property": "ThunderAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"8": {
"property": "WindAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"9": {
"property": "QuantumAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"10": {
"property": "ImaginaryAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
}
},
"26": {
"1": {
"property": "BreakDamageAddedRatioBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"2": {
"property": "SPRatioBase",
"base": 0.012442000210285189,
"step": 0.004355000331997871,
"step_num": null
},
"3": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"4": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"5": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.012096000835299492,
"step_num": null
}
},
"31": {
"1": {
"property": "HPDelta",
"base": 67.73760223388672,
"step": 23.708160400390625,
"step_num": null
}
},
"32": {
"1": {
"property": "AttackDelta",
"base": 33.86880111694336,
"step": 11.854080200195312,
"step_num": null
}
},
"33": {
"1": {
"property": "HPAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
},
"4": {
"property": "CriticalChanceBase",
"base": 0.03110400028526783,
"step": 0.010886001400649548,
"step_num": null
},
"5": {
"property": "CriticalDamageBase",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"6": {
"property": "HealRatioBase",
"base": 0.03317800164222717,
"step": 0.011611999943852425,
"step_num": null
},
"7": {
"property": "StatusProbabilityBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
}
},
"34": {
"1": {
"property": "HPAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
},
"4": {
"property": "SpeedDelta",
"base": 2.4191999435424805,
"step": 1.0,
"step_num": null
}
},
"35": {
"1": {
"property": "HPAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
},
"4": {
"property": "PhysicalAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"5": {
"property": "FireAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"6": {
"property": "IceAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"7": {
"property": "ThunderAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"8": {
"property": "WindAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"9": {
"property": "QuantumAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"10": {
"property": "ImaginaryAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
}
},
"36": {
"1": {
"property": "BreakDamageAddedRatioBase",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"2": {
"property": "SPRatioBase",
"base": 0.018662000074982643,
"step": 0.006532000377774239,
"step_num": null
},
"3": {
"property": "HPAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"4": {
"property": "AttackAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"5": {
"property": "DefenceAddedRatio",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
}
},
"41": {
"1": {
"property": "HPDelta",
"base": 90.31680297851562,
"step": 31.61087989807129,
"step_num": null
}
},
"42": {
"1": {
"property": "AttackDelta",
"base": 45.15840148925781,
"step": 15.805439949035645,
"step_num": null
}
},
"43": {
"1": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"4": {
"property": "CriticalChanceBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"5": {
"property": "CriticalDamageBase",
"base": 0.08294399827718735,
"step": 0.029029998928308487,
"step_num": null
},
"6": {
"property": "HealRatioBase",
"base": 0.04423699900507927,
"step": 0.015483000315725803,
"step_num": null
},
"7": {
"property": "StatusProbabilityBase",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
}
},
"44": {
"1": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"4": {
"property": "SpeedDelta",
"base": 3.225600004196167,
"step": 1.100000023841858,
"step_num": null
}
},
"45": {
"1": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"4": {
"property": "PhysicalAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"5": {
"property": "FireAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"6": {
"property": "IceAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"7": {
"property": "ThunderAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"8": {
"property": "WindAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"9": {
"property": "QuantumAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"10": {
"property": "ImaginaryAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
}
},
"46": {
"1": {
"property": "BreakDamageAddedRatioBase",
"base": 0.08294399827718735,
"step": 0.029029998928308487,
"step_num": null
},
"2": {
"property": "SPRatioBase",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"3": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"4": {
"property": "AttackAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"5": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
}
},
"51": {
"1": {
"property": "HPDelta",
"base": 112.89600372314452,
"step": 39.51359939575195,
"step_num": null
}
},
"52": {
"1": {
"property": "AttackDelta",
"base": 56.448001861572266,
"step": 19.756799697875977,
"step_num": null
}
},
"53": {
"1": {
"property": "HPAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.08640000224113464,
"step": 0.030240001156926155,
"step_num": null
},
"4": {
"property": "CriticalChanceBase",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
},
"5": {
"property": "CriticalDamageBase",
"base": 0.10367999970912932,
"step": 0.036288000643253326,
"step_num": null
},
"6": {
"property": "HealRatioBase",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"7": {
"property": "StatusProbabilityBase",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
}
},
"54": {
"1": {
"property": "HPAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.08640000224113464,
"step": 0.030240001156926155,
"step_num": null
},
"4": {
"property": "SpeedDelta",
"base": 4.0320000648498535,
"step": 1.399999976158142,
"step_num": null
}
},
"55": {
"1": {
"property": "HPAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.08640000224113464,
"step": 0.030240001156926155,
"step_num": null
},
"4": {
"property": "PhysicalAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"5": {
"property": "FireAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"6": {
"property": "IceAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"7": {
"property": "ThunderAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"8": {
"property": "WindAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"9": {
"property": "QuantumAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"10": {
"property": "ImaginaryAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
}
},
"56": {
"1": {
"property": "BreakDamageAddedRatioBase",
"base": 0.10367999970912932,
"step": 0.036288000643253326,
"step_num": null
},
"2": {
"property": "SPRatioBase",
"base": 0.03110400028526783,
"step": 0.010886001400649548,
"step_num": null
},
"3": {
"property": "HPAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"4": {
"property": "AttackAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"5": {
"property": "DefenceAddedRatio",
"base": 0.08640000224113464,
"step": 0.030240001156926155,
"step_num": null
}
},
"433": {
"1": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
}
},
"434": {
"1": {
"property": "CriticalChanceBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
}
},
"436": {
"1": {
"property": "HealRatioBase",
"base": 0.04423699900507927,
"step": 0.015483000315725803,
"step_num": null
}
},
"441": {
"1": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
}
},
"443": {
"1": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
}
}
}
+1656
View File
File diff suppressed because it is too large Load Diff
+1656
View File
File diff suppressed because it is too large Load Diff
+1656
View File
File diff suppressed because it is too large Load Diff
+1656
View File
File diff suppressed because it is too large Load Diff
+298
View File
@@ -0,0 +1,298 @@
{
"2": {
"1": {
"property": "HPDelta",
"base": 13.548015594482422,
"step": 1.6935019493103027,
"step_num": 2
},
"2": {
"property": "AttackDelta",
"base": 6.774007797241211,
"step": 0.8467509746551514,
"step_num": 2
},
"3": {
"property": "DefenceDelta",
"base": 6.774007797241211,
"step": 0.8467509746551514,
"step_num": 2
},
"4": {
"property": "HPAddedRatio",
"base": 0.01382400095462799,
"step": 0.001728000584989786,
"step_num": 2
},
"5": {
"property": "AttackAddedRatio",
"base": 0.01382400095462799,
"step": 0.001728000584989786,
"step_num": 2
},
"6": {
"property": "DefenceAddedRatio",
"base": 0.01728000119328499,
"step": 0.002160000381991267,
"step_num": 2
},
"7": {
"property": "SpeedDelta",
"base": 1.0,
"step": 0.10000000149011612,
"step_num": 2
},
"8": {
"property": "CriticalChanceBase",
"base": 0.010368000715970991,
"step": 0.001296000787988305,
"step_num": 2
},
"9": {
"property": "CriticalDamageBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"10": {
"property": "StatusProbabilityBase",
"base": 0.01382400095462799,
"step": 0.001728000584989786,
"step_num": 2
},
"11": {
"property": "StatusResistanceBase",
"base": 0.01382400095462799,
"step": 0.001728000584989786,
"step_num": 2
},
"12": {
"property": "BreakDamageAddedRatioBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
}
},
"3": {
"1": {
"property": "HPDelta",
"base": 20.322023391723633,
"step": 2.540252923965454,
"step_num": 2
},
"2": {
"property": "AttackDelta",
"base": 10.161011695861816,
"step": 1.2701259851455688,
"step_num": 2
},
"3": {
"property": "DefenceDelta",
"base": 10.161011695861816,
"step": 1.2701259851455688,
"step_num": 2
},
"4": {
"property": "HPAddedRatio",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"5": {
"property": "AttackAddedRatio",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"6": {
"property": "DefenceAddedRatio",
"base": 0.025919999927282333,
"step": 0.0032400002237409353,
"step_num": 2
},
"7": {
"property": "SpeedDelta",
"base": 1.2000000476837158,
"step": 0.10000000149011612,
"step_num": 2
},
"8": {
"property": "CriticalChanceBase",
"base": 0.015552000142633917,
"step": 0.001944000832736492,
"step_num": 2
},
"9": {
"property": "CriticalDamageBase",
"base": 0.03110400028526783,
"step": 0.003888000966981054,
"step_num": 2
},
"10": {
"property": "StatusProbabilityBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"11": {
"property": "StatusResistanceBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"12": {
"property": "BreakDamageAddedRatioBase",
"base": 0.03110400028526783,
"step": 0.003888000966981054,
"step_num": 2
}
},
"4": {
"1": {
"property": "HPDelta",
"base": 27.096031188964844,
"step": 3.3870038986206055,
"step_num": 2
},
"2": {
"property": "AttackDelta",
"base": 13.548015594482422,
"step": 1.6935019493103027,
"step_num": 2
},
"3": {
"property": "DefenceDelta",
"base": 13.548015594482422,
"step": 1.6935019493103027,
"step_num": 2
},
"4": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.0034560004714876413,
"step_num": 2
},
"5": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.0034560004714876413,
"step_num": 2
},
"6": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"7": {
"property": "SpeedDelta",
"base": 1.600000023841858,
"step": 0.20000000298023224,
"step_num": 2
},
"8": {
"property": "CriticalChanceBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"9": {
"property": "CriticalDamageBase",
"base": 0.041471999138593674,
"step": 0.0051840003579854965,
"step_num": 2
},
"10": {
"property": "StatusProbabilityBase",
"base": 0.027648000046610832,
"step": 0.0034560004714876413,
"step_num": 2
},
"11": {
"property": "StatusResistanceBase",
"base": 0.027648000046610832,
"step": 0.0034560004714876413,
"step_num": 2
},
"12": {
"property": "BreakDamageAddedRatioBase",
"base": 0.041471999138593674,
"step": 0.0051840003579854965,
"step_num": 2
}
},
"5": {
"1": {
"property": "HPDelta",
"base": 33.87004089355469,
"step": 4.233755111694336,
"step_num": 2
},
"2": {
"property": "AttackDelta",
"base": 16.93501853942871,
"step": 2.1168770790100098,
"step_num": 2
},
"3": {
"property": "DefenceDelta",
"base": 16.93501853942871,
"step": 2.1168770790100098,
"step_num": 2
},
"4": {
"property": "HPAddedRatio",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"5": {
"property": "AttackAddedRatio",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"6": {
"property": "DefenceAddedRatio",
"base": 0.04320000112056732,
"step": 0.005400000140070915,
"step_num": 2
},
"7": {
"property": "SpeedDelta",
"base": 2.0,
"step": 0.30000001192092896,
"step_num": 2
},
"8": {
"property": "CriticalChanceBase",
"base": 0.025919999927282333,
"step": 0.0032400002237409353,
"step_num": 2
},
"9": {
"property": "CriticalDamageBase",
"base": 0.05183999985456467,
"step": 0.006480000447481871,
"step_num": 2
},
"10": {
"property": "StatusProbabilityBase",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"11": {
"property": "StatusResistanceBase",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"12": {
"property": "BreakDamageAddedRatioBase",
"base": 0.05183999985456467,
"step": 0.006480000447481871,
"step_num": 2
}
}
}
+15
View File
@@ -0,0 +1,15 @@
services:
srtools:
build:
context: .
dockerfile: Dockerfile
container_name: srtools
restart: unless-stopped
ports:
- "3006:3000"
networks:
- srtools-network
networks:
srtools-network:
driver: bridge
+11
View File
@@ -0,0 +1,11 @@
import { getRequestConfig } from "next-intl/server";
import { cookies } from "next/headers";
export default getRequestConfig( async () => {
const locale = (await cookies()).get("MYNEXTAPP_LOCALE")?.value || "en";
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default
}
})
+177
View File
@@ -0,0 +1,177 @@
{
"TabTitle": {
"title": "Firefly Tools",
"description": "Firefly tools by Kain"
},
"DataPage": {
"skillType": "技能类型",
"skillName": "技能名称",
"character": "角色",
"id": "ID",
"path": "命运",
"rarity": "稀有度",
"element": "元素",
"technique": "秘术",
"talent": "天赋",
"basic": "普通攻击",
"skill": "技能",
"ultimate": "终极技",
"servant": "仆从",
"damage": "伤害",
"type": "类型",
"warrior": "毁灭",
"knight": "守护",
"mage": "博学",
"priest": "丰饶",
"rogue": "狩猎",
"shaman": "和谐",
"warlock": "虚无",
"memory": "回忆",
"fire": "火",
"ice": "冰",
"imaginary": "幻象",
"physical": "物理",
"quantum": "量子",
"thunder": "雷",
"wind": "风",
"hp": "生命值",
"atk": "攻击",
"speed": "速度",
"critRate": "暴击率",
"critDmg": "暴击伤害",
"breakEffect": "破甲伤害",
"effectRes": "效果抗性",
"energyRegenerationRate": "能量恢复速率",
"effectHitRate": "效果命中率",
"outgoingHealingBoost": "治疗增强",
"fireDmgBoost": "火元素伤害增强",
"iceDmgBoost": "冰元素伤害增强",
"imaginaryDmgBoost": "幻象伤害增强",
"physicalDmgBoost": "物理伤害增强",
"quantumDmgBoost": "量子伤害增强",
"thunderDmgBoost": "雷元素伤害增强",
"windDmgBoost": "风元素伤害增强",
"pursued": "附加伤害",
"true damage": "真实伤害",
"follow-up": "后续伤害",
"elemental damage": "破甲与超破甲伤害",
"dot": "持续伤害",
"qte": "QTE 技能",
"level": "等级",
"relics": "遗器",
"eidolons": "虚数形态",
"lightcones": "光锥",
"loadData": "加载数据",
"exportData": "导出数据",
"connectSetting": "连接设置",
"connected": "已连接",
"unconnected": "未连接",
"psConnection": "PS 连接",
"connectionType": "连接类型",
"status": "状态",
"connectPs": "连接 PS",
"other": "其他",
"freeSr": "FreeSR",
"database": "数据库",
"enka": "Enka",
"monsterSetting": "怪物设置",
"serverUrl": "服务器 URL",
"privateType": "私有类型",
"local": "本地",
"server": "服务器",
"username": "用户名",
"password": "密码",
"placeholderServerUrl": "输入服务器 URL",
"placeholderUsername": "输入用户名",
"placeholderPassword": "输入密码",
"connectedSuccess": "成功连接 PS",
"connectedFailed": "连接 PS 失败",
"syncSuccess": "已成功与 PS 同步数据",
"syncFailed": "与 PS 同步数据失败",
"sync": "同步",
"importSetting": "导入设置",
"profile": "配置档",
"default": "默认",
"copyProfiles": "复制配置档",
"addNewProfile": "添加新配置档",
"createNewProfile": "创建新配置档",
"editProfile": "编辑配置档",
"placeholderProfileName": "输入配置档名称",
"profileName": "配置档名称",
"create": "创建",
"update": "更新",
"characterInformation": "角色信息",
"skills": "技能",
"showcaseCard": "展示卡",
"comingSoon": "敬请期待",
"characterName": "角色名称",
"placeholderCharacter": "输入角色名称",
"characterSettings": "角色设置",
"levelConfiguration": "等级配置",
"characterLevel": "角色等级",
"max": "最大",
"ultimateEnergy": "终极能量",
"currentEnergy": "当前能量",
"setTo50": "设为50%",
"battleConfiguration": "战斗配置",
"useTechnique": "使用秘术",
"techniqueNote": "启用战前秘术效果",
"enhancement": "强化",
"enhancementLevel": "强化等级",
"origin": "来源",
"enhancedNote": "强化更高可解锁额外技能",
"lightconeEquipment": "光锥装备",
"lightconeSettings": "光锥设置",
"placeholderLevel": "输入等级",
"superimpositionRank": "叠加等级",
"ranksNote": "更高等级提供更强效果",
"changeLightcone": "更换光锥",
"removeLightcone": "移除光锥",
"equipLightcone": "装备光锥",
"noLightconeEquipped": "未装备光锥",
"equipLightconeNote": "装备光锥以增强角色能力",
"filter": "筛选",
"selectedCharacters": "已选角色",
"selectedProfiles": "已选配置档",
"clearAll": "清除全部",
"selectAll": "全选",
"copy": "复制",
"copied": "已复制",
"noAvatarSelected": "未选择角色",
"noAvatarToCopySelected": "未选择要复制的角色",
"pleaseSelectAtLeastOneProfile": "请至少选择一个配置档",
"pleaseEnterUid": "请输入 UID",
"failedToFetchEnkaData": "获取 Enka 数据失败",
"pleaseSelectAtLeastOneCharacter": "请至少选择一个角色",
"noDataToImport": "无可导入数据",
"pleaseSelectAFile": "请选择一个文件",
"fileMustBeAValidJsonFile": "文件必须为有效 JSON",
"importEnkaDataSuccess": "导入 Enka 数据成功",
"importFreeSRDataSuccess": "导入 FreeSR 数据成功",
"importDatabaseSuccess": "导入数据库成功",
"getData": "获取数据",
"import": "导入",
"freeSRImport": "导入 FreeSR",
"onlySupportFreeSRJsonFile": "仅支持 FreeSR JSON 文件",
"pickAFile": "选择文件",
"lightConeSetting": "光锥设置",
"relicMaker": "遗器制作",
"pleaseSelectAllOptions": "请选择所有选项",
"relicSavedSuccessfully": "遗器已成功保存",
"mainSettings": "主设置",
"mainStat": "主属性",
"set": "套装",
"pleaseSelectASet": "请选择一个套装",
"effectBonus": "效果加成",
"totalRoll": "总次数",
"randomizeStats": "随机属性",
"randomizeRolls": "随机次数",
"selectASubStat": "选择副属性",
"selectASet": "选择套装",
"selectAMainStat": "选择主属性",
"save": "保存",
"reset": "重置",
"roll": "滚动",
"step": "步数"
}
}
+178
View File
@@ -0,0 +1,178 @@
{
"TabTitle": {
"title": "Firefly Tools",
"description": "Firefly tools by Kain"
},
"DataPage": {
"skillType": "Skill Type",
"skillName": "Skill Name",
"character": "Character",
"id": "Id",
"path": "Path",
"rarity": "Rarity",
"element": "Element",
"technique": "Technique",
"talent": "Talent",
"basic": "Basic Attack",
"skill": "Skill",
"ultimate": "Ultimate",
"servant": "Servant",
"damage": "Damage",
"type": "Type",
"warrior": "Destruction",
"knight": "Preservation",
"mage": "Erudition",
"priest": "Abundance",
"rogue": "The Hunt",
"shaman": "Harmony",
"warlock": "Nihility",
"memory": "Remembrance",
"fire": "Fire",
"ice": "Ice",
"imaginary": "Imaginary",
"physical": "Physical",
"quantum": "Quantum",
"thunder": "Thunder",
"wind": "Wind",
"hp": "Hp",
"atk": "Atk",
"speed": "Speed",
"critRate": "Crit Rate",
"critDmg": "Crit Dmg",
"breakEffect": "Break Effect",
"effectRes": "Effect Res",
"energyRegenerationRate": "Energy Regeneration Rate",
"effectHitRate": "Effect Hit Rate",
"outgoingHealingBoost": "Outgoing Healing Boost",
"fireDmgBoost": "Fire damage boost",
"iceDmgBoost": "Ice damage Boost",
"imaginaryDmgBoost": "Imaginary damage boost",
"physicalDmgBoost": "Physical damage boost",
"quantumDmgBoost": "Quantum damage boost",
"thunderDmgBoost": "Thunder damage boost",
"windDmgBoost": "Wind damage boost",
"pursued": "Additional damage",
"true damage": "True damage",
"follow-up": "Follow-up Damage",
"elemental damage": "Break and Super break damage",
"dot": "Damage over time ",
"qte": "QTE Skill",
"level": "Level",
"relics": "Relics",
"eidolons": "Eidolons",
"lightcones": "Lightcones",
"loadData": "Load data",
"exportData": "Export data",
"connectSetting": "Connection Setting",
"connected": "Connected",
"unconnected": "Unconnected",
"psConnection": "PS Connection",
"connectionType": "Connection Type",
"status": "Status",
"connectPs": "Connect PS",
"other": "Other",
"freeSr": "FreeSR",
"database": "Database",
"enka": "Enka",
"monsterSetting": "Monster Setting",
"serverUrl": "Server URL",
"privateType": "Private Type",
"local": "Local",
"server": "Server",
"username": "Username",
"password": "Password",
"placeholderServerUrl": "Enter server URL",
"placeholderUsername": "Enter username",
"placeholderPassword": "Enter password",
"connectedSuccess": "Connected to PS successfully",
"connectedFailed": "Failed to connect to PS",
"syncSuccess": "Synced data to PS successfully",
"syncFailed": "Failed to sync data to PS",
"sync": "Sync",
"importSetting": "Import Setting",
"profile": "Profile",
"default": "Default",
"copyProfiles": "Copy profiles",
"addNewProfile": "Add new profile",
"createNewProfile": "Create new profile",
"editProfile": "Edit profile",
"placeholderProfileName": "Enter profile name",
"profileName": "Profile name",
"create": "Create",
"update": "Update",
"characterInformation": "Character Information",
"skills": "Skills",
"showcaseCard": "Showcase Card",
"comingSoon": "Coming soon",
"characterName": "Character name",
"placeholderCharacter": "Enter character name",
"characterSettings": "Character Settings",
"levelConfiguration": "Level Configuration",
"characterLevel": "Character Level",
"max": "MAX",
"ultimateEnergy": "Ultimate Energy",
"currentEnergy": "Current Energy",
"setTo50": "Set to 50%",
"battleConfiguration": "Battle Configuration",
"useTechnique": "Use Technique",
"techniqueNote": "Enable pre-battle technique effects",
"enhancement": "Enhancement",
"enhancementLevel": "Enhancement Level",
"origin": "Origin",
"enhancedNote": "Higher enhanced unlock additional abilities",
"lightconeEquipment": "Lightcone Equipment",
"lightconeSettings": "Lightcone Settings",
"placeholderLevel": "Enter level",
"superimpositionRank": "Superimposition Rank",
"ranksNote": "Higher ranks provide stronger effects",
"changeLightcone": "Change Lightcone",
"removeLightcone": "Remove Lightcone",
"equipLightcone": "Equip Lightcone",
"noLightconeEquipped": "No Lightcone Equipped",
"equipLightconeNote": "Equip a lightcone to enhance your character's abilities",
"filter": "Filter",
"selectedCharacters": "Selected Characters",
"selectedProfiles": "Selected Profiles",
"clearAll": "Clear All",
"selectAll": "Select All",
"copy": "Copy",
"copied": "Copied",
"noAvatarSelected": "No avatar selected",
"noAvatarToCopySelected": "No avatar to copy selected",
"pleaseSelectAtLeastOneProfile": "Please select at least one profile",
"pleaseEnterUid": "Please enter UID",
"failedToFetchEnkaData": "Failed to fetch enka data",
"pleaseSelectAtLeastOneCharacter": "Please select at least one character",
"noDataToImport": "No data to import",
"pleaseSelectAFile": "Please select a file",
"fileMustBeAValidJsonFile": "File must be a valid json file",
"importEnkaDataSuccess": "Import Enka data success",
"importFreeSRDataSuccess": "Import FreeSR data success",
"importDatabaseSuccess": "Import database success",
"getData": "Get Data",
"import": "Import",
"freeSRImport": "FreeSR Import",
"onlySupportFreeSRJsonFile": "Only support FreeSR json file",
"pickAFile": "Pick a file",
"lightConeSetting": "LightCone Setting",
"relicMaker": "Relic Maker",
"pleaseSelectAllOptions": "Please select all options",
"relicSavedSuccessfully": "Relic saved successfully",
"mainSettings": "Main Settings",
"mainStat": "Main Stat",
"set": "Set",
"pleaseSelectASet": "Please select a set",
"effectBonus": "Effect Bonus",
"totalRoll": "Total Roll",
"randomizeStats": "Randomize Stats",
"randomizeRolls": "Randomize Rolls",
"selectASubStat": "Select a sub stat",
"selectASet": "Select a set",
"selectAMainStat": "Select a main stat",
"save": "Save",
"reset": "Reset",
"roll": "Roll",
"step": "Step"
}
}
+177
View File
@@ -0,0 +1,177 @@
{
"TabTitle": {
"title": "Firefly Tools",
"description": "Firefly tools by Kain"
},
"DataPage": {
"skillType": "スキルタイプ",
"skillName": "スキル名",
"character": "キャラクター",
"id": "ID",
"path": "運命",
"rarity": "レアリティ",
"element": "属性",
"technique": "秘術",
"talent": "天賦",
"basic": "通常攻撃",
"skill": "スキル",
"ultimate": "アルティメット",
"servant": "サーバント",
"damage": "ダメージ",
"type": "タイプ",
"warrior": "破壊",
"knight": "保護",
"mage": "博識",
"priest": "豊穣",
"rogue": "狩猟",
"shaman": "調和",
"warlock": "虚無",
"memory": "記憶",
"fire": "火",
"ice": "氷",
"imaginary": "幻影",
"physical": "物理",
"quantum": "量子",
"thunder": "雷",
"wind": "風",
"hp": "HP",
"atk": "攻撃",
"speed": "速度",
"critRate": "会心率",
"critDmg": "会心ダメージ",
"breakEffect": "破甲ダメージ",
"effectRes": "効果抵抗",
"energyRegenerationRate": "エネルギー回復速度",
"effectHitRate": "効果命中率",
"outgoingHealingBoost": "回復増加",
"fireDmgBoost": "火属性ダメージ強化",
"iceDmgBoost": "氷属性ダメージ強化",
"imaginaryDmgBoost": "幻影ダメージ強化",
"physicalDmgBoost": "物理ダメージ強化",
"quantumDmgBoost": "量子ダメージ強化",
"thunderDmgBoost": "雷属性ダメージ強化",
"windDmgBoost": "風属性ダメージ強化",
"pursued": "追加ダメージ",
"true damage": "真のダメージ",
"follow-up": "追撃ダメージ",
"elemental damage": "破甲および超破甲ダメージ",
"dot": "継続ダメージ",
"qte": "QTEスキル",
"level": "レベル",
"relics": "遺物",
"eidolons": "エイドロン",
"lightcones": "ライトコーン",
"loadData": "データ読み込み",
"exportData": "データ出力",
"connectSetting": "接続設定",
"connected": "接続済み",
"unconnected": "未接続",
"psConnection": "PS接続",
"connectionType": "接続タイプ",
"status": "ステータス",
"connectPs": "PS接続",
"other": "その他",
"freeSr": "FreeSR",
"database": "データベース",
"enka": "Enka",
"monsterSetting": "モンスター設定",
"serverUrl": "サーバー URL",
"privateType": "プライベートタイプ",
"local": "ローカル",
"server": "サーバー",
"username": "ユーザー名",
"password": "パスワード",
"placeholderServerUrl": "サーバー URL を入力",
"placeholderUsername": "ユーザー名を入力",
"placeholderPassword": "パスワードを入力",
"connectedSuccess": "PS接続成功",
"connectedFailed": "PS接続失敗",
"syncSuccess": "PSへのデータ同期に成功",
"syncFailed": "PSへのデータ同期に失敗",
"sync": "同期",
"importSetting": "インポート設定",
"profile": "プロファイル",
"default": "デフォルト",
"copyProfiles": "プロファイルをコピー",
"addNewProfile": "プロファイルを追加",
"createNewProfile": "新しいプロファイルを作成",
"editProfile": "プロファイル編集",
"placeholderProfileName": "プロファイル名を入力",
"profileName": "プロファイル名",
"create": "作成",
"update": "更新",
"characterInformation": "キャラクター情報",
"skills": "スキル",
"showcaseCard": "ショーケースカード",
"comingSoon": "近日公開",
"characterName": "キャラクター名",
"placeholderCharacter": "キャラクター名を入力",
"characterSettings": "キャラクター設定",
"levelConfiguration": "レベル構成",
"characterLevel": "キャラクターレベル",
"max": "最大",
"ultimateEnergy": "アルティメットエネルギー",
"currentEnergy": "現在のエネルギー",
"setTo50": "50%に設定",
"battleConfiguration": "バトル構成",
"useTechnique": "秘術を使用",
"techniqueNote": "バトル前に秘術効果を有効化",
"enhancement": "強化",
"enhancementLevel": "強化レベル",
"origin": "由来",
"enhancedNote": "強化レベルが高いほど追加スキル解放",
"lightconeEquipment": "ライトコーン装備",
"lightconeSettings": "ライトコーン設定",
"placeholderLevel": "レベルを入力",
"superimpositionRank": "重ねランク",
"ranksNote": "ランクが高いほど効果強い",
"changeLightcone": "ライトコーン変更",
"removeLightcone": "ライトコーン解除",
"equipLightcone": "ライトコーン装備",
"noLightconeEquipped": "ライトコーン未装備",
"equipLightconeNote": "キャラクター強化のため装備してください",
"filter": "フィルター",
"selectedCharacters": "選択されたキャラクター",
"selectedProfiles": "選択されたプロファイル",
"clearAll": "すべてクリア",
"selectAll": "すべて選択",
"copy": "コピー",
"copied": "コピー済み",
"noAvatarSelected": "キャラクター未選択",
"noAvatarToCopySelected": "コピーするキャラクター未選択",
"pleaseSelectAtLeastOneProfile": "プロファイルを最低1つ選択してください",
"pleaseEnterUid": "UIDを入力してください",
"failedToFetchEnkaData": "Enka のデータ取得に失敗",
"pleaseSelectAtLeastOneCharacter": "キャラクターを最低1つ選択してください",
"noDataToImport": "インポートするデータがありません",
"pleaseSelectAFile": "ファイルを選択してください",
"fileMustBeAValidJsonFile": "有効な JSON ファイルである必要があります",
"importEnkaDataSuccess": "Enka データのインポート成功",
"importFreeSRDataSuccess": "FreeSR データのインポート成功",
"importDatabaseSuccess": "データベースのインポート成功",
"getData": "データ取得",
"import": "インポート",
"freeSRImport": "FreeSR インポート",
"onlySupportFreeSRJsonFile": "FreeSR JSON ファイルのみ対応",
"pickAFile": "ファイル選択",
"lightConeSetting": "ライトコーン設定",
"relicMaker": "遺物製作",
"pleaseSelectAllOptions": "すべてのオプションを選択してください",
"relicSavedSuccessfully": "遺物の保存に成功しました",
"mainSettings": "メイン設定",
"mainStat": "主属性",
"set": "セット",
"pleaseSelectASet": "セットを選択してください",
"effectBonus": "効果ボーナス",
"totalRoll": "総ロール数",
"randomizeStats": "ステータスランダム化",
"randomizeRolls": "ロール回数ランダム化",
"selectASubStat": "サブステータスを選択",
"selectASet": "セットを選択",
"selectAMainStat": "主属性を選択",
"save": "保存",
"reset": "リセット",
"roll": "ロール",
"step": "ステップ"
}
}
+177
View File
@@ -0,0 +1,177 @@
{
"TabTitle": {
"title": "Firefly Tools",
"description": "Firefly tools by Kain"
},
"DataPage": {
"skillType": "스킬 유형",
"skillName": "스킬 이름",
"character": "캐릭터",
"id": "ID",
"path": "운명",
"rarity": "희귀도",
"element": "속성",
"technique": "비기",
"talent": "탈렌트",
"basic": "기본 공격",
"skill": "스킬",
"ultimate": "궁극기",
"servant": "서번트",
"damage": "데미지",
"type": "종류",
"warrior": "파괴",
"knight": "보호",
"mage": "박학",
"priest": "풍요",
"rogue": "사냥",
"shaman": "조화",
"warlock": "허무",
"memory": "기억",
"fire": "화염",
"ice": "얼음",
"imaginary": "환상",
"physical": "물리",
"quantum": "양자",
"thunder": "번개",
"wind": "바람",
"hp": "HP",
"atk": "공격력",
"speed": "속도",
"critRate": "치명타 확률",
"critDmg": "치명타 피해",
"breakEffect": "파괴 피해",
"effectRes": "효과 저항",
"energyRegenerationRate": "에너지 회복속도",
"effectHitRate": "효과 적중률",
"outgoingHealingBoost": "치유 강화",
"fireDmgBoost": "화염 피해 증가",
"iceDmgBoost": "얼음 피해 증가",
"imaginaryDmgBoost": "환상 피해 증가",
"physicalDmgBoost": "물리 피해 증가",
"quantumDmgBoost": "양자 피해 증가",
"thunderDmgBoost": "번개 피해 증가",
"windDmgBoost": "바람 피해 증가",
"pursued": "추가 피해",
"true damage": "진실한 피해",
"follow-up": "후속 피해",
"elemental damage": "파괴 및 초파괴 피해",
"dot": "시간 경과 피해",
"qte": "QTE 스킬",
"level": "레벨",
"relics": "유물",
"eidolons": "에이돌론",
"lightcones": "라이트콘",
"loadData": "데이터 불러오기",
"exportData": "데이터 내보내기",
"connectSetting": "연결 설정",
"connected": "연결됨",
"unconnected": "연결 안됨",
"psConnection": "PS 연결",
"connectionType": "연결 타입",
"status": "상태",
"connectPs": "PS 연결",
"other": "기타",
"freeSr": "FreeSR",
"database": "데이터베이스",
"enka": "Enka",
"monsterSetting": "몬스터 설정",
"serverUrl": "서버 URL",
"privateType": "프라이빗 타입",
"local": "로컬",
"server": "서버",
"username": "사용자명",
"password": "비밀번호",
"placeholderServerUrl": "서버 URL 입력",
"placeholderUsername": "사용자명 입력",
"placeholderPassword": "비밀번호 입력",
"connectedSuccess": "PS에 성공적으로 연결됨",
"connectedFailed": "PS 연결 실패",
"syncSuccess": "PS에 데이터 동기화 성공",
"syncFailed": "PS에 데이터 동기화 실패",
"sync": "동기화",
"importSetting": "설정 가져오기",
"profile": "프로필",
"default": "기본",
"copyProfiles": "프로필 복사",
"addNewProfile": "새 프로필 추가",
"createNewProfile": "새 프로필 생성",
"editProfile": "프로필 편집",
"placeholderProfileName": "프로필 이름 입력",
"profileName": "프로필 이름",
"create": "생성",
"update": "업데이트",
"characterInformation": "캐릭터 정보",
"skills": "스킬",
"showcaseCard": "쇼케이스 카드",
"comingSoon": "곧 출시",
"characterName": "캐릭터 이름",
"placeholderCharacter": "캐릭터 이름 입력",
"characterSettings": "캐릭터 설정",
"levelConfiguration": "레벨 구성",
"characterLevel": "캐릭터 레벨",
"max": "최대",
"ultimateEnergy": "궁극 에너지",
"currentEnergy": "현재 에너지",
"setTo50": "50%로 설정",
"battleConfiguration": "전투 구성",
"useTechnique": "비기 사용",
"techniqueNote": "전투 전 비기 효과 활성화",
"enhancement": "강화",
"enhancementLevel": "강화 레벨",
"origin": "출처",
"enhancedNote": "강화 레벨이 높을수록 추가 효과 해제",
"lightconeEquipment": "라이트콘 장비",
"lightconeSettings": "라이트콘 설정",
"placeholderLevel": "레벨 입력",
"superimpositionRank": "중첩 등급",
"ranksNote": "등급이 높을수록 효과가 강함",
"changeLightcone": "라이트콘 변경",
"removeLightcone": "라이트콘 제거",
"equipLightcone": "라이트콘 장착",
"noLightconeEquipped": "라이트콘 미장착",
"equipLightconeNote": "캐릭터 능력 향상을 위해 장착하세요",
"filter": "필터",
"selectedCharacters": "선택된 캐릭터",
"selectedProfiles": "선택된 프로필",
"clearAll": "전체 지우기",
"selectAll": "전체 선택",
"copy": "복사",
"copied": "복사됨",
"noAvatarSelected": "캐릭터가 선택되지 않음",
"noAvatarToCopySelected": "복사할 캐릭터가 선택되지 않음",
"pleaseSelectAtLeastOneProfile": "프로필을 최소 하나 선택하세요",
"pleaseEnterUid": "UID를 입력해주세요",
"failedToFetchEnkaData": "Enka 데이터 가져오기 실패",
"pleaseSelectAtLeastOneCharacter": "캐릭터를 최소 하나 선택하세요",
"noDataToImport": "가져올 데이터가 없습니다",
"pleaseSelectAFile": "파일을 선택하세요",
"fileMustBeAValidJsonFile": "유효한 JSON 파일이어야 합니다",
"importEnkaDataSuccess": "Enka 데이터 가져오기 성공",
"importFreeSRDataSuccess": "FreeSR 데이터 가져오기 성공",
"importDatabaseSuccess": "데이터베이스 가져오기 성공",
"getData": "데이터 가져오기",
"import": "가져오기",
"freeSRImport": "FreeSR 가져오기",
"onlySupportFreeSRJsonFile": "FreeSR JSON 파일만 지원",
"pickAFile": "파일 선택",
"lightConeSetting": "라이트콘 설정",
"relicMaker": "유물 제작",
"pleaseSelectAllOptions": "모든 옵션을 선택해주세요",
"relicSavedSuccessfully": "유물 저장 성공",
"mainSettings": "메인 설정",
"mainStat": "주 속성",
"set": "세트",
"pleaseSelectASet": "세트 하나를 선택해주세요",
"effectBonus": "효과 보너스",
"totalRoll": "총 횟수",
"randomizeStats": "속성 랜덤화",
"randomizeRolls": "횟수 랜덤화",
"selectASubStat": "부 속성 선택",
"selectASet": "세트 선택",
"selectAMainStat": "주 속성 선택",
"save": "저장",
"reset": "초기화",
"roll": "굴리기",
"step": "단계"
}
}
+177
View File
@@ -0,0 +1,177 @@
{
"TabTitle": {
"title": "Firefly Tools",
"description": "Firefly tools by Kain"
},
"DataPage": {
"skillType": "Loại kỹ năng",
"skillName": "Tên kỹ năng",
"character": "Nhân vật",
"id": "ID",
"path": "Vận mệnh",
"rarity": "Độ hiếm",
"element": "Nguyên tố",
"technique": "Bí kỹ",
"talent": "Thiên phú",
"basic": "Đánh thường",
"skill": "Kỹ năng",
"ultimate": "Tuyệt kỹ",
"servant": "Phụ trợ",
"damage": "Sát thương",
"type": "Loại",
"warrior": "Hủy Diệt",
"knight": "Bảo Hộ",
"mage": "Tri Thức",
"priest": "Trù phú",
"rogue": "Săn Bắn",
"shaman": "Hòa Hợp",
"warlock": "Hư Vô",
"memory": "Ký Ức",
"fire": "Hỏa",
"ice": "Băng",
"imaginary": "Ảo Ảnh",
"physical": "Vật Lý",
"quantum": "Lượng Tử",
"thunder": "Lôi",
"wind": "Phong",
"hp": "HP",
"atk": "Tấn công",
"speed": "Tốc độ",
"critRate": "Tỷ lệ bạo",
"critDmg": "ST bạo",
"breakEffect": "sát thương kích phá",
"effectRes": "Kháng hiệu ứng",
"energyRegenerationRate": "Tốc độ hồi năng lượng",
"effectHitRate": "Tỷ lệ trúng hiệu ứng",
"outgoingHealingBoost": "Tăng hồi phục",
"fireDmgBoost": "Tăng sát thương Hỏa",
"iceDmgBoost": "Tăng sát thương Băng",
"imaginaryDmgBoost": "Tăng sát thương Ảo Ảnh",
"physicalDmgBoost": "Tăng sát thương Vật Lý",
"quantumDmgBoost": "Tăng sát thương Lượng Tử",
"thunderDmgBoost": "Tăng sát thương Lôi",
"windDmgBoost": "Tăng sát thương Phong",
"pursued": "Sát thương thêm",
"true damage": "Sát thương chuẩn",
"follow-up": "Sát thương phản kích",
"elemental damage": "Sát thương kích phá và siêu kích phá",
"dot": "Sát thương theo thời gian",
"qte": "Kỹ năng QTE",
"level": "Cấp độ",
"relics": "Thánh di vật",
"eidolons": "Tinh hồn",
"lightcones": "Nón ánh sáng",
"loadData": "Tải dữ liệu",
"exportData": "Xuất dữ liệu",
"connectSetting": "Cài đặt kết nối",
"connected": "Đã kết nối",
"unconnected": "Chưa kết nối",
"psConnection": "Kết nối PS",
"connectionType": "Loại kết nối",
"status": "Trạng thái",
"connectPs": "Kết nối PS",
"other": "Khác",
"freeSr": "FreeSR",
"database": "Database",
"enka": "Enka",
"monsterSetting": "Cài đặt quái",
"serverUrl": "Địa chỉ server",
"privateType": "Loại riêng tư",
"local": "Cục bộ",
"server": "Máy chủ",
"username": "Tên người dùng",
"password": "Mật khẩu",
"placeholderServerUrl": "Nhập địa chỉ server",
"placeholderUsername": "Nhập tên người dùng",
"placeholderPassword": "Nhập mật khẩu",
"connectedSuccess": "Kết nối PS thành công",
"connectedFailed": "Kết nối PS thất bại",
"syncSuccess": "Đồng bộ dữ liệu với PS thành công",
"syncFailed": "Đồng bộ dữ liệu với PS thất bại",
"sync": "Đồng bộ",
"importSetting": "Cài đặt nhập",
"profile": "Hồ sơ",
"default": "Mặc định",
"copyProfiles": "Sao chép hồ sơ",
"addNewProfile": "Thêm hồ sơ mới",
"createNewProfile": "Tạo hồ sơ mới",
"editProfile": "Chỉnh sửa hồ sơ",
"placeholderProfileName": "Nhập tên hồ sơ",
"profileName": "Tên hồ sơ",
"create": "Tạo",
"update": "Cập nhật",
"characterInformation": "Thông tin nhân vật",
"skills": "Kỹ năng",
"showcaseCard": "Thẻ trưng bày",
"comingSoon": "Sắp ra mắt",
"characterName": "Tên nhân vật",
"placeholderCharacter": "Nhập tên nhân vật",
"characterSettings": "Cài đặt nhân vật",
"levelConfiguration": "Cấu hình cấp độ",
"characterLevel": "Cấp độ nhân vật",
"max": "Tối đa",
"ultimateEnergy": "Năng lượng tuyệt kỹ",
"currentEnergy": "Năng lượng hiện tại",
"setTo50": "Đặt thành 50%",
"battleConfiguration": "Cấu hình trận đấu",
"useTechnique": "Dùng chiến pháp",
"techniqueNote": "Bật hiệu ứng chiến pháp trước trận",
"enhancement": "Cường hóa",
"enhancementLevel": "Cấp độ cường hóa",
"origin": "Nguồn gốc",
"enhancedNote": "Cường hóa cao mở thêm kỹ năng",
"lightconeEquipment": "Trang bị nón ánh sáng",
"lightconeSettings": "Cài đặt nón ánh sáng",
"placeholderLevel": "Nhập cấp độ",
"superimpositionRank": "Bậc chồng kỹ năng",
"ranksNote": "Bậc càng cao hiệu ứng càng mạnh",
"changeLightcone": "Thay đổi nón ánh sáng",
"removeLightcone": "Gỡ nón ánh sáng",
"equipLightcone": "Trang bị nón ánh sáng",
"noLightconeEquipped": "Chưa trang bị nón ánh sáng",
"equipLightconeNote": "Trang bị nón để tăng sức mạnh cho nhân vật",
"filter": "Lọc",
"selectedCharacters": "Nhân vật đã chọn",
"selectedProfiles": "Hồ sơ đã chọn",
"clearAll": "Xóa tất cả",
"selectAll": "Chọn tất cả",
"copy": "Sao chép",
"copied": "Đã sao chép",
"noAvatarSelected": "Chưa chọn nhân vật",
"noAvatarToCopySelected": "Chưa chọn nhân vật để sao chép",
"pleaseSelectAtLeastOneProfile": "Vui lòng chọn ít nhất một hồ sơ",
"pleaseEnterUid": "Vui lòng nhập UID",
"failedToFetchEnkaData": "Lấy dữ liệu Enka thất bại",
"pleaseSelectAtLeastOneCharacter": "Vui lòng chọn ít nhất một nhân vật",
"noDataToImport": "Không có dữ liệu để nhập",
"pleaseSelectAFile": "Vui lòng chọn một tệp",
"fileMustBeAValidJsonFile": "Tệp phải là tệp JSON hợp lệ",
"importEnkaDataSuccess": "Nhập dữ liệu Enka thành công",
"importFreeSRDataSuccess": "Nhập dữ liệu FreeSR thành công",
"importDatabaseSuccess": "Nhập cơ sở dữ liệu thành công",
"getData": "Lấy dữ liệu",
"import": "Nhập",
"freeSRImport": "Nhập FreeSR",
"onlySupportFreeSRJsonFile": "Chỉ hỗ trợ tệp JSON từ FreeSR",
"pickAFile": "Chọn tệp",
"lightConeSetting": "Cài đặt Nón Ánh Sáng",
"relicMaker": "Trình tạo Thánh Di Vật",
"pleaseSelectAllOptions": "Vui lòng chọn tất cả tùy chọn",
"relicSavedSuccessfully": "Lưu thánh di vật thành công",
"mainSettings": "Cài đặt chính",
"mainStat": "Chỉ số chính",
"set": "Bộ",
"pleaseSelectASet": "Vui lòng chọn một bộ",
"effectBonus": "Hiệu ứng cộng thêm",
"totalRoll": "Tổng lượt lăn",
"randomizeStats": "Ngẫu nhiên chỉ số",
"randomizeRolls": "Ngẫu nhiên lượt lăn",
"selectASubStat": "Chọn chỉ số phụ",
"selectASet": "Chọn một bộ",
"selectAMainStat": "Chọn chỉ số chính",
"save": "Lưu",
"reset": "Đặt lại",
"roll": "Sô lượt",
"step": "Bước nhảy"
}
}
+177
View File
@@ -0,0 +1,177 @@
{
"TabTitle": {
"title": "Firefly Tools",
"description": "Firefly tools by Kain"
},
"DataPage": {
"skillType": "技能类型",
"skillName": "技能名称",
"character": "角色",
"id": "ID",
"path": "命运",
"rarity": "稀有度",
"element": "元素",
"technique": "秘术",
"talent": "天赋",
"basic": "普通攻击",
"skill": "技能",
"ultimate": "终极技",
"servant": "仆从",
"damage": "伤害",
"type": "类型",
"warrior": "毁灭",
"knight": "守护",
"mage": "博学",
"priest": "丰饶",
"rogue": "狩猎",
"shaman": "和谐",
"warlock": "虚无",
"memory": "回忆",
"fire": "火",
"ice": "冰",
"imaginary": "幻象",
"physical": "物理",
"quantum": "量子",
"thunder": "雷",
"wind": "风",
"hp": "生命值",
"atk": "攻击",
"speed": "速度",
"critRate": "暴击率",
"critDmg": "暴击伤害",
"breakEffect": "破甲伤害",
"effectRes": "效果抗性",
"energyRegenerationRate": "能量恢复速率",
"effectHitRate": "效果命中率",
"outgoingHealingBoost": "治疗增强",
"fireDmgBoost": "火元素伤害增强",
"iceDmgBoost": "冰元素伤害增强",
"imaginaryDmgBoost": "幻象伤害增强",
"physicalDmgBoost": "物理伤害增强",
"quantumDmgBoost": "量子伤害增强",
"thunderDmgBoost": "雷元素伤害增强",
"windDmgBoost": "风元素伤害增强",
"pursued": "附加伤害",
"true damage": "真实伤害",
"follow-up": "后续伤害",
"elemental damage": "破甲与超破甲伤害",
"dot": "持续伤害",
"qte": "QTE 技能",
"level": "等级",
"relics": "遗器",
"eidolons": "虚数形态",
"lightcones": "光锥",
"loadData": "加载数据",
"exportData": "导出数据",
"connectSetting": "连接设置",
"connected": "已连接",
"unconnected": "未连接",
"psConnection": "PS 连接",
"connectionType": "连接类型",
"status": "状态",
"connectPs": "连接 PS",
"other": "其他",
"freeSr": "FreeSR",
"database": "数据库",
"enka": "Enka",
"monsterSetting": "怪物设置",
"serverUrl": "服务器 URL",
"privateType": "私有类型",
"local": "本地",
"server": "服务器",
"username": "用户名",
"password": "密码",
"placeholderServerUrl": "输入服务器 URL",
"placeholderUsername": "输入用户名",
"placeholderPassword": "输入密码",
"connectedSuccess": "成功连接 PS",
"connectedFailed": "连接 PS 失败",
"syncSuccess": "已成功与 PS 同步数据",
"syncFailed": "与 PS 同步数据失败",
"sync": "同步",
"importSetting": "导入设置",
"profile": "配置档",
"default": "默认",
"copyProfiles": "复制配置档",
"addNewProfile": "添加新配置档",
"createNewProfile": "创建新配置档",
"editProfile": "编辑配置档",
"placeholderProfileName": "输入配置档名称",
"profileName": "配置档名称",
"create": "创建",
"update": "更新",
"characterInformation": "角色信息",
"skills": "技能",
"showcaseCard": "展示卡",
"comingSoon": "敬请期待",
"characterName": "角色名称",
"placeholderCharacter": "输入角色名称",
"characterSettings": "角色设置",
"levelConfiguration": "等级配置",
"characterLevel": "角色等级",
"max": "最大",
"ultimateEnergy": "终极能量",
"currentEnergy": "当前能量",
"setTo50": "设为50%",
"battleConfiguration": "战斗配置",
"useTechnique": "使用秘术",
"techniqueNote": "启用战前秘术效果",
"enhancement": "强化",
"enhancementLevel": "强化等级",
"origin": "来源",
"enhancedNote": "强化更高可解锁额外技能",
"lightconeEquipment": "光锥装备",
"lightconeSettings": "光锥设置",
"placeholderLevel": "输入等级",
"superimpositionRank": "叠加等级",
"ranksNote": "更高等级提供更强效果",
"changeLightcone": "更换光锥",
"removeLightcone": "移除光锥",
"equipLightcone": "装备光锥",
"noLightconeEquipped": "未装备光锥",
"equipLightconeNote": "装备光锥以增强角色能力",
"filter": "筛选",
"selectedCharacters": "已选角色",
"selectedProfiles": "已选配置档",
"clearAll": "清除全部",
"selectAll": "全选",
"copy": "复制",
"copied": "已复制",
"noAvatarSelected": "未选择角色",
"noAvatarToCopySelected": "未选择要复制的角色",
"pleaseSelectAtLeastOneProfile": "请至少选择一个配置档",
"pleaseEnterUid": "请输入 UID",
"failedToFetchEnkaData": "获取 Enka 数据失败",
"pleaseSelectAtLeastOneCharacter": "请至少选择一个角色",
"noDataToImport": "无可导入数据",
"pleaseSelectAFile": "请选择一个文件",
"fileMustBeAValidJsonFile": "文件必须为有效 JSON",
"importEnkaDataSuccess": "导入 Enka 数据成功",
"importFreeSRDataSuccess": "导入 FreeSR 数据成功",
"importDatabaseSuccess": "导入数据库成功",
"getData": "获取数据",
"import": "导入",
"freeSRImport": "导入 FreeSR",
"onlySupportFreeSRJsonFile": "仅支持 FreeSR JSON 文件",
"pickAFile": "选择文件",
"lightConeSetting": "光锥设置",
"relicMaker": "遗器制作",
"pleaseSelectAllOptions": "请选择所有选项",
"relicSavedSuccessfully": "遗器已成功保存",
"mainSettings": "主设置",
"mainStat": "主属性",
"set": "套装",
"pleaseSelectASet": "请选择一个套装",
"effectBonus": "效果加成",
"totalRoll": "总次数",
"randomizeStats": "随机属性",
"randomizeRolls": "随机次数",
"selectASubStat": "选择副属性",
"selectASet": "选择套装",
"selectAMainStat": "选择主属性",
"save": "保存",
"reset": "重置",
"roll": "滚动",
"step": "步数"
}
}
+37 -2
View File
@@ -1,7 +1,42 @@
import createNextIntlPlugin from "next-intl/plugin";
import type { NextConfig } from "next";
const withNextIntl = createNextIntlPlugin()
const nextConfig: NextConfig = {
/* config options here */
reactStrictMode: false,
output: 'standalone',
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'localhost',
pathname: '**',
},
{
protocol: 'http',
hostname: 'localhost',
pathname: '**',
},
{
protocol: 'https',
hostname: 'api.hakush.in',
pathname: '**',
},
],
},
eslint: {
ignoreDuringBuilds: true,
},
async rewrites() {
return [
{
source: '/api/character/:id',
destination: 'https://api.hakush.in/hsr/data/en/character/:id.json',
},
];
},
};
export default nextConfig;
export default withNextIntl(nextConfig);
+18 -6
View File
@@ -9,19 +9,31 @@
"lint": "next lint"
},
"dependencies": {
"axios": "^1.10.0",
"framer-motion": "^12.12.1",
"next": "15.3.2",
"next-intl": "^4.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "15.3.2"
"react-select": "^5.10.1",
"react-toastify": "^11.0.5",
"socket.io-client": "^4.8.1",
"zod": "^3.25.67",
"zustand": "^5.0.5"
},
"devDependencies": {
"typescript": "^5",
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/jest": "^29.5.14",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"daisyui": "^5.0.27",
"eslint": "^9",
"eslint-config-next": "15.3.2",
"@eslint/eslintrc": "^3"
"eslint-config-next": "15.3.1",
"tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4",
"ts-to-zod": "^3.15.0",
"typescript": "^5"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+13
View File
@@ -0,0 +1,13 @@
@echo off
echo [INFO] Create folder src\zod if not exist...
if not exist src\zod (
mkdir src\zod
)
echo [INFO] Start convert file .ts from src\types to Zod schemas...
for %%f in (src\types\*.ts) do (
echo [ZOD] Chuyển %%f -> src\zod\%%~nf.zod.ts
npx ts-to-zod src\types\%%~nxf src\zod\%%~nf.zod.ts
)
echo [DONE] ✅ Done
@@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from 'next/server'
import { loadCharacters } from '@/lib/characterLoader'
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ id: string, locale: string }> }
) {
const { id, locale } = await params
const characters = await loadCharacters([id], locale)
const char = characters[id]
if (!char) {
return NextResponse.json({ error: 'Character not found' }, { status: 404 })
}
return NextResponse.json(char)
}
+20
View File
@@ -0,0 +1,20 @@
import { loadCharacters } from "@/lib/characterLoader";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
try {
const body = await request.json();
const charIds = body.charIds as string[];
const { locale } = await params;
if (!Array.isArray(charIds) || charIds.some(id => typeof id !== 'string')) {
return NextResponse.json({ error: 'Invalid charIds' }, { status: 400 });
}
const characters = await loadCharacters(charIds, locale);
return NextResponse.json(characters);
} catch {
return NextResponse.json({ error: 'Failed to load characters' }, { status: 500 });
}
}
@@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from 'next/server'
import { loadLightcones } from '@/lib/lighconeLoader'
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ id: string, locale: string }> }
) {
const { id, locale } = await params
const lightcones = await loadLightcones([id], locale)
const lightcone = lightcones[id]
if (!lightcone) {
return NextResponse.json({ error: 'Lightcone not found' }, { status: 404 })
}
return NextResponse.json(lightcone)
}
+20
View File
@@ -0,0 +1,20 @@
import { loadLightcones } from "@/lib/lighconeLoader";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
try {
const body = await request.json();
const lightconeIds = body.lightconeIds as string[];
const { locale } = await params;
if (!Array.isArray(lightconeIds) || lightconeIds.some(id => typeof id !== 'string')) {
return NextResponse.json({ error: 'Invalid lightconeIds' }, { status: 400 });
}
const lightcones = await loadLightcones(lightconeIds, locale);
return NextResponse.json(lightcones);
} catch {
return NextResponse.json({ error: 'Failed to load lightcones' }, { status: 500 });
}
}
+18
View File
@@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from 'next/server'
import { loadRelics } from '@/lib/relicLoader'
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ id: string, locale: string }> }
) {
const { id, locale } = await params
const relics = await loadRelics([id], locale)
const relic = relics[id]
if (!relic) {
return NextResponse.json({ error: 'Relic not found' }, { status: 404 })
}
return NextResponse.json(relic)
}
+20
View File
@@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import { loadRelics } from "@/lib/relicLoader";
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
try {
const body = await request.json();
const relicIds = body.relicIds as string[];
const { locale } = await params;
if (!Array.isArray(relicIds) || relicIds.some(id => typeof id !== 'string')) {
return NextResponse.json({ error: 'Invalid relicIds' }, { status: 400 });
}
const relics = await loadRelics(relicIds, locale);
return NextResponse.json(relics);
} catch {
return NextResponse.json({ error: 'Failed to load relics' }, { status: 500 });
}
}
+11
View File
@@ -0,0 +1,11 @@
import { loadConfigMaze } from "@/lib/configMazeLoader";
import { NextResponse } from "next/server";
export async function GET() {
try {
const configMaze = await loadConfigMaze();
return NextResponse.json(configMaze);
} catch {
return NextResponse.json({ error: 'Failed to load config maze' }, { status: 500 });
}
}
+11
View File
@@ -0,0 +1,11 @@
import { loadMainAffix } from "@/lib/affixLoader";
import { NextResponse } from "next/server";
export async function GET() {
try {
const mainAffix = await loadMainAffix();
return NextResponse.json(mainAffix);
} catch {
return NextResponse.json({ error: 'Failed to load main affix' }, { status: 500 });
}
}
+73
View File
@@ -0,0 +1,73 @@
import { NextRequest, NextResponse } from 'next/server'
import axios from 'axios'
import net from 'net'
function isPrivateHost(hostname: string): boolean {
if (
hostname === 'localhost' ||
hostname.startsWith('127.') ||
hostname.startsWith('10.') ||
hostname.startsWith('192.168.') ||
/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)
) {
return true
}
if (net.isIP(hostname)) {
return true
}
return false
}
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { serverUrl, method, ...payload } = body
if (!serverUrl) {
return NextResponse.json({ error: 'Missing serverUrl' }, { status: 400 })
}
if (!method) {
return NextResponse.json({ error: 'Missing method' }, { status: 400 })
}
let url = serverUrl.trim()
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = `http://${url}`
}
const parsed = new URL(url)
if (isPrivateHost(parsed.hostname)) {
return NextResponse.json(
{ error: `Connection to private/internal address (${parsed.hostname}) is not allowed` },
{ status: 403 }
)
}
let response
switch (method.toUpperCase()) {
case 'GET':
const queryString = new URLSearchParams(payload as any).toString()
const fullUrl = queryString ? `${url}?${queryString}` : url
response = await axios.get(fullUrl)
break
case 'POST':
response = await axios.post(url, payload)
break
case 'PUT':
response = await axios.put(url, payload)
break
case 'DELETE':
response = await axios.delete(url, { data: payload })
break
default:
return NextResponse.json({ error: `Unsupported method: ${method}` }, { status: 405 })
}
return NextResponse.json(response.data)
} catch (err: any) {
return NextResponse.json({ error: err.message || 'Proxy failed' }, { status: 500 })
}
}
+11
View File
@@ -0,0 +1,11 @@
import { loadSubAffix } from "@/lib/affixLoader";
import { NextResponse } from "next/server";
export async function GET() {
try {
const subAffix = await loadSubAffix();
return NextResponse.json(subAffix);
} catch {
return NextResponse.json({ error: 'Failed to load sub affix' }, { status: 500 });
}
}
+12
View File
@@ -0,0 +1,12 @@
"use client"
import Image from "next/image";
import EidolonsInfo from "@/components/eidolonsInfo";
import { useRouter } from 'next/navigation'
export default function EidolonsInfoPage() {
const router = useRouter()
return (
<div className="w-full">
<EidolonsInfo/>
</div>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 195 KiB

+55 -18
View File
@@ -1,26 +1,63 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
@plugin "daisyui" {
themes: winter --default, night --prefersdark, cupcake, coffee;
}
@plugin 'tailwind-scrollbar' {
nocompatible: true;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: #9ca3af;
border-radius: 0;
}
::-webkit-scrollbar-button {
display: none;
}
@tailwind utilities;
@layer components {
/* .my-react-select-container {
} */
.my-react-select-container .my-react-select__control {
@apply bg-white dark:bg-neutral-700 border-2 border-neutral-300 dark:border-neutral-700 hover:border-neutral-400 dark:hover:border-neutral-500;
}
.my-react-select-container .my-react-select__control--is-focused {
@apply border-neutral-500 hover:border-neutral-500 dark:border-neutral-400 dark:hover:border-neutral-400 shadow-none;
}
.my-react-select-container .my-react-select__menu {
@apply bg-neutral-100 dark:bg-neutral-700 border-2 border-neutral-300 dark:border-neutral-600;
}
.my-react-select-container .my-react-select__option {
@apply text-neutral-600 dark:text-neutral-200 bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-700 dark:hover:bg-neutral-800;
}
/* .my-react-select-container .my-react-select__option--is-focused {
@apply bg-neutral-200 dark:bg-neutral-800;
} */
.my-react-select-container .my-react-select__indicator-separator {
@apply bg-neutral-400;
}
.my-react-select-container .my-react-select__menu {
z-index: 999;
}
.my-react-select-container .my-react-select__input-container,
.my-react-select-container .my-react-select__placeholder,
.my-react-select-container .my-react-select__single-value {
@apply text-neutral-600 dark:text-neutral-200;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

+37 -5
View File
@@ -1,6 +1,14 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Header from "../components/header";
import { ClientThemeWrapper, ThemeProvider } from "@/components/themeController";
import Footer from "@/components/footer";
import { NextIntlClientProvider } from "next-intl";
import { getLocale, getMessages } from "next-intl/server";
import { ToastContainer } from 'react-toastify';
import AvatarBar from "@/components/avatarBar";
import ActionBar from "@/components/actionBar";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -13,21 +21,45 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Firefly SrTools",
description: "SrTools by Kain",
};
export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const messages = await getMessages();
const locale = await getLocale()
return (
<html lang="en">
<html lang={locale} suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
suppressHydrationWarning
>
{children}
<NextIntlClientProvider messages={messages}>
<ThemeProvider>
<ClientThemeWrapper>
<div className="min-h-screen w-full">
<Header />
<div className="grid grid-cols-12 w-full">
<div className="col-span-3 sticky top-0 self-start h-fit">
<AvatarBar />
</div>
<div className="col-span-9">
<ActionBar />
{children}
</div>
</div>
<Footer />
</div>
</ClientThemeWrapper>
</ThemeProvider>
</NextIntlClientProvider>
<ToastContainer />
</body>
</html>
);
+10 -100
View File
@@ -1,103 +1,13 @@
import Image from "next/image";
"use client"
import AvatarInfo from "@/components/avatarInfo";
import { useRouter } from "next/navigation";
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
);
const router = useRouter()
return (
<div className="w-full">
<AvatarInfo></AvatarInfo>
</div>
);
}
+13
View File
@@ -0,0 +1,13 @@
"use client"
import Image from "next/image";
import RelicsInfo from "@/components/relicsInfo";
import { useRouter } from 'next/navigation'
export default function RelicsInfoPage() {
const router = useRouter()
return (
<div className="w-full">
<RelicsInfo></RelicsInfo>
</div>
);
}
+372
View File
@@ -0,0 +1,372 @@
"use client";
import useListAvatarStore from "@/stores/avatarStore";
import { useTranslations } from "next-intl";
import Image from "next/image";
import { useRouter } from 'next/navigation'
import ParseText from "../parseText";
import useLocaleStore from "@/stores/localeStore";
import { getNameChar } from "@/helper/getName";
import { useEffect, useMemo, useState } from "react";
import { motion } from "framer-motion";
import useModelStore from "@/stores/modelStore";
import useUserDataStore from "@/stores/userDataStore";
import { RelicStore } from "@/types";
import { toast } from "react-toastify";
import useGlobalStore from "@/stores/globalStore";
import { connectToPS, syncDataToPS } from "@/helper";
import CopyImport from "../importBar/copy";
import useCopyProfileStore from "@/stores/copyProfile";
export default function ActionBar() {
const router = useRouter()
const { avatarSelected, listRawAvatar } = useListAvatarStore()
const { setListCopyAvatar } = useCopyProfileStore()
const transI18n = useTranslations("DataPage")
const { locale, setLocale } = useLocaleStore()
const { isOpenCreateProfile, setIsOpenCreateProfile, isOpenCopy, setIsOpenCopy } = useModelStore()
const { avatars, setAvatar } = useUserDataStore()
const [profileName, setProfileName] = useState("");
const [formState, setFormState] = useState("EDIT");
const [profileEdit, setProfileEdit] = useState(-1);
const { isConnectPS, setIsConnectPS } = useGlobalStore()
const profileCurrent = useMemo(() => {
if (!avatarSelected) return null;
const avatar = avatars[avatarSelected.id];
return avatar?.profileList[avatar.profileSelect] || null;
}, [avatarSelected, avatars]);
const listProfile = useMemo(() => {
if (!avatarSelected) return [];
const avatar = avatars[avatarSelected.id];
return avatar?.profileList || [];
}, [avatarSelected, avatars]);
const handleUpdateProfile = () => {
if (!profileName.trim()) return;
if (formState === "CREATE" && avatarSelected && avatars[avatarSelected.id]) {
const newListProfile = [...listProfile]
const newProfile = {
profile_name: profileName,
lightcone: null,
relics: {} as Record<string, RelicStore>
}
newListProfile.push(newProfile)
setAvatar({ ...avatars[avatarSelected.id], profileList: newListProfile, profileSelect: newListProfile.length - 1 })
toast.success("Profile created successfully")
} else if (formState === "EDIT" && profileCurrent && avatarSelected && profileEdit !== -1) {
const newListProfile = [...listProfile]
newListProfile[profileEdit].profile_name = profileName;
setAvatar({ ...avatars[avatarSelected.id], profileList: newListProfile })
toast.success("Profile updated successfully")
}
handleCloseModal("update_profile_modal");
};
const handleShow = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
modal.showModal();
}
};
// Close modal handler
const handleCloseModal = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
modal.close();
}
};
// Handle ESC key to close modal
useEffect(() => {
if (!isOpenCreateProfile) {
handleCloseModal("update_profile_modal");
return;
}
if (!isOpenCopy) {
handleCloseModal("copy_profile_modal");
return;
}
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpenCreateProfile) {
handleCloseModal("update_profile_modal");
}
if (event.key === 'Escape' && isOpenCopy) {
handleCloseModal("copy_profile_modal");
}
};
window.addEventListener('keydown', handleEscKey);
return () => window.removeEventListener('keydown', handleEscKey);
}, [isOpenCreateProfile]);
const actionMove = (path: string) => {
router.push(`/${path}`)
}
const handleProfileSelect = (profileId: number) => {
if (!avatarSelected) return;
if (avatars[avatarSelected.id].profileSelect === profileId) return;
setAvatar({ ...avatars[avatarSelected.id], profileSelect: profileId })
toast.success(`Profile changed to Profile: ${avatars[avatarSelected.id].profileList[profileId].profile_name}`)
}
const handleDeleteProfile = (profileId: number, e: React.MouseEvent) => {
e.stopPropagation()
if (!avatarSelected || profileId == 0) return;
if (window.confirm(`Are you sure you want to delete profile: ${avatars[avatarSelected.id].profileList[profileId].profile_name}?`)) {
const newListProfile = [...listProfile]
newListProfile.splice(profileId, 1)
setAvatar({ ...avatars[avatarSelected.id], profileList: newListProfile, profileSelect: profileId - 1 })
toast.success(`Profile ${avatars[avatarSelected.id].profileList[profileId].profile_name} deleted successfully`)
}
}
const handleConnectOrSyncPS = async () => {
if (isConnectPS) {
const res = await syncDataToPS()
if (res.success) {
toast.success(transI18n("syncSuccess"))
} else {
toast.error(`${transI18n("syncFailed")}: ${res.message}`)
setIsConnectPS(false)
}
} else {
const res = await connectToPS()
if (res.success) {
toast.success(transI18n("connectedSuccess"))
setIsConnectPS(true)
} else {
toast.error(`${transI18n("connectedFailed")}: ${res.message}`)
setIsConnectPS(false)
}
}
}
return (
<div className="w-full mb-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-center justify-items-center">
<div className="grid grid-rows-2 lg:grid-rows-1 items-center w-full">
<div className="flex justify-between gap-10 w-full">
<div className="flex items-center p-1 h-full lg:p-2 opacity-80 lg:hover:opacity-100 cursor-pointer text-base md:text-lg lg:text-xl">
{avatarSelected && (
<>
<Image
src={"https://api.hakush.in/hsr/UI/element/" + avatarSelected.damageType.toLowerCase() + ".webp"}
alt={'fire'}
className="h-[40px] w-[40px] object-contain"
width={100}
height={100}
/>
<div className="flex items-center justify-center h-full w-full">
<p className="text-center font-bold text-xl">
{transI18n(avatarSelected.baseType.toLowerCase())}
</p>
<div className="text-center font-bold text-xl">{" / "}</div>
<ParseText
locale={locale}
text={getNameChar(locale, avatarSelected).toWellFormed()}
className={"font-bold text-xl"}
/>
{avatarSelected?.id && (
<div className="text-center italic text-sm ml-2"> {`(${avatarSelected.id})`}</div>
)}
</div>
</>
)}
</div>
</div>
<div className="flex items-center gap-2 ml-4 w-full">
<span className="text-base opacity-70 font-bold w-16">{transI18n("profile")}:</span>
<div className="dropdown dropdown-end w-full">
<div
tabIndex={0}
role="button"
className="btn btn-warning border-info btn-soft gap-1 min-w-0"
>
<span className="truncate max-w-24 font-bold">
{profileCurrent?.profile_name}
</span>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
<ul className="dropdown-content z-10 menu p-2 shadow bg-base-100 rounded-box w-full mt-1 border border-base-300 max-h-60 overflow-y-auto">
{listProfile.map((profile, index) => (
<li key={index} className="grid grid-cols-12">
<button
className={`col-span-8 btn btn-ghost`}
onClick={(e) => handleProfileSelect(index)}
>
<span className="flex-1 truncate text-left">
{profile.profile_name}
{index === 0 && (
<span className="text-xs text-base-content/60 ml-1">({transI18n("default")})</span>
)}
</span>
</button>
{index !== 0 && (
<>
<button
className="col-span-2 flex items-center justify-center text-lg text-warning hover:bg-warning/20 z-20 w-full h-full"
onClick={() => {
setFormState("EDIT");
setProfileName(profile.profile_name)
setProfileEdit(index)
setIsOpenCreateProfile(true)
handleShow("update_profile_modal")
}}
title="Edit Profile"
>
<svg className="w-4 h-4 " fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536M4 20h4l10.293-10.293a1 1 0 000-1.414l-2.586-2.586a1 1 0 00-1.414 0L4 16v4z" />
</svg>
</button>
<button
className="col-span-2 flex items-center justify-center text-error hover:bg-error/20 z-20 w-full h-full text-lg"
onClick={(e) => {
handleDeleteProfile(index, e)
}}
title="Delete Profile"
>
<svg className="w-4 h-4 " fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6M9 7V4h6v3m5 0H4" />
</svg>
</button>
</>
)}
</li>
))}
<li className="border-t border-base-300 mt-2 pt-2 z-10">
<button
onClick={() => {
setIsOpenCopy(true)
setListCopyAvatar(listRawAvatar)
handleShow("copy_profile_modal")
}}
className="btn btn-ghost flex justify-start px-3 py-2 h-full w-full hover:bg-base-200 cursor-pointer text-primary z-20"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
{transI18n("copyProfiles")}
</button>
<button
className="btn btn-ghost flex justify-start px-3 py-2 h-full w-full hover:bg-base-200 cursor-pointer text-primary z-20"
onClick={() => {
setIsOpenCreateProfile(true)
setFormState("CREATE");
setProfileName("")
handleShow("update_profile_modal")
}}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
{transI18n("addNewProfile")}
</button>
</li>
</ul>
</div>
</div>
</div>
<div className="flex flex-col gap-2 w-full">
{/* Action Buttons */}
<div className="grid grid-cols-2 xl:grid-cols-3 gap-2 w-full">
<button className="btn btn-success btn-sm" onClick={() => actionMove('')}>{transI18n("characterInformation")}</button>
<button className="btn btn-success btn-sm" onClick={() => actionMove('relics-info')}>{transI18n("relics")}</button>
<button className="btn btn-success btn-sm" onClick={() => actionMove('eidolons-info')}>{transI18n("eidolons")}</button>
<button disabled className="btn btn-success btn-sm cursor-not-allowed italic">{transI18n("skills")} ({transI18n("comingSoon")})</button>
<button disabled className="btn btn-success btn-sm cursor-not-allowed italic">{transI18n("showcaseCard")} {transI18n("comingSoon")}</button>
<button onClick={handleConnectOrSyncPS} className="btn btn-primary btn-sm"> {isConnectPS ? transI18n("sync") : transI18n("connectPs")}</button>
</div>
</div>
<dialog id="update_profile_modal" className="modal sm:modal-middle backdrop-blur-sm">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10">
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
onClick={() => {
setIsOpenCreateProfile(false)
handleCloseModal("update_profile_modal")
}}
>
</motion.button>
</div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
{formState === "CREATE" ? transI18n("createNewProfile") : transI18n("editProfile")}
</h3>
</div>
<div className="px-6 space-y-4">
<div className="form-control">
<label className="label">
<span className="label-text text-primary font-semibold text-lg">{transI18n("profileName")}</span>
</label>
<input type="text" placeholder={transI18n("placeholderProfileName")} className="input input-warning mt-1 w-full"
value={profileName}
onChange={(e) => setProfileName(e.target.value)}
/>
</div>
<div className="modal-action">
<button className="btn btn-success btn-sm sm:btn-md" onClick={handleUpdateProfile}>
{formState === "CREATE" ? transI18n("create") : transI18n("update")}
</button>
</div>
</div>
</div>
</dialog>
<dialog id="copy_profile_modal" className="modal">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10">
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
onClick={() => {
setIsOpenCopy(false)
handleCloseModal("copy_profile_modal")
}}
>
</motion.button>
</div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
{transI18n("copyProfiles").toUpperCase()}
</h3>
</div>
<CopyImport />
</div>
</dialog>
</div>
</div>
);
}
+164
View File
@@ -0,0 +1,164 @@
"use client"
import { getCharacterListApi, fetchCharactersByIdsNative, getConfigMazeApi, getLightconeListApi, fetchLightconesByIdsNative, fetchRelicsByIdsNative, getRelicSetListApi, getMainAffixApi, getSubAffixApi } from "@/lib/api"
import Image from "next/image"
import { useEffect, useState } from "react"
import CharacterCard from "../card/characterCard"
import useLocaleStore from "@/stores/localeStore"
import { listCurrentLanguageApi } from "@/lib/constant"
import useAvatarStore from "@/stores/avatarStore"
import useUserDataStore from "@/stores/userDataStore"
import { converterToAvatarStore, getAvatarNotExist } from "@/helper"
import useLightconeStore from "@/stores/lightconeStore"
import useRelicStore from "@/stores/relicStore"
import useAffixStore from "@/stores/affixStore"
import useMazeStore from "@/stores/mazeStore"
import { useTranslations } from "next-intl"
export default function AvatarBar() {
const [listElement, setListElement] = useState<Record<string, boolean>>({ "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false })
const [listPath, setListPath] = useState<Record<string, boolean>>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false })
const { listAvatar, setListAvatar, setAvatarSelected, setFilter, filter, setAllMapAvatarInfo, avatarSelected } = useAvatarStore()
const { setAvatars, avatars } = useUserDataStore()
const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore()
const { setListLightcone, setAllMapLightconeInfo } = useLightconeStore()
const { setListRelic, setAllMapRelicInfo } = useRelicStore()
const { setMapMainAffix, setMapSubAffix } = useAffixStore()
const { setAllData } = useMazeStore()
useEffect(() => {
const fetchData = async () => {
// Maze
const maze = await getConfigMazeApi()
setAllData(maze)
// Affix
const mapMainAffix = await getMainAffixApi()
setMapMainAffix(mapMainAffix)
const mapSubAffix = await getSubAffixApi()
setMapSubAffix(mapSubAffix)
}
fetchData()
}, [])
useEffect(() => {
const fetchData = async () => {
// Avatar
const listAvatar = await getCharacterListApi()
listAvatar.sort((a, b) => {
const aHasRelease = typeof a.release === 'number';
const bHasRelease = typeof b.release === 'number';
if (!aHasRelease && !bHasRelease) return 0;
if (!aHasRelease) return -1;
if (!bHasRelease) return 1;
return b.release! - a.release!;
});
const mapAvatar = await fetchCharactersByIdsNative(listAvatar.map((item) => item.id), listCurrentLanguageApi[locale.toLowerCase()])
setListAvatar(listAvatar)
setAllMapAvatarInfo(mapAvatar)
const avatarStore = converterToAvatarStore(getAvatarNotExist())
if (Object.keys(avatarStore).length > 0) {
setAvatars({ ...avatars, ...avatarStore })
}
if (!avatarSelected) {
setAvatarSelected(listAvatar[0])
}
// Lightcone
const listLightcone = await getLightconeListApi()
listLightcone.sort((a, b) => Number(b.id) - Number(a.id))
setListLightcone(listLightcone)
const mapLightcone = await fetchLightconesByIdsNative(listLightcone.map((item) => item.id), listCurrentLanguageApi[locale.toLowerCase()])
setAllMapLightconeInfo(mapLightcone)
// Relic
const listRelic = await getRelicSetListApi()
setListRelic(listRelic)
const mapRelic = await fetchRelicsByIdsNative(listRelic.map((item) => item.id), listCurrentLanguageApi[locale.toLowerCase()])
setAllMapRelicInfo(mapRelic)
}
fetchData()
}, [locale])
useEffect(() => {
setFilter({ ...filter, locale: locale, element: Object.keys(listElement).filter((key) => listElement[key]), path: Object.keys(listPath).filter((key) => listPath[key]) })
}, [locale, listElement, listPath])
return (
<div className="grid grid-flow-row h-full auto-rows-max w-full">
<div className="h-full rounded-lg mx-2 py-2">
<div className="container">
<div className="flex flex-col h-full gap-2">
<div className="flex flex-col gap-2">
<div className="flex justify-center">
<input type="text"
placeholder={transI18n("placeholderCharacter")}
className="input input-bordered input-primary w-full max-w-xs"
value={filter.name}
onChange={(e) => setFilter({ ...filter, name: e.target.value, locale: locale })}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-4 lg:grid-cols-7 mb-1 mx-1 gap-2 w-full max-h-[17vh] min-h-[5vh] overflow-y-auto">
{Object.entries(listElement).map(([key, value], index) => (
<div
key={index}
onClick={() => {
setListElement((prev) => ({ ...prev, [key]: !prev[key] }))
}}
className="hover:bg-gray-600 grid items-center justify-items-center cursor-pointer rounded-md shadow-lg"
style={{
backgroundColor: listElement[key] ? "#374151" : "#6B7280"
}}>
<Image src={"https://api.hakush.in/hsr/UI/element/" + key + ".webp"}
alt={key}
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md"
width={200}
height={200} />
</div>
))}
</div>
<div className="grid grid-cols-1 md:grid-cols-4 lg:grid-cols-8 mb-1 mx-1 gap-2 overflow-y-auto w-full max-h-[17vh] min-h-[5vh]">
{Object.entries(listPath).map(([key, value], index) => (
<div
key={index}
onClick={() => {
setListPath((prev) => ({ ...prev, [key]: !prev[key] }))
}}
className="hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer"
style={{
backgroundColor: listPath[key] ? "#374151" : "#6B7280"
}}
>
<Image src={"https://api.hakush.in/hsr/UI/pathicon/" + key + ".webp"}
alt={key}
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md"
width={200}
height={200} />
</div>
))}
</div>
</div>
<div className="flex items-start h-full">
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 w-full h-[65vh] overflow-y-scroll overflow-x-hidden">
{listAvatar.map((item, index) => (
<div key={index} onClick={() => setAvatarSelected(item)}>
<CharacterCard data={item} />
</div>
))}
</ul>
</div>
</div>
</div>
</div>
</div>
)
}
+517
View File
@@ -0,0 +1,517 @@
"use client"
import { useRouter } from 'next/navigation'
import useAvatarStore from "@/stores/avatarStore"
import useUserDataStore from "@/stores/userDataStore";
import { useEffect, useMemo, useState } from "react";
import { motion } from "framer-motion";
import LightconeBar from '../lightconeBar'
import useLightconeStore from '@/stores/lightconeStore'
import { calcPromotion, calcRarity, replaceByParam } from '@/helper';
import { getSkillTree } from '@/helper/getSkillTree';
import { useTranslations } from 'next-intl';
import ParseText from '../parseText';
import useLocaleStore from '@/stores/localeStore';
import useModelStore from '@/stores/modelStore';
import useMazeStore from '@/stores/mazeStore';
export default function AvatarInfo() {
const router = useRouter()
const { avatarSelected, mapAvatarInfo } = useAvatarStore()
const { Technique } = useMazeStore()
const { avatars, setAvatars, setAvatar } = useUserDataStore()
const { isOpenLightcone, setIsOpenLightcone } = useModelStore()
const { listLightcone, mapLightconeInfo } = useLightconeStore()
const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore();
const lightcone = useMemo(() => {
if (!avatarSelected) return null;
const avatar = avatars[avatarSelected.id];
return avatar?.profileList[avatar.profileSelect]?.lightcone || null;
}, [avatarSelected, avatars]);
const lightconeDetail = useMemo(() => {
if (!lightcone) return null;
return listLightcone.find((item) => Number(item.id) === Number(lightcone.item_id)) || null;
}, [lightcone, listLightcone]);
const handleShow = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
setIsOpenLightcone(true);
modal.showModal();
}
};
// Close modal handler
const handleCloseModal = (modalId: string) => {
setIsOpenLightcone(false);
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
modal.close();
}
};
// Handle ESC key to close modal
useEffect(() => {
if (!isOpenLightcone) {
handleCloseModal("action_detail_modal");
return;
}
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpenLightcone) {
handleCloseModal("action_detail_modal");
}
};
window.addEventListener('keydown', handleEscKey);
return () => window.removeEventListener('keydown', handleEscKey);
}, [isOpenLightcone]);
return (
<div className="bg-base-100 max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
{avatarSelected && avatars[avatarSelected?.id || ""] && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full">
<div className="m-2 min-h-96">
<div className="container">
<div className="card bg-base-200 shadow-xl">
<div className="card-body">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h2 className="card-title text-2xl font-bold flex items-center gap-2">
<div className="w-2 h-8 bg-gradient-to-b from-success to-success/50 rounded-full"></div>
{transI18n("characterSettings")}
</h2>
</div>
<div className="space-y-6">
{/* Level Control */}
<div className="bg-base-100 rounded-xl p-6 border border-base-content/10">
<h4 className="text-lg font-semibold mb-4 flex items-center gap-2">
<div className="w-2 h-6 bg-gradient-to-b from-info to-info/50 rounded-full"></div>
{transI18n("levelConfiguration")}
</h4>
<div className="form-control">
<label className="label">
<span className="label-text font-medium">{transI18n("characterLevel")}</span>
<span className="label-text-alt text-info font-mono">{avatars[avatarSelected?.id || ""]?.level}/80</span>
</label>
<div className="relative">
<input
type="number"
min="1"
max="80"
value={avatars[avatarSelected?.id || ""]?.level}
onChange={(e) => {
const newLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1));
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], level: newLevel, promotion: calcPromotion(newLevel) } });
}}
className="input input-bordered w-full pr-16 font-mono"
placeholder={transI18n("placeholderLevel")}
/>
<div
onClick={() => {
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], level: 80, promotion: calcPromotion(80) } });
}}
className="absolute right-3 top-1/2 -translate-y-1/2 text-base-content/60 cursor-pointer">
<span className="text-sm">{transI18n("max")}</span>
</div>
</div>
<div className="mt-2">
<input
type="range"
min="1"
max="80"
value={avatars[avatarSelected?.id || ""]?.level}
onChange={(e) => {
const newLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1));
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], level: newLevel, promotion: calcPromotion(newLevel) } });
}}
className="range range-info range-sm w-full"
/>
</div>
</div>
</div>
{/* Energy Control */}
<div className="bg-base-100 rounded-xl p-6 border border-base-content/10">
<h4 className="text-lg font-semibold mb-4 flex items-center gap-2">
<div className="w-2 h-6 bg-gradient-to-b from-warning to-warning/50 rounded-full"></div>
{transI18n("ultimateEnergy")}
</h4>
<div className="form-control">
<label className="label">
<span className="label-text font-medium">{transI18n("currentEnergy")}</span>
<span className="label-text text-warning font-mono">
{Math.round(avatars[avatarSelected?.id || ""]?.sp_value)}/{avatars[avatarSelected?.id || ""]?.sp_max}
</span>
</label>
<input
type="range"
min="0"
max={avatars[avatarSelected?.id || ""]?.sp_max}
value={avatars[avatarSelected?.id || ""]?.sp_value}
onChange={(e) => {
if (!avatars[avatarSelected?.id || ""]?.can_change_sp) return
const newSpValue = Math.min(avatars[avatarSelected?.id || ""]?.sp_max, Math.max(0, parseInt(e.target.value) || 0));
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], sp_value: newSpValue } });
}}
className="range range-warning range-sm w-full"
/>
<div className="flex justify-between text-sm text-base-content/60 mt-1">
<span>0%</span>
<span className="font-mono text-warning">{((avatars[avatarSelected?.id || ""]?.sp_value / avatars[avatarSelected?.id || ""]?.sp_max) * 100).toFixed(1)}%</span>
<span>100%</span>
</div>
<div className="mt-3">
<button
className="btn btn-sm btn-outline btn-warning"
onClick={() => {
const newSpValue = Math.ceil(avatars[avatarSelected?.id || ""]?.sp_max / 2);
const newAvatar = { ...avatars[avatarSelected?.id || ""], sp_value: newSpValue }
console.log(newAvatar)
setAvatar(newAvatar)
}}
>
{transI18n("setTo50")}
</button>
</div>
</div>
</div>
{/* Technique Toggle */}
<div className="bg-base-100 rounded-xl p-6 border border-base-content/10">
<h4 className="text-lg font-semibold mb-4 flex items-center gap-2">
<div className="w-2 h-6 bg-gradient-to-b from-accent to-accent/50 rounded-full"></div>
{transI18n("battleConfiguration")}
</h4>
<div className="form-control">
<label className="grid grid-cols-1 gap-2 sm:grid-cols-2 label cursor-pointer">
<div className="flex items-center gap-2">
<svg className="w-5 h-5 text-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<span className="label-text font-medium">{transI18n("useTechnique")}</span>
</div>
<input
type="checkbox"
checked={avatars[avatarSelected?.id || ""]?.techniques.length > 0}
onChange={(e) => {
const techniques = e.target.checked ? Technique[avatarSelected?.id || ""]?.maze_buff : [];
const newAvatar = { ...avatars[avatarSelected?.id || ""], techniques };
setAvatar(newAvatar);
}}
className="toggle toggle-accent"
/>
</label>
<div className="text-xs text-base-content/60 mt-1">
{transI18n("techniqueNote")}
</div>
</div>
</div>
{/* Enhancement Selection */}
{Object.entries(mapAvatarInfo[avatarSelected?.id || ""]?.Enhanced || {}).length > 0 && (
<div className="bg-base-100 rounded-xl p-6 border border-base-content/10">
<h4 className="text-lg font-semibold mb-4 flex items-center gap-2">
<svg className="w-5 h-5 text-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.42 15.17 17.25 21A2.652 2.652 0 0 0 21 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 1 1-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 0 0 4.486-6.336l-3.276 3.277a3.004 3.004 0 0 1-2.25-2.25l3.276-3.276a4.5 4.5 0 0 0-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437 1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008Z" />
</svg>
{transI18n("enhancement")}
</h4>
<div className="form-control">
<label className="label">
<span className="label-text font-medium">{transI18n("enhancementLevel")}</span>
<span className="label-text-alt text-secondary font-mono">
{avatars[avatarSelected?.id || ""]?.enhanced || transI18n("origin")}
</span>
</label>
<select
value={avatars[avatarSelected?.id || ""]?.enhanced || ""}
onChange={(e) => {
const newAvatar = avatars[avatarSelected?.id || ""]
if (newAvatar) {
newAvatar.enhanced = e.target.value
const skillTree = getSkillTree(e.target.value)
if (skillTree) {
newAvatar.data.skills = skillTree
}
setAvatar(newAvatar)
}
}}
className="select select-bordered select-secondary"
>
<option value="">{transI18n("origin")}</option>
{Object.entries(mapAvatarInfo[avatarSelected?.id || ""]?.Enhanced || {}).map(([key, value]) => (
<option key={key} value={key}>
{key}
</option>
))}
</select>
<div className="text-xs text-base-content/60 mt-1">
{transI18n("enhancedNote")}
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</div>
<div className="m-2 min-h-96">
<div className="container">
<div className="card bg-base-200 shadow-xl">
<div className="card-body">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h2 className="card-title text-2xl font-bold flex items-center gap-2">
<div className="w-2 h-8 bg-gradient-to-b from-primary to-secondary rounded-full"></div>
{transI18n("lightconeEquipment")}
</h2>
</div>
{lightcone && lightconeDetail ? (
<div className="grid lg:grid-cols-3 gap-6">
<div className="lg:col-span-3">
{/* Level & Rank Controls */}
<div className="bg-base-100 rounded-xl p-6 border border-base-content/10">
<h4 className="text-lg font-semibold mb-4 flex items-center gap-2">
<div className="w-2 h-6 bg-gradient-to-b from-accent to-accent/50 rounded-full"></div>
{transI18n("lightconeSettings")}
</h4>
<div className="grid md:grid-cols-2 gap-6">
{/* Level Input */}
<div className="form-control">
<label className="label">
<span className="label-text font-medium">{transI18n("level")}</span>
<span className="label-text-alt text-primary font-mono">{lightcone.level}/80</span>
</label>
<div className="relative">
<input
type="number"
min="1"
max="80"
value={lightcone.level || 1}
onChange={(e) => {
const newLightconeLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1))
const newLightcone = { ...lightcone, level: newLightconeLevel, promotion: calcPromotion(newLightconeLevel) }
const newAvatar = { ...avatars[avatarSelected.id] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
setAvatar(newAvatar)
}}
className="input input-bordered w-full pr-16 font-mono"
placeholder={transI18n("placeholderLevel")}
/>
<div
onClick={() => {
const newLightcone = { ...lightcone, level: 80, promotion: calcPromotion(80) }
const newAvatar = { ...avatars[avatarSelected.id] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
setAvatar(newAvatar)
}}
className="absolute right-3 top-1/2 -translate-y-1/2 text-base-content/60 cursor-pointer">
<span className="text-sm">{transI18n("max")}</span>
</div>
</div>
<div className="mt-2">
<input
type="range"
min="1"
max="80"
value={lightcone.level || 1}
onChange={(e) => {
const newLightconeLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1))
const newLightcone = { ...lightcone, level: newLightconeLevel, promotion: calcPromotion(newLightconeLevel) }
const newAvatar = { ...avatars[avatarSelected.id] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
setAvatar(newAvatar)
}}
className="range range-primary range-sm"
/>
</div>
</div>
{/* Rank Selection */}
<div className="form-control space-y-3">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
<span className="font-medium text-sm sm:text-base">{transI18n("superimpositionRank")}</span>
<span className="text-info font-mono text-lg sm:text-xl font-semibold">S{lightcone.rank}</span>
</div>
{/* Rank Buttons */}
<div className="w-full">
<div className="grid grid-cols-5 gap-1.5 sm:gap-2 md:grid-cols-3 xl:grid-cols-5 max-w-sm mx-auto sm:mx-0">
{[1, 2, 3, 4, 5].map((r) => (
<button
key={r}
onClick={() => {
const newLightcone = { ...lightcone, rank: r }
const newAvatar = { ...avatars[avatarSelected.id] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
setAvatar(newAvatar)
}}
className={`
btn btn-sm sm:btn-md font-mono font-semibold
transition-all duration-200 hover:scale-105
${lightcone.rank === r
? 'btn-primary shadow-lg'
: 'btn-outline btn-primary hover:btn-primary'
}`}
>
S{r}
</button>
))}
</div>
</div>
{/* Help Text */}
<div className="text-xs sm:text-sm text-base-content/60 text-center sm:text-left mt-2">
{transI18n("ranksNote")}
</div>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="flex flex-wrap gap-3 mt-2">
<button
onClick={() => handleShow("action_detail_modal")}
className="btn btn-primary btn-lg flex-1 min-w-fit"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg>
{transI18n("changeLightcone")}
</button>
<button
onClick={() => {
const newAvatar = { ...avatars[avatarSelected.id] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = null
setAvatar(newAvatar)
}}
className="btn btn-warning btn-lg flex-1 min-w-fit"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
{transI18n("removeLightcone")}
</button>
</div>
</div>
{/* Lightcone Image */}
<div className="lg:col-span-1">
<div className="">
<img
loading="lazy"
src={`https://api.hakush.in/hsr/UI/lightconemaxfigures/${lightconeDetail.id}.webp`}
className="w-full h-full rounded-lg object-cover shadow-lg"
alt="Lightcone"
/>
</div>
</div>
{/* Lightcone Info & Controls */}
<div className="lg:col-span-2 space-y-6">
{/* Basic Info */}
{mapLightconeInfo[lightcone.item_id] && (
<div className="bg-base-300 rounded-xl p-6 border border-base-content/10">
<div className="flex flex-wrap items-center gap-3 mb-4">
<h3 className="text-2xl font-bold text-base-content">
<ParseText
locale={locale}
text={mapLightconeInfo[lightcone.item_id].Name}
/>
</h3>
<div className="badge badge-outline badge-lg">
{transI18n(mapLightconeInfo[lightcone.item_id].BaseType.toLowerCase())}
</div>
<div className="badge badge-outline badge-lg">
{calcRarity(mapLightconeInfo[lightcone.item_id].Rarity) + "⭐"}
</div>
<div className="badge badge-outline badge-lg">
{"id: " + lightcone.item_id}
</div>
</div>
<div className="prose prose-sm max-w-none">
<div
className="text-base-content/80 leading-relaxed"
dangerouslySetInnerHTML={{
__html: replaceByParam(
mapLightconeInfo[lightcone.item_id].Refinements.Desc,
mapLightconeInfo[lightcone.item_id].Refinements.Level[lightcone.rank.toString()]?.ParamList || []
)
}}
/>
</div>
</div>
)}
</div>
</div>
) : (
/* No Lightcone Equipped State */
<div className="text-center py-12">
<div
onClick={() => handleShow("action_detail_modal")}
className="w-24 h-24 mx-auto mb-6 bg-base-300 rounded-full flex items-center justify-center cursor-pointer"
>
<svg className="w-12 h-12 text-base-content/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</div>
<h3 className="text-xl font-semibold mb-2">{transI18n("noLightconeEquipped")}</h3>
<p className="text-base-content/60 mb-6">{transI18n("equipLightconeNote")}</p>
<button
onClick={() => handleShow("action_detail_modal")}
className="btn btn-success btn-lg"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
{transI18n("equipLightcone")}
</button>
</div>
)}
</div>
</div>
</div>
</div>
</div>
)}
<dialog id="action_detail_modal" className="modal backdrop-blur-sm">
<div className="modal-box w-11/12 max-w-5xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10">
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
onClick={() => handleCloseModal("action_detail_modal")}
>
</motion.button>
</div>
<LightconeBar />
</div>
</dialog>
</div>
);
}
+67
View File
@@ -0,0 +1,67 @@
"use client";
import { getNameChar } from '@/helper';
import useLocaleStore from '@/stores/localeStore';
import { CharacterBasic } from '@/types';
import ParseText from '../parseText';
import Image from 'next/image';
interface CharacterCardProps {
data: CharacterBasic
}
export default function CharacterCard({ data }: CharacterCardProps) {
const { locale } = useLocaleStore();
const text = getNameChar(locale, data)
return (
<li
className="z-10 flex flex-col items-center rounded-xl shadow-xl
bg-gradient-to-br from-base-300 via-base-100 to-warning/70
transform transition-transform duration-300 ease-in-out
hover:scale-105 cursor-pointer min-h-[170px] sm:min-h-[180px] md:min-h-[210px] lg:min-h-[220px] xl:min-h-[240px] 2xl:min-h-[340px]"
>
<div
className={`w-full rounded-md bg-gradient-to-br ${data.rank === "CombatPowerAvatarRarityType5"
? "from-yellow-100 via-yellow-300 to-yellow-500"
: "from-purple-100 via-purple-300 to-purple-500"
}`}
>
<div className="relative w-full h-full">
<Image
width={376}
height={512}
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${data.id}.webp`}
className="w-full h-full rounded-md object-cover"
alt="ALT"
/>
<Image
width={32}
height={32}
src={`https://api.hakush.in/hsr/UI/element/${data.damageType.toLowerCase()}.webp`}
className="absolute top-0 left-0 w-6 h-6 rounded-full"
alt={data.damageType.toLowerCase()}
/>
<Image
width={32}
height={32}
src={`https://api.hakush.in/hsr/UI/pathicon/${data.baseType.toLowerCase()}.webp`}
className="absolute top-0 right-0 w-6 h-6 rounded-full"
alt={data.baseType.toLowerCase()}
style={{
boxShadow: "inset 0 0 8px 4px #9CA3AF"
}}
/>
</div>
</div>
<ParseText
locale={locale}
text={text}
className="mt-2 px-1 text-center text-base font-normal leading-tight 2xl:text-lg"
/>
</li>
);
}
+117
View File
@@ -0,0 +1,117 @@
import React, { useState, useMemo } from 'react';
import { CharacterInfoCardType } from '@/types';
import { getNameChar, replaceByParam } from '@/helper';
import useLocaleStore from '@/stores/localeStore';
import useAvatarStore from '@/stores/avatarStore';
import useLightconeStore from '@/stores/lightconeStore';
import useRelicStore from '@/stores/relicStore';
import Image from 'next/image';
import ParseText from '../parseText';
export default function CharacterInfoCard({ character, selectedCharacters, onCharacterToggle }: { character: CharacterInfoCardType, selectedCharacters: CharacterInfoCardType[], onCharacterToggle: (characterId: CharacterInfoCardType) => void }) {
const isSelected = selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id);
const { mapAvatarInfo } = useAvatarStore();
const { mapLightconeInfo } = useLightconeStore();
const { mapRelicInfo } = useRelicStore();
const { locale } = useLocaleStore();
return (
<div
className={`bg-base-200/60 rounded-xl p-4 border cursor-pointer transition-all duration-200 ${isSelected
? 'border-blue-400 ring-2 ring-blue-400/50'
: 'border-base-300/50 hover:border-base-300 opacity-75'
}`}
onClick={() => onCharacterToggle(character)}
>
{/* Character Portrait */}
<div className="relative mb-4">
<div className="w-full h-48 rounded-lg overflow-hidden relative">
<Image
src={`https://api.hakush.in/hsr/UI/avatarshopicon/${character.avatar_id}.webp`}
alt={mapAvatarInfo[character.avatar_id.toString()]?.Name || ""}
width={376}
height={512}
className="w-full h-full object-contain"
/>
<Image
width={48}
height={48}
src={`https://api.hakush.in/hsr/UI/element/${mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}.webp`}
className="absolute top-0 left-0 w-10 h-10 rounded-full"
alt={mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}
/>
<Image
width={48}
height={48}
src={`https://api.hakush.in/hsr/UI/pathicon/${mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}.webp`}
className="absolute top-0 right-0 w-10 h-10 rounded-full"
alt={mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}
style={{
boxShadow: "inset 0 0 8px 4px #9CA3AF"
}}
/>
</div>
</div>
{/* Character Name and Level */}
<div className="w-full rounded-lg flex items-center justify-center mb-2">
<div className="text-center">
<ParseText className="text-lg font-bold"
text={mapAvatarInfo[character.avatar_id.toString()]?.Name}
locale={locale}
/>
<div className="text-base mb-1">Lv.{character.level} E{character.rank}</div>
</div>
</div>
{character.relics.length > 0 && (
<div className="flex flex-wrap items-center justify-center gap-2 mb-4">
{character.relics.map((relic, index) => (
<div key={index} className="relative">
<div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50">
<Image
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.webp`}
alt="Relic"
width={124}
height={124}
className="w-14 h-14 object-contain"
/>
</div>
<div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-slate-800 text-white text-xs px-1 rounded">
+{relic.level}
</div>
</div>
))}
</div>
)}
{/* Light Cone */}
{character.lightcone.item_id && (
<div className="">
<div className="rounded-lg h-42 flex items-center justify-center">
<img
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${character.lightcone.item_id}.webp`}
alt={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name}
className="w-full h-full object-contain rounded-lg"
/>
</div>
<div className="w-full h-full rounded-lg flex items-center justify-center">
<div className="text-center">
<div className="text-lg font-bold">
<ParseText
text={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name}
locale={locale}
/>
</div>
<div className="text-base mb-1">Lv.{character.lightcone.level} S{character.lightcone.rank}</div>
</div>
</div>
</div>
)}
</div>
);
};
+48
View File
@@ -0,0 +1,48 @@
"use client";
import { getNameLightcone } from '@/helper';
import useLocaleStore from '@/stores/localeStore';
import { LightConeBasic } from '@/types';
import ParseText from '../parseText';
interface LightconeCardProps {
data: LightConeBasic
}
export default function LightconeCard({ data }: LightconeCardProps) {
const { locale } = useLocaleStore();
const text = getNameLightcone(locale, data)
return (
<li className="z-10 flex flex-col items-center rounded-md shadow-lg
bg-gradient-to-b from-customStart to-customEnd transform transition-transform duration-300
hover:scale-105 cursor-pointer min-h-[220px]"
>
<div
className={`w-full rounded-md bg-gradient-to-br ${data.rank === "CombatPowerLightconeRarity5"
? "from-yellow-400 via-yellow-100 to-yellow-500"
: data.rank === "CombatPowerLightconeRarity4" ? "from-purple-300 via-purple-100 to-purple-400" :
"from-blue-300 via-blue-100 to-blue-400"
}`}
>
<div className="relative w-full h-full">
<img
loading="lazy"
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${data.id}.webp`}
className="w-full h-full rounded-md object-cover"
alt="ALT"
/>
</div>
</div>
<ParseText
locale={locale}
text={text}
className="mt-2 px-1 text-center text-base font-normal leading-tight"
/>
</li>
);
}
+73
View File
@@ -0,0 +1,73 @@
import React from 'react';
import { AvatarProfileCardType } from '@/types';
import useLocaleStore from '@/stores/localeStore';
import useAvatarStore from '@/stores/avatarStore';
import useLightconeStore from '@/stores/lightconeStore';
import useRelicStore from '@/stores/relicStore';
import Image from 'next/image';
import ParseText from '../parseText';
export default function ProfileCard({ profile, selectedProfile, onProfileToggle }: { profile: AvatarProfileCardType, selectedProfile: AvatarProfileCardType[], onProfileToggle: (profileId: AvatarProfileCardType) => void }) {
const isSelected = selectedProfile.some((selectedProfile) => selectedProfile.key === profile.key);
const { mapLightconeInfo } = useLightconeStore();
const { locale } = useLocaleStore();
return (
<div
className={`bg-base-200/60 rounded-xl p-4 border cursor-pointer transition-all duration-200 ${isSelected
? 'border-blue-400 ring-2 ring-blue-400/50'
: 'border-base-300/50 hover:border-base-300 opacity-75'
}`}
onClick={() => onProfileToggle(profile)}
>
{/* Light Cone */}
{profile.lightcone && (
<div className="">
<div className="rounded-lg h-42 flex items-center justify-center">
<img
src={`https://api.hakush.in/hsr/UI/lightconemediumicon/${profile.lightcone.item_id}.webp`}
alt={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name}
className="w-full h-full object-contain rounded-lg"
/>
</div>
<div className="w-full h-full rounded-lg flex items-center justify-center">
<div className="text-center">
<div className="text-lg font-bold">
<ParseText
text={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name}
locale={locale}
/>
</div>
<div className="text-base mb-1">Lv.{profile.lightcone.level} S{profile.lightcone.rank}</div>
</div>
</div>
</div>
)}
{Object.keys(profile.relics).length > 0 && (
<div className="flex flex-wrap items-center justify-center gap-2 mb-4">
{Object.entries(profile.relics).map(([key, relic], index) => (
<div key={index} className="relative">
<div className="w-9 h-9 rounded-lg flex items-center justify-center border border-amber-500/50">
<Image
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.webp`}
alt="Relic"
width={124}
height={124}
className="w-14 h-14 object-contain"
/>
</div>
<div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-slate-800 text-white text-xs px-1 rounded">
+{relic.level}
</div>
</div>
))}
</div>
)}
</div>
);
};
+143
View File
@@ -0,0 +1,143 @@
import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";
import Image from "next/image";
import { useMemo } from "react";
interface RelicCardProps {
slot: string
avatarId: string
}
const getRarityColor = (rarity: string) => {
switch (rarity) {
case '3': return 'border-green-500 shadow-green-500/50 bg-gradient-to-br from-green-700 via-green-400 to-green-500';
case '4': return 'border-blue-500 shadow-blue-500/50 bg-gradient-to-br from-blue-700 via-blue-400 to-blue-500';
case '5': return 'border-purple-500 shadow-purple-500/50 bg-gradient-to-br from-purple-700 via-purple-400 to-purple-500';
case '6': return 'border-yellow-500 shadow-yellow-500/50 bg-gradient-to-br from-yellow-700 via-yellow-400 to-yellow-500';
default: return 'border-gray-500 shadow-gray-500/50';
}
};
const getRarityName = (slot: string) => {
switch (slot) {
case '1': return (
<div className="flex items-center gap-1">
<Image src="/relics/HEAD.png" alt="Head" width={20} height={20} />
<h2>Head</h2>
</div>
);
case '2': return (
<div className="flex items-center gap-1">
<Image src="/relics/HAND.png" alt="Head" width={20} height={20} />
<h2>Hands</h2>
</div>
);
case '3': return (
<div className="flex items-center gap-1">
<Image src="/relics/BODY.png" alt="Head" width={20} height={20} />
<h2>Body</h2>
</div>
);
case '4': return (
<div className="flex items-center gap-1">
<Image src="/relics/FOOT.png" alt="Head" width={20} height={20} />
<h2>Feet</h2>
</div>
);
case '5': return (
<div className="flex items-center gap-1">
<Image src="/relics/OBJECT.png" alt="Head" width={20} height={20} />
<h2>Planar sphere</h2>
</div>
);
case '6': return (
<div className="flex items-center gap-1">
<Image src="/relics/NECK.png" alt="Head" width={20} height={20} />
<h2>Link rope</h2>
</div>
);
default: return '';
}
};
export default function RelicCard({ slot, avatarId }: RelicCardProps) {
const { locale } = useLocaleStore();
const { avatars } = useUserDataStore()
const relicDetail = useMemo(() => {
const avatar = avatars[avatarId];
if (avatar) {
if (avatar.profileList[avatar.profileSelect].relics[slot]) {
return avatar.profileList[avatar.profileSelect].relics[slot];
}
return null;
}
return null;
}, [avatars, avatarId, slot]);
return (
<div>
{relicDetail ? (
<div
className="flex flex-col items-center cursor-pointer">
<div
className={`
relative w-24 h-24 rounded-full border-4
${getRarityColor(relicDetail.relic_id.toString()[0])}
shadow-xl
flex items-center justify-center
cursor-pointer hover:scale-105 transition-transform
ring-4 ring-primary
`}
>
<span>
<Image
src={`https://api.hakush.in/hsr/UI/relicfigures/IconRelic_${relicDetail.relic_set_id}_${slot}.webp`}
alt="Relic"
width={124}
height={124}
className="w-14 h-14 object-contain"
/>
</span>
{/* Level Badge */}
<div className="absolute -bottom-2 bg-base-100 border-2 border-base-300 rounded-full px-2 py-1">
<span className="text-sm font-bold text-primary">+{relicDetail.level}</span>
</div>
</div>
<div className="mt-3 text-center">
<div className="text-sm font-medium text-base-content">{getRarityName(slot)}</div>
</div>
</div>
) : (
<div
className="flex flex-col items-center cursor-pointer">
<div
className={`
relative w-24 h-24 rounded-full border-4
${getRarityColor("None")}
bg-base-300 shadow-xl
flex items-center justify-center
cursor-pointer hover:scale-105 transition-transform
ring-4 ring-primary
`}
>
<span className="text-3xl">
<svg className="w-12 h-12 text-base-content/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</span>
{/* Level Badge */}
<div className="absolute -bottom-2 bg-base-100 border-2 border-base-300 rounded-full px-2 py-1">
<span className="text-sm font-bold text-primary">+{0}</span>
</div>
</div>
<div className="mt-3 text-center">
<div>{getRarityName(slot)}</div>
</div>
</div>
)}
</div>
)
}
+665
View File
@@ -0,0 +1,665 @@
"use client";
import React, { useState, useRef} from 'react';
export default function ShowCaseInfo() {
const [imageUrl, setImageUrl] = useState<string>('https://api.hakush.in/hsr/UI/avatardrawcard/1001.webp');
const [position, setPosition] = useState<{ x: number; y: number }>({ x: 0, y: 100 });
const ref = useRef<HTMLDivElement>(null)
return (
<div className="flex flex-col justify-start m-2">
<div className="overflow-auto">
<div ref={ref} className="relative min-h-[650px] w-[1400px] rounded-3xl overflow-hidden">
<img
src="https://cdn.jsdelivr.net/gh/picklejason/hsr-showcase@main/public/blur-bg.png"
alt="Showcase Background"
className="absolute inset-0 w-full h-full object-cover"
/>
<div className="absolute bottom-2 left-4 z-10">
<span className="shadow-black [text-shadow:1px_1px_2px_var(--tw-shadow-color)]"></span>
</div>
<div className="flex flex-row items-center">
<div
className="relative min-h-[650px] w-[28%]"
>
<div
className="flex h-[650px] items-center"
style={{ cursor: 'grab' }}
>
<div className="overflow-hidden w-full h-full"
>
<img
src={imageUrl}
className="scale-[1.8] cursor-pointer object-cover"
alt="Character Preview"
style={{
position: 'relative',
top: `${position.y}px`,
left: `${position.x}px`,
}}
/>
</div>
</div>
<div className="absolute right-0 top-0 mr-[-15px] pt-3">
<div className="flex flex-col">
{[
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/memory-of-you-eidolon_icon_small.webp",
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/memory-of-it-eidolon_icon_small.webp",
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/memory-of-everything-eidolon_icon_small.webp",
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/never-forfeit-again-eidolon_icon_small.webp",
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/never-forget-again-eidolon_icon_small.webp",
"https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/eidolon/just-like-this-always-eidolon_icon_small.webp"
].map((src, index) => (
<div key={index} className="relative my-1 flex rounded-full border-2 border-neutral-300 bg-neutral-800">
<img src={src} alt="Rank Icon" className="h-auto w-10" />
<div className="absolute flex h-full w-full items-center justify-center rounded-full bg-neutral-800/70">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" className="size-5">
<path fillRule="evenodd" d="M10 1a4.5 4.5 0 0 0-4.5 4.5V9H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2h-.5V5.5A4.5 4.5 0 0 0 10 1Zm3 8V5.5a3 3 0 1 0-6 0V9h6Z" clipRule="evenodd"></path>
</svg>
</div>
</div>
))}
</div>
</div>
</div>
<div className="relative flex min-h-[650px] w-[72%] flex-row items-center gap-3.5 rounded-r-3xl pl-10">
<img
src="https://cdn.jsdelivr.net/gh/picklejason/hsr-showcase@main/public/fade-bg.png"
alt="Background"
className="absolute inset-0 w-full h-full object-cover rounded-r-3xl"
style={{ zIndex: -1 }}
/>
<div className="flex h-[650px] w-1/3 flex-col justify-between py-3">
<div className="flex h-full flex-col justify-between">
<div>
<div className="flex flex-row items-center justify-between">
<span className="text-4xl">March 7th</span>
<img src="https://api.hakush.in/hsr/UI/element/ice.webp" alt="Element Icon" className="h-auto w-14" />
</div>
<div className="flex flex-row items-center gap-2">
<img src="https://api.hakush.in/hsr/UI/pathicon/knight.webp" alt="Path Icon" className="h-auto w-8" />
<span className="text-xl">Preservation</span>
</div>
<div>
<span className="text-2xl">Lv. 80</span>
<span className="text-xl"> / </span>
<span className="text-xl text-neutral-400">80</span>
</div>
</div>
<div className="relative mx-4 flex h-[225px] w-auto flex-row items-center justify-evenly">
<div className="absolute mb-5">
<img src="https://api.hakush.in/hsr/UI/pathicon/knight.webp" alt="Path Icon" className="h-40 w-40 opacity-20" />
</div>
<div className="flex h-full w-1/3 flex-col justify-center gap-8">
<div className="flex flex-col items-center">
<div className="relative flex flex-col items-center">
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/frigid-cold-arrow-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
<span className="black-blur absolute bottom-4 text-sm">6 / 6</span>
<span className="z-10 mt-1.5 truncate text-sm">Basic ATK</span>
</div>
</div>
<div className="flex flex-col items-center">
<div className="relative flex flex-col items-center">
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/the-power-of-cuteness-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
<span className="black-blur absolute bottom-4 text-sm">10 / 10</span>
<span className="z-10 mt-1.5 truncate text-sm">Skill</span>
</div>
</div>
</div>
<div className="flex w-1/3 justify-center">
<div className="relative flex flex-col items-center">
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/glacial-cascade-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
<span className="black-blur absolute bottom-4 text-sm">10 / 10</span>
<span className="z-10 mt-1.5 truncate text-sm">Ultimate</span>
</div>
</div>
<div className="flex h-full w-1/3 flex-col justify-center gap-8">
<div className="flex flex-col items-center">
<div className="relative flex flex-col items-center">
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/girl-power-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
<span className="black-blur absolute bottom-4 text-sm">8 / 8</span>
<span className="z-10 mt-1.5 truncate text-sm">Talent</span>
</div>
</div>
<div className="flex flex-col items-center">
<div className="relative flex flex-col items-center">
<img src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/skill/freezing-beauty-skill_icon.webp" alt="Skill Icon" className="h-auto w-12 rounded-full border-2 border-neutral-500 bg-neutral-800" />
<span className="black-blur absolute bottom-4 text-sm">5 / 5</span>
<span className="z-10 mt-1.5 truncate text-sm">Technique</span>
</div>
</div>
</div>
</div>
<div className="flex items-center justify-center">
<div className="flex w-full flex-row justify-evenly">
{/* First Column */}
<div className="flex flex-col justify-center items-center" style={{ gap: '0.25rem' }}>
<img
src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/trace/purify-trace_icon.webp"
alt="Icon 1001101"
className="bg-neutral-800 h-auto w-5 rounded-full"
/>
<div className="flex flex-row justify-center items-center" style={{ gap: '0.25rem' }}>
<img
src="https://api.hakush.in/hsr/UI/trace/IconDefence.webp"
alt="Icon 1001101"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
<img
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
alt="Icon 1001101"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
</div>
</div>
{/* Second Column */}
<div className="flex flex-col justify-center items-center" style={{ gap: '0.25rem' }}>
<img
src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/trace/reinforce-trace_icon.webp"
alt="Icon 1001102"
className="bg-neutral-800 h-auto w-5 rounded-full"
/>
<div className="flex flex-row justify-center items-center" style={{ gap: '0.25rem' }}>
<img
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
alt="Icon 1001102"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
<img
src="https://api.hakush.in/hsr/UI/trace/IconStatusResistance.webp"
alt="Icon 1001102"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
</div>
</div>
{/* Third Column */}
<div className="flex flex-col justify-center items-center" style={{ gap: '0.25rem' }}>
<img
src="https://worker-sparkling-dawn-a1c1.srv2.workers.dev/hsr.honeyhunterworld.com/img/trace/ice-spell-trace_icon.webp"
alt="Icon 1001103"
className="bg-neutral-800 h-auto w-5 rounded-full"
/>
<div className="flex flex-row justify-center items-center" style={{ gap: '0.25rem' }}>
<img
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
alt="Icon 1001103"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
<img
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
alt="Icon 1001103"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
<img
src="https://api.hakush.in/hsr/UI/trace/IconDefence.webp"
alt="Icon 1001103"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
</div>
</div>
{/* Fourth Column */}
<div className="flex flex-col justify-center items-center" style={{ gap: '0.25rem' }}>
<div className="flex flex-row justify-center items-center" style={{ gap: '0.25rem' }}>
<img
src="https://api.hakush.in/hsr/UI/trace/IconIceAddedRatio.webp"
alt="Icon 1001201"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
<img
src="https://api.hakush.in/hsr/UI/trace/IconStatusResistance.webp"
alt="Icon 1001201"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
<img
src="https://api.hakush.in/hsr/UI/trace/IconDefence.webp"
alt="Icon 1001201"
className="bg-neutral-800 h-auto w-3 rounded-full"
/>
</div>
</div>
</div>
</div>
<div className="flex flex-row items-center justify-center">
<div className="relative flex flex-col items-center">
<img
src="https://api.hakush.in/hsr/UI/lightconemaxfigures/24000.webp"
alt="Light Cone Preview"
className="h-auto w-32"
/>
<img
src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png"
alt="Light Cone Rarity Icon"
className="absolute bottom-0 left-1 h-auto w-36"
/>
</div>
<div className="flex w-3/5 flex-col items-center gap-2 text-center">
<span className="text-xl">On the Fall of an Aeon</span>
<div className="flex items-center gap-2">
<div className="h-6 w-6 rounded-full font-normal bg-[#f6ce71] text-black">
V
</div>
<div>
<span className="text-lg">Lv. 80</span>
<span> / </span>
<span className="text-neutral-400">80</span>
</div>
</div>
<div className="flex flex-row gap-1.5">
<div className="black-blur flex flex-row items-center rounded pr-1">
<img
src="https://srtools.pages.dev/icon/property/IconAttack.png"
alt="Attribute Icon"
className="h-auto w-6 p-1"
/>
<span className="text-sm">529</span>
</div>
<div className="black-blur flex flex-row items-center rounded pr-1">
<img
src="https://srtools.pages.dev/icon/property/IconDefence.png"
alt="Attribute Icon"
className="h-auto w-6 p-1"
/>
<span className="text-sm">397</span>
</div>
<div className="black-blur flex flex-row items-center rounded pr-1">
<img
src="https://srtools.pages.dev/icon/property/IconMaxHP.png"
alt="Attribute Icon"
className="h-auto w-6 p-1"
/>
<span className="text-sm">1058</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="flex h-[650px] w-1/3 flex-col justify-between py-3">
<div className="flex w-full flex-col justify-between gap-y-0.5 text-base h-[500px]">
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">HP</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">2942</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">ATK</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">3212</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">DEF</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">1255</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconSpeed.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">SPD</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">106</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">CRIT Rate</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">16.7%</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">CRIT DMG</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">79.2%</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconBreakUp.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">Break Effect</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">186.9%</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconStatusResistance.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">Effect RES</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">4.0%</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconEnergyRecovery.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">Energy Regeneration Rate</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">0.0%</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconStatusProbability.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">Effect Hit Rate</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">20.3%</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconHealRatio.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">Outgoing Healing Boost</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">0.0%</div>
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconIceAddedRatio.png" alt="Stat Icon" className="h-auto w-10 p-2" />
<span className="font-bold">Ice DMG Boost</span>
</div>
<div className="ml-3 mr-3 flex-grow border rounded opacity-50" />
<div className="flex cursor-default flex-col text-right font-bold">6.4%</div>
</div>
</div>
<hr />
<div className="flex flex-col items-center gap-1">
<div className="flex w-full flex-row justify-between text-left">
<span>Prisoner in Deep Confinement</span>
<div>
<span className="black-blur bg-black flex w-5 justify-center rounded px-1.5 py-0.5">2</span>
</div>
</div>
<div className="flex w-full flex-row justify-between text-left">
<span>Watchmaker, Master of Dream Machinations</span>
<div>
<span className="black-blur bg-black flex w-5 justify-center rounded px-1.5 py-0.5">2</span>
</div>
</div>
<div className="flex w-full flex-row justify-between text-left">
<span>Talia: Kingdom of Banditry</span>
<div>
<span className="black-blur bg-black flex w-5 justify-center rounded px-1.5 py-0.5">2</span>
</div>
</div>
</div>
</div>
<div className="w-1/3">
<div className="flex h-[650px] flex-col justify-between py-3 text-lg">
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
<div className="flex">
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_116_1.webp" alt="Relic Icon" className="h-auto w-20" />
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
</div>
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Main Affix Icon" className="h-auto w-9" />
<span className="text-base text-[#f1a23c]">705</span>
<span className="black-blur rounded px-1 text-xs">+15</span>
</div>
<div style={{ opacity: 0.5, height: '80px', borderLeftWidth: '1px' }}></div>
<div className="m-auto grid w-1/2 grid-cols-2 gap-2">
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+7.8%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+3.2%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+17.5%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconBreakUp.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+15.6%</span>
</div>
</div>
</div>
</div>
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
<div className="flex">
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_118_2.webp" alt="Relic Icon" className="h-auto w-20" />
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
</div>
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
<span className="text-base text-[#f1a23c]">352</span>
<span className="black-blur rounded px-1 text-xs">+15</span>
</div>
<div style={{ opacity: 0.5, height: '80px', borderLeftWidth: '1px' }}></div>
<div className="m-auto grid w-1/2 grid-cols-2 gap-2">
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+6.9%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+11.7%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconStatusProbability.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+3.9%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconBreakUp.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+23.3%</span>
</div>
</div>
</div>
</div>
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
<div className="flex">
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_116_3.webp" alt="Relic Icon" className="h-auto w-20" />
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
</div>
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
<span className="text-base text-[#f1a23c]">43.2%</span>
<span className="black-blur rounded px-1 text-xs">+15</span>
</div>
<div style={{ opacity: 0.5, height: '80px', borderLeftWidth: '1px' }}></div>
<div className="m-auto grid w-1/2 grid-cols-2 gap-2">
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+57</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+3.9%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+2.6%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconBreakUp.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+19.4%</span>
</div>
</div>
</div>
</div>
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
<div className="flex">
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_118_4.webp" alt="Relic Icon" className="h-auto w-20" />
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
</div>
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
<span className="text-base text-[#f1a23c]">18.9%</span>
<span className="black-blur rounded px-1 text-xs">+15</span>
</div>
<div style={{ opacity: 0.5, height: '80px', borderLeftWidth: '1px' }}></div>
<div className="m-auto grid w-1/2 grid-cols-2 gap-2">
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+5.2%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+4.3%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+2.7%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+34</span>
</div>
</div>
</div>
</div>
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
<div className="flex">
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_118_4.webp" alt="Relic Icon" className="h-auto w-20" />
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
</div>
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
<span className="text-base text-[#f1a23c]">18.9%</span>
<span className="black-blur rounded px-1 text-xs">+15</span>
</div>
<div style={{ opacity: 0.5, height: '80px', borderLeftWidth: '1px' }}></div>
<div className="m-auto grid w-1/2 grid-cols-2 gap-2">
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+5.2%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+4.3%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+2.7%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+34</span>
</div>
</div>
</div>
</div>
<div className="black-blur relative flex flex-row items-center rounded-s-lg border-l-2 p-1 border-yellow-600">
<div className="flex">
<img src="https://api.hakush.in/hsr/UI/relicfigures/IconRelic_118_4.webp" alt="Relic Icon" className="h-auto w-20" />
<img src="https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/deco/Star5.png" alt="Relic Rarity Icon" className="absolute bottom-1 h-auto w-20" />
</div>
<div className="mx-1 flex w-1/6 flex-col items-center justify-center">
<img src="https://srtools.pages.dev/icon/property/IconAttack.png" alt="Main Affix Icon" className="h-auto w-9" />
<span className="text-base text-[#f1a23c]">18.9%</span>
<span className="black-blur rounded px-1 text-xs">+15</span>
</div>
<div style={{ opacity: 0.5, height: '80px', borderLeftWidth: '1px' }}></div>
<div className="m-auto grid w-1/2 grid-cols-2 gap-2">
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconMaxHP.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+5.2%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalDamage.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+4.3%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconCriticalChance.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+2.7%</span>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row items-center">
<img src="https://srtools.pages.dev/icon/property/IconDefence.png" alt="Sub Affix Icon" className="h-auto w-7" />
<span className="text-sm">+34</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
+66
View File
@@ -0,0 +1,66 @@
"use client"
import { replaceByParam } from "@/helper";
import useListAvatarStore from "@/stores/avatarStore";
import Image from "next/image";
import ParseText from "../parseText";
import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";
import { useMemo } from "react";
export default function EidolonsInfo() {
const { avatarSelected, mapAvatarInfo } = useListAvatarStore()
const { locale } = useLocaleStore()
const { setAvatars, avatars } = useUserDataStore()
const charRank = useMemo(() => {
if (!avatarSelected) return null;
const avatar = avatars[avatarSelected.id];
if (avatar?.enhanced != "") {
return mapAvatarInfo[avatarSelected.id]?.Enhanced[avatar?.enhanced].Ranks
}
return mapAvatarInfo[avatarSelected.id]?.Ranks
}, [avatarSelected, avatars, locale, mapAvatarInfo]);
return (
<div className="grid grid-cols-1 m-4 p-4 font-bold gap-4 w-fit max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
{charRank && avatars[avatarSelected?.id || ""] && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Object.entries(charRank || {}).map(([key, rank]) => (
<div key={key}
className="flex flex-col items-center cursor-pointer hover:scale-105"
onClick={() => {
let newRank = Number(key)
if (Number(key) == 1 && avatars[avatarSelected?.id || ""]?.data?.rank == 1) {
newRank = 0
}
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], data: { ...avatars[avatarSelected?.id || ""].data, rank: newRank } } })
}}
>
<Image
loading="lazy"
className={`w-60 h-60 mb-2 ${Number(key) <= avatars[avatarSelected?.id || ""]?.data?.rank ? "" : "grayscale"}`}
src={`https://api.hakush.in/hsr/UI/rank/_dependencies/textures/${avatarSelected?.id}/${avatarSelected?.id}_Rank_${key}.webp`}
alt={`Rank ${key}`}
width={240}
height={240}
/>
<div className="text-lg pb-1 flex items-center justify-items-center gap-2">
<span className="inline-block text-indigo-500">{key}.</span>
<ParseText
locale={locale}
text={rank.Name}
className="text-center text-base font-normal leading-tight"
/>
</div>
<div className="text-sm font-normal">
<div dangerouslySetInnerHTML={{ __html: replaceByParam(rank.Desc, rank.ParamList) }} />
</div>
</div>
))}
</div>
)}
</div>
);
}
+11
View File
@@ -0,0 +1,11 @@
export default function Footer() {
return(
<footer className="footer footer-horizontal footer-center bg-base-200 text-base-content rounded p-10">
<aside>
<p>Copyright © {new Date().getFullYear()} - Kain (Powered by Nextjs & DaisyUi)</p>
</aside>
</footer>
)
}
+602
View File
@@ -0,0 +1,602 @@
"use client"
import { connectToPS, downloadJson, syncDataToPS } from "@/helper";
import { converterToFreeSRJson } from "@/helper/converterToFreeSRJson";
import { useChangeTheme } from "@/hooks/useChangeTheme";
import { listCurrentLanguage } from "@/lib/constant";
import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";
import { motion } from "framer-motion";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import EnkaImport from "../importBar/enka";
import useModelStore from "@/stores/modelStore";
import FreeSRImport from "../importBar/freesr";
import { toast } from "react-toastify";
import { micsSchema } from "@/zod";
import useConnectStore from "@/stores/connectStore";
import useGlobalStore from "@/stores/globalStore";
const themes = [
{ label: "Winter" },
{ label: "Night" },
{ label: "Cupcake" },
{ label: "Coffee" },
];
export default function Header() {
const { changeTheme } = useChangeTheme()
const { locale, setLocale } = useLocaleStore()
const { avatars, battle_config, setAvatars, setBattleConfig } = useUserDataStore()
const router = useRouter()
const transI18n = useTranslations("DataPage")
const { setIsOpenImport, isOpenImport } = useModelStore()
const [message, setMessage] = useState({ text: '', type: '' });
const [importModal, setImportModal] = useState("enka");
const [isModalOpen, setIsModalOpen] = useState(false);
const {
connectionType,
privateType,
serverUrl,
username,
password,
setConnectionType,
setPrivateType,
setServerUrl,
setUsername,
setPassword
} = useConnectStore()
const { isConnectPS, setIsConnectPS } = useGlobalStore()
useEffect(() => {
const cookieLocale = document.cookie.split("; ")
.find((row) => row.startsWith("MYNEXTAPP_LOCALE"))
?.split("=")[1];
if (cookieLocale) {
if (!listCurrentLanguage.hasOwnProperty(cookieLocale)) {
setLocale("en")
} else {
setLocale(cookieLocale)
}
} else {
let browserLocale = navigator.language.slice(0, 2);
if (!listCurrentLanguage.hasOwnProperty(browserLocale)) {
browserLocale = "en"
}
setLocale(browserLocale);
document.cookie = `MYNEXTAPP_LOCALE=${browserLocale};`
router.refresh()
}
}, [router, setLocale])
const changeLocale = (newLocale: string) => {
setLocale(newLocale)
document.cookie = `MYNEXTAPP_LOCALE=${newLocale};`
router.refresh()
}
const handleShow = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
setIsModalOpen(true);
modal.showModal();
}
};
// Close modal handler
const handleCloseModal = (modalId: string) => {
setIsModalOpen(false);
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
modal.close()
}
};
// Handle ESC key to close modal
useEffect(() => {
if (!isOpenImport) {
handleCloseModal("import_modal");
return;
}
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
handleCloseModal("import_modal");
}
};
window.addEventListener('keydown', handleEscKey);
return () => window.removeEventListener('keydown', handleEscKey);
}, [isModalOpen, isOpenImport]);
const handleImportDatabase = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) {
toast.error(transI18n("pleaseSelectAFile"))
return
}
if (!file.name.endsWith(".json") || file.type !== "application/json") {
toast.error(transI18n("fileMustBeAValidJsonFile"))
return
}
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target?.result as string);
const parsed = micsSchema.parse(data)
setAvatars(parsed.avatars)
setBattleConfig(parsed.battle_config)
toast.success(transI18n("importDatabaseSuccess"))
} catch (error) {
toast.error(transI18n("fileMustBeAValidJsonFile"))
}
};
reader.readAsText(file);
}
};
return (
<div className="navbar bg-base-100 shadow-md sticky top-0 z-50 px-3 py-1">
<div className="navbar-start">
{/* Mobile menu dropdown */}
<div className="dropdown">
<div tabIndex={0} role="button" className="btn btn-ghost btn-sm lg:hidden hover:bg-base-200 transition-all duration-300">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" />
</svg>
</div>
<ul
tabIndex={0}
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-10 mt-3 w-52 p-2 shadow-md border border-base-200"
>
<li>
<details>
<summary className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium w-full">{transI18n("loadData")}</summary>
<ul className="p-2">
<li><a onClick={() => {
setImportModal("freesr")
setIsOpenImport(true)
handleShow("import_modal")
}}>{transI18n("freeSr")}</a></li>
<li>
<>
<input
type="file"
accept="application/json"
id="database-data-upload"
className="hidden"
onChange={handleImportDatabase}
/>
<button
onClick={() => document.getElementById('database-data-upload')?.click()}
>
{transI18n("database")}
</button>
</>
</li>
<li><a onClick={() => {
setImportModal("enka")
setIsOpenImport(true)
handleShow("import_modal")
}}>{transI18n("enka")}</a></li>
</ul>
</details>
</li>
<li>
<details>
<summary className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium">{transI18n("exportData")}</summary>
<ul className="p-2">
<li><a onClick={() => downloadJson("freesr-data", converterToFreeSRJson(avatars, battle_config))}>{transI18n("freeSr")}</a></li>
<li><a onClick={() => downloadJson("database-data", { avatars: avatars, battle_config: battle_config })}>{transI18n("database")}</a></li>
</ul>
</details>
</li>
<li>
<button
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
onClick={() => handleShow("connection_modal")}
>
{transI18n("connectSetting")}
</button>
</li>
<li>
<button
disabled
className="disabled px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
>
{transI18n("monsterSetting")} ({transI18n("comingSoon")})
</button>
</li>
</ul>
</div>
{/* Logo */}
<a className="hidden sm:grid sm:grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
<h1 className="text-xl font-bold">
<span className="text-emerald-500">Firefly Sr</span>
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
Tools
</span>
</h1>
<p className="text-sm text-gray-500">By Kain</p>
</a>
</div>
{/* Desktop navigation */}
<div className="navbar-center hidden lg:flex">
<ul className="menu menu-horizontal gap-1">
<li>
<details>
<summary className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium">{transI18n("loadData")}</summary>
<ul className="p-2">
<li><a onClick={() => {
setImportModal("freesr")
setIsOpenImport(true)
handleShow("import_modal")
}}>{transI18n("freeSr")}</a></li>
<li>
<>
<input
type="file"
accept="application/json"
id="database-data-upload"
className="hidden"
onChange={handleImportDatabase}
/>
<button
onClick={() => document.getElementById('database-data-upload')?.click()}
>
{transI18n("database")}
</button>
</>
</li>
<li><a onClick={() => {
setImportModal("enka")
setIsOpenImport(true)
handleShow("import_modal")
}}>{transI18n("enka")}</a></li>
</ul>
</details>
</li>
<li>
<details>
<summary className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium">{transI18n("exportData")}</summary>
<ul className="p-2">
<li><a onClick={() => downloadJson("freesr-data", converterToFreeSRJson(avatars, battle_config))}>{transI18n("freeSr")}</a></li>
<li><a onClick={() => downloadJson("database-data", { avatars: avatars, battle_config: battle_config })}>{transI18n("database")}</a></li>
</ul>
</details>
</li>
<li>
<button
className="px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
onClick={() => handleShow("connection_modal")}
>
{transI18n("connectSetting")}
</button>
</li>
<li>
<button
disabled
className="disabled:opacity-50 px-3 py-2 hover:bg-base-200 rounded-md transition-all duration-200 font-medium"
>
{transI18n("monsterSetting")} ({transI18n("comingSoon")})
</button>
</li>
</ul>
</div>
{/* Right side items */}
<div className="navbar-end gap-2">
<div className="px-2">
<div className="flex items-center space-x-2 p-1.5 rounded-full shadow-md">
<div className={`hidden lg:block text-sm italic ${isConnectPS ? 'text-green-500' : 'text-red-500'}`}>
{isConnectPS ? transI18n("connected") : transI18n("unconnected")}
</div>
<div className={`w-3 h-3 rounded-full ${isConnectPS ? 'bg-green-500' : 'bg-red-500'}`}></div>
</div>
</div>
{/* Language selector - REFINED */}
<div className="dropdown dropdown-end">
<div className="flex items-center gap-1 border border-base-300 rounded text-sm px-1.5 py-0.5 hover:bg-base-200 cursor-pointer transition-all duration-200">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-4">
<path strokeLinecap="round" strokeLinejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802" />
</svg>
<select
className="outline-none bg-base-200 cursor-pointer text-sm pr-0"
value={locale}
onChange={(e) => changeLocale(e.target.value)}
>
{Object.entries(listCurrentLanguage).map(([key, value]) => (
<option key={key} value={key}>{value}</option>
))}
</select>
</div>
</div>
<div title="Change Theme" className="dropdown dropdown-end">
<div tabIndex={0} role="button" className="btn btn-ghost btn-sm hover:bg-base-200 transition-all duration-200 px-2">
<svg
width={16}
height={16}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
className="h-4 w-4 stroke-current"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
/>
</svg>
<svg
width="10px"
height="10px"
className="ml-1 h-2 w-2 fill-current opacity-60"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 2048 2048"
>
<path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z" />
</svg>
</div>
<div
tabIndex={0}
className="dropdown-content bg-base-100 text-base-content rounded-lg top-px overflow-y-auto border border-base-200 shadow-md mt-12"
>
<ul className="menu w-40 p-1">
{themes.map((theme) => (
<li
key={theme.label}
onClick={() => {
if (changeTheme) changeTheme(theme.label.toLowerCase());
}}
>
<button
className="gap-2 px-2 py-1.5 hover:bg-base-200 rounded text-sm transition-all duration-200"
data-set-theme={theme.label.toLowerCase()}
data-act-class="[&_svg]:visible"
>
<div
data-theme={theme.label.toLowerCase()}
className="bg-base-100 grid shrink-0 grid-cols-2 gap-0.5 rounded p-1 shadow-sm"
>
<div className="bg-base-content size-1 rounded-full" />
<div className="bg-primary size-1 rounded-full" />
<div className="bg-secondary size-1 rounded-full" />
<div className="bg-accent size-1 rounded-full" />
</div>
<div className="text-sm">{theme.label}</div>
</button>
</li>
))}
</ul>
</div>
</div>
{/* GitHub Link */}
<Link
className='hidden sm:flex btn btn-ghost btn-sm btn-circle bg-white/20 hover:bg-white/100 transition-all duration-200 items-center justify-center'
href={"https://github.com/AzenKain/Firefly-Srtools"}
target="_blank"
rel="noopener noreferrer"
>
<svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
</svg>
</Link>
</div>
<dialog id="connection_modal" className="modal sm:modal-middle backdrop-blur-sm">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10">
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
onClick={() => handleCloseModal("connection_modal")}
>
</motion.button>
</div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
{"PS Connection"}
</h3>
</div>
<div className="px-6 py-4">
{/* Select connection type */}
<div className="form-control grid grid-cols-1 w-full mb-6">
<label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("connectionType")}</span>
</label>
<select
className="select w-full select-bordered border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={connectionType}
onChange={(e) => setConnectionType(e.target.value)}
>
<option value="FireflyGo">FireflyGo</option>
<option value="Other">{transI18n("other")}</option>
</select>
</div>
{/* Show host/port if Other */}
{connectionType === "Other" && (
<div className="flex flex-col md:space-x-4 mb-6 gap-2">
<div className="form-control w-full mb-4 md:mb-0">
<label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("serverUrl")}</span>
</label>
<input
type="text"
placeholder={transI18n("placeholderServerUrl")}
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={serverUrl}
onChange={(e) => setServerUrl(e.target.value)}
/>
</div>
<div className="form-control w-full mb-4 md:mb-0">
<label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("privateType")}</span>
</label>
<select
className="select w-full select-bordered border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={privateType}
onChange={(e) => setPrivateType(e.target.value)}
>
<option value="Local">{transI18n("local")}</option>
<option value="Server">{transI18n("server")}</option>
</select>
</div>
<div className="form-control w-full mb-4 md:mb-0">
<label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("username")}</span>
</label>
<input
type="text"
placeholder={transI18n("placeholderUsername")}
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="form-control w-full mb-4 md:mb-0">
<label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("password")}</span>
</label>
<input
type="password"
placeholder={transI18n("placeholderPassword")}
className="input input-bordered w-full border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
</div>
)}
{message.text && (
<div className={`alert ${message.type === 'success' ? 'alert-success' :
message.type === 'error' ? 'alert-error' : 'alert-info'
} mb-6`}>
<span>{message.text}</span>
</div>
)}
<div className="flex flex-col sm:flex-row justify-between items-center mt-6 mb-2">
<div className="mb-4 sm:mb-0">
<span className="text-md mr-2">{transI18n("status")}:</span>
<span className={`badge ${isConnectPS ? 'badge-success' : 'badge-error'} badge-lg`}>
{isConnectPS ? transI18n("connected") : transI18n("unconnected")}
</span>
</div>
<div className="flex space-x-2">
<button
onClick={
async () => {
const response = await connectToPS()
if (response.success) {
setIsConnectPS(true)
setMessage({
type: "success",
text: transI18n("connectedSuccess")
})
} else {
setIsConnectPS(false)
setMessage({
type: "error",
text: response.message
})
}
}
}
className={`btn btn-primary`}
>
{transI18n("connectPs")}
</button>
{isConnectPS && (
<button
onClick={
async () => {
const response = await syncDataToPS()
if (response.success) {
setMessage({
type: "success",
text: transI18n("syncSuccess")
})
} else {
setMessage({
type: "error",
text: `${transI18n("syncFailed")}: ${response.message}`
})
}
}
}
className={`btn btn-success`}
>
{transI18n("sync")}
</button>
)}
</div>
</div>
</div>
</div>
</dialog>
<dialog id="import_modal" className="modal backdrop-blur-sm">
<div className="modal-box w-11/12 max-w-max bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10">
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
onClick={() => {
handleCloseModal("import_modal")
setIsOpenImport(false)
}}
>
</motion.button>
</div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
{transI18n("importSetting")}
</h3>
</div>
{importModal === "enka" && <EnkaImport />}
{importModal === "freesr" && <FreeSRImport />}
</div>
</dialog>
</div>
)
}
+279
View File
@@ -0,0 +1,279 @@
"use client"
import useModelStore from "@/stores/modelStore";
import useUserDataStore from "@/stores/userDataStore";
import { useEffect, useState } from "react";
import useCopyProfileStore from "@/stores/copyProfile";
import ProfileCard from "../card/profileCard";
import { AvatarProfileCardType, AvatarProfileStore } from "@/types";
import Image from "next/image";
import SelectCustom from "../select";
import useListAvatarStore from "@/stores/avatarStore";
import { getNameChar } from "@/helper";
import useLocaleStore from "@/stores/localeStore";
import { useTranslations } from "next-intl";
export default function CopyImport() {
const { avatars, setAvatar } = useUserDataStore();
const {avatarSelected} = useListAvatarStore()
const { locale } = useLocaleStore()
const {
selectedProfiles,
listCopyAvatar,
avatarCopySelected,
setSelectedProfiles,
filterCopy,
setFilterCopy,
setAvatarCopySelected
} = useCopyProfileStore()
const transI18n = useTranslations("DataPage")
const [listPath, setListPath] = useState<Record<string, boolean>>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false })
const [listElement, setListElement] = useState<Record<string, boolean>>({ "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false })
const [listRank, setListRank] = useState<Record<string, boolean>>({ "4": false, "5": false })
const [message, setMessage] = useState({
type: "",
text: ""
})
const handleProfileToggle = (profile: AvatarProfileCardType) => {
if (selectedProfiles.some((selectedProfile) => selectedProfile.key === profile.key)) {
setSelectedProfiles(selectedProfiles.filter((selectedProfile) => selectedProfile.key !== profile.key));
return;
}
setSelectedProfiles([...selectedProfiles, profile]);
};
useEffect(() => {
setFilterCopy({
...filterCopy,
locale: locale,
path: Object.keys(listPath).filter((key) => listPath[key]),
element: Object.keys(listElement).filter((key) => listElement[key]),
rarity: Object.keys(listRank).filter((key) => listRank[key])
})
}, [listPath, listRank, locale])
const clearSelection = () => {
setSelectedProfiles([]);
setMessage({
type: "success",
text: transI18n("clearAll")
})
};
const selectAll = () => {
if (avatarCopySelected) {
setSelectedProfiles(avatars[avatarCopySelected?.id.toString()].profileList.map((profile, index) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
return null;
}
return {
key: index,
...profile,
} as AvatarProfileCardType
}).filter((profile) => profile !== null));
}
setMessage({
type: "success",
text: transI18n("selectedAll")
})
};
const handleCopy = () => {
if (selectedProfiles.length === 0) {
setMessage({
type: "error",
text: transI18n("pleaseSelectAtLeastOneProfile")
});
return;
}
if (!avatarCopySelected) {
setMessage({
type: "error",
text: transI18n("noAvatarToCopySelected")
});
return;
}
if (!avatarSelected) {
setMessage({
type: "error",
text: transI18n("noAvatarSelected")
});
return;
}
const newListProfile = avatars[avatarCopySelected.id.toString()].profileList.map((profile, index) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
return null;
}
return {
...profile,
profile_name: profile.profile_name + ` - Copy: ${avatarCopySelected?.id}`,
} as AvatarProfileStore
}).filter((profile) => profile !== null);
const newAvatar = {
...avatars[avatarSelected.id.toString()],
profileList: avatars[avatarSelected.id.toString()].profileList.concat(newListProfile),
profileSelect: avatars[avatarSelected.id.toString()].profileList.length - 1,
}
setAvatar(newAvatar);
setSelectedProfiles([]);
setMessage({
type: "success",
text: transI18n("copied")
});
};
return (
<div className="rounded-lg px-2 min-h-[60vh]">
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="mb-2">
<div className="mt-2 w-full">
<div className="flex items-start flex-col gap-2 mt-2 w-full">
<div>{transI18n("filter")}</div>
<div className="flex flex-wrap justify-start gap-10 mt-2 w-full">
{/* Path */}
<div>
<div className="flex flex-wrap gap-2 justify-start items-center">
{Object.entries(listPath).map(([key], index) => (
<div
key={index}
onClick={() => {
setListPath((prev) => ({ ...prev, [key]: !prev[key] }))
}}
className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
style={{
backgroundColor: listPath[key] ? "#374151" : "#6B7280"
}}>
<Image
src={`https://api.hakush.in/hsr/UI/pathicon/${key}.webp`}
alt={key}
className="h-[32px] w-[32px] object-contain rounded-md"
width={200}
height={200}
/>
</div>
))}
</div>
</div>
{/* Element */}
<div>
<div className="flex flex-wrap gap-2 justify-start items-center">
{Object.entries(listElement).map(([key], index) => (
<div
key={index}
onClick={() => {
setListElement((prev) => ({ ...prev, [key]: !prev[key] }))
}}
className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
style={{
backgroundColor: listElement[key] ? "#374151" : "#6B7280"
}}>
<Image
src={`https://api.hakush.in/hsr/UI/element/${key}.webp`}
alt={key}
className="h-[28px] w-[28px] 2xl:h-[40px] 2xl:w-[40px] object-contain rounded-md"
width={200}
height={200}
/>
</div>
))}
</div>
</div>
{/* Rank */}
<div>
<div className="flex flex-wrap gap-2 justify-end items-center">
{Object.entries(listRank).map(([key], index) => (
<div
key={index}
onClick={() => {
setListRank((prev) => ({ ...prev, [key]: !prev[key] }))
}}
className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer"
style={{
backgroundColor: listRank[key] ? "#374151" : "#6B7280"
}}>
<div className="font-bold text-white h-[32px] w-[32px] text-center flex items-center justify-center">{key}*</div>
</div>
))}
</div>
</div>
</div>
</div>
<div className="grid grid-cols-1 gap-2">
<div>
<div>{transI18n("characterName")}</div>
{listCopyAvatar.length > 0 && (
<SelectCustom
customSet={listCopyAvatar.map((avatar) => ({
value: avatar.id.toString(),
label: getNameChar(locale, avatar),
imageUrl: `https://api.hakush.in/hsr/UI/avatarshopicon/${avatar.id}.webp`
}))}
excludeSet={[]}
selectedCustomSet={avatarCopySelected?.id.toString() || ""}
placeholder="Character Select"
setSelectedCustomSet={(value) => setAvatarCopySelected(listCopyAvatar.find((avatar) => avatar.id.toString() === value) || null)}
/>
)}
</div>
</div>
</div>
{/* Selection Info */}
<div className="mt-6 flex items-center gap-4 bg-slate-800/50 p-4 rounded-lg">
<span className="text-blue-400 font-medium">
{transI18n("selectedProfiles")}: {selectedProfiles.length}
</span>
<button
onClick={clearSelection}
className="text-error/75 hover:text-error text-sm font-medium px-3 py-1 border border-error/75 rounded hover:bg-error/10 transition-colors cursor-pointer"
>
{transI18n("clearAll")}
</button>
<button
onClick={selectAll}
className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer">
{transI18n("selectAll")}
</button>
{selectedProfiles.length > 0 && (
<button
onClick={handleCopy}
className="text-success/75 hover:text-success text-sm font-medium px-3 py-1 border border-success/75 rounded hover:bg-success/10 transition-colors cursor-pointer">
{transI18n("copy")}
</button>
)}
</div>
{message.type && message.text && (
<div className={(message.type == "error" ? "text-error" : "text-success") + " text-lg mt-2"} >{message.type == "error" ? "😭" : "🎉"} {message.text}</div>
)}
</div>
{/* Character Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{avatarCopySelected && avatars[avatarCopySelected?.id.toString()]?.profileList.map((profile, index) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
return null;
}
return (
<ProfileCard
key={index}
profile={{ ...profile, key: index }}
selectedProfile={selectedProfiles}
onProfileToggle={handleProfileToggle}
/>
)
})}
</div>
</div>
</div>
)
}
+222
View File
@@ -0,0 +1,222 @@
"use client"
import { useState } from "react";
import CharacterInfoCard from "../card/characterInfoCard";
import useEnkaStore from "@/stores/enkaStore";
import { SendDataThroughProxy } from "@/lib/api";
import { CharacterInfoCardType, EnkaResponse } from "@/types";
import useUserDataStore from "@/stores/userDataStore";
import { converterOneEnkaDataToAvatarStore } from "@/helper";
import useModelStore from "@/stores/modelStore";
import { toast } from "react-toastify";
import { useTranslations } from "next-intl";
export default function EnkaImport() {
const {
uidInput,
setUidInput,
enkaData,
selectedCharacters,
setSelectedCharacters,
setEnkaData,
} = useEnkaStore();
const transI18n = useTranslations("DataPage")
const { avatars, setAvatar } = useUserDataStore();
const { isOpenImport, setIsOpenImport } = useModelStore()
const [isLoading, setIsLoading] = useState(false)
const [Error, setError] = useState("")
const handlerFetchData = async () => {
if (!uidInput) {
setError(transI18n("pleaseEnterUid"))
return;
}
setIsLoading(true)
const data : EnkaResponse = await SendDataThroughProxy({data: {serverUrl: "https://enka.network/api/hsr/uid/" + uidInput, method: "GET"}})
if (data) {
setEnkaData(data)
setSelectedCharacters(data.detailInfo.avatarDetailList.map((character) => {
return {
key: character.avatarId,
avatar_id: character.avatarId,
rank: character.rank ?? 0,
level: character.level,
lightcone: {
level: character.equipment.level,
rank: character.equipment.rank,
item_id: character.equipment.tid,
},
relics: character.relicList.map((relic) => ({
level: relic.level,
relic_id: relic.tid,
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
})),
} as CharacterInfoCardType
}))
setError("")
} else {
setError(transI18n("failedToFetchEnkaData"))
}
setIsLoading(false)
}
const handleCharacterToggle = (character: CharacterInfoCardType) => {
if (selectedCharacters.some((selectedCharacter) => selectedCharacter.key === character.key)) {
setSelectedCharacters(selectedCharacters.filter((selectedCharacter) => selectedCharacter.key !== character.key));
return;
}
setSelectedCharacters([...selectedCharacters, character]);
};
const clearSelection = () => {
setSelectedCharacters([]);
};
const selectAll = () => {
if (enkaData) {
setSelectedCharacters(enkaData?.detailInfo.avatarDetailList.map((character) => {
return {
key: character.avatarId,
avatar_id: character.avatarId,
rank: character.rank ?? 0,
level: character.level,
lightcone: {
level: character.equipment.level,
rank: character.equipment.rank,
item_id: character.equipment.tid,
},
relics: character.relicList.map((relic) => ({
level: relic.level,
relic_id: relic.tid,
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
})),
} as CharacterInfoCardType
}));
}
};
const handleImport = () => {
if (selectedCharacters.length === 0) {
setError(transI18n("pleaseSelectAtLeastOneCharacter"));
return;
}
if (!enkaData) {
setError(transI18n("noDataToImport"));
return;
}
setError("");
const listAvatars = { ...avatars }
const filterData = enkaData.detailInfo.avatarDetailList.filter((character) => selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatarId))
filterData.forEach((character) => {
const newAvatar = { ...listAvatars[character.avatarId.toString()] }
if (Object.keys(newAvatar).length !== 0) {
newAvatar.level = character.level
newAvatar.promotion = character.promotion
newAvatar.data = {
rank: character.rank ?? 0,
skills: character.skillTreeList.reduce((acc, skill) => {
acc[skill.pointId] = skill.level;
return acc;
}, {} as Record<string, number>),
}
const newProfile = converterOneEnkaDataToAvatarStore(character, newAvatar.profileList.length)
if (newProfile) {
newAvatar.profileList.push()
newAvatar.profileSelect = newAvatar.profileList.length - 1
}
setAvatar(newAvatar)
}
})
setIsOpenImport(false)
toast.success(transI18n("importEnkaDataSuccess"))
};
return (
<div className="bg-slate-900 p-6">
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-white text-2xl font-bold mb-6">HSR UID</h1>
<div className="flex gap-4 items-center mb-6">
<input
type="text"
value={uidInput}
onChange={(e) => setUidInput(e.target.value)}
className="bg-slate-800 text-white px-4 py-3 rounded-lg border border-slate-700 flex-1 max-w-md focus:outline-none focus:border-blue-500"
placeholder="Enter UID"
/>
<button onClick={handlerFetchData} className="btn btn-success rounded-lg transition-colors">
{transI18n("getData")}
</button>
{selectedCharacters.length > 0 && (
<button onClick={handleImport} className="btn btn-primary rounded-lg transition-colors">
{transI18n("import")}
</button>
)}
</div>
{Error && (
<div className="text-error text-base mt-2">😭 {Error}</div>
)}
{/* Player Info */}
{enkaData && (
<div className="text-white space-y-2">
<div className="text-lg">Name: {enkaData.detailInfo.nickname}</div>
<div className="text-lg">UID: {enkaData.detailInfo.uid}</div>
<div className="text-lg">Level: {enkaData.detailInfo.level}</div>
</div>
)}
{/* Selection Info */}
<div className="mt-6 flex items-center gap-4 bg-slate-800/50 p-4 rounded-lg">
<span className="text-blue-400 font-medium">
{transI18n("selectedCharacters")}: {selectedCharacters.length}
</span>
<button
onClick={clearSelection}
className="text-error/75 hover:text-error text-sm font-medium px-3 py-1 border border-error/75 rounded hover:bg-error/10 transition-colors cursor-pointer"
>
{transI18n("clearAll")}
</button>
<button onClick={selectAll} className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer">
{transI18n("selectAll")}
</button>
</div>
</div>
{isLoading && (
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
</div>
)}
{/* Character Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{enkaData?.detailInfo.avatarDetailList.map((character, index) => (
<CharacterInfoCard
key={character.avatarId}
character={{
key: character.avatarId,
avatar_id: character.avatarId,
rank: character.rank ?? 0,
level: character.level,
lightcone: {
level: character.equipment.level,
rank: character.equipment.rank,
item_id: character.equipment.tid,
},
relics: character.relicList.map((relic) => ({
level: relic.level,
relic_id: relic.tid,
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
})),
} as CharacterInfoCardType
}
selectedCharacters={selectedCharacters}
onCharacterToggle={handleCharacterToggle}
/>
))}
</div>
</div>
</div>
);
}
+235
View File
@@ -0,0 +1,235 @@
"use client"
import useFreeSRStore from "@/stores/freesrStore";
import useModelStore from "@/stores/modelStore";
import useUserDataStore from "@/stores/userDataStore";
import { CharacterInfoCardType } from "@/types";
import { useState } from "react";
import CharacterInfoCard from "../card/characterInfoCard";
import { freeSRJsonSchema } from "@/zod";
import { toast } from "react-toastify";
import { converterOneFreeSRDataToAvatarStore } from "@/helper";
import { useTranslations } from "next-intl";
export default function FreeSRImport() {
const { avatars, setAvatar, setBattleConfig } = useUserDataStore();
const { isOpenImport, setIsOpenImport } = useModelStore()
const [isLoading, setIsLoading] = useState(false)
const [Error, setError] = useState("")
const { freeSRData, setFreeSRData, selectedCharacters, setSelectedCharacters } = useFreeSRStore()
const transI18n = useTranslations("DataPage")
const handleCharacterToggle = (character: CharacterInfoCardType) => {
if (selectedCharacters.some((selectedCharacter) => selectedCharacter.key === character.key)) {
setSelectedCharacters(selectedCharacters.filter((selectedCharacter) => selectedCharacter.key !== character.key));
return;
}
setSelectedCharacters([...selectedCharacters, character]);
};
const clearSelection = () => {
setSelectedCharacters([]);
};
const selectAll = () => {
if (freeSRData) {
setSelectedCharacters(Object.entries(freeSRData?.avatars).map(([key, character]) => {
const lightcone = freeSRData.lightcones.find((lightcone) => lightcone.equip_avatar === character.avatar_id)
const relics = freeSRData.relics.filter((relic) => relic.equip_avatar === character.avatar_id)
return {
key: character.avatar_id,
avatar_id: character.avatar_id,
rank: character.data.rank ?? 0,
level: character.level,
lightcone: {
level: lightcone?.level ?? 0,
rank: lightcone?.rank ?? 0,
item_id: lightcone?.item_id ?? "",
},
relics: relics.map((relic) => ({
level: relic.level,
relic_id: relic.relic_id,
relic_set_id: relic.relic_set_id,
})),
} as CharacterInfoCardType
}));
}
};
const handlerReadFile = (event: React.ChangeEvent<HTMLInputElement>) => {
setIsLoading(true)
const file = event.target.files?.[0];
if (!file) {
setSelectedCharacters([])
setFreeSRData(null)
setError(transI18n("pleaseSelectAFile"))
setIsLoading(false)
return
}
if (!file.name.endsWith(".json") || file.type !== "application/json") {
setSelectedCharacters([])
setFreeSRData(null)
setError(transI18n("fileMustBeAValidJsonFile"))
setIsLoading(false)
return
}
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target?.result as string);
const parsed = freeSRJsonSchema.parse(data)
setFreeSRData(parsed)
setError("")
setSelectedCharacters(Object.entries(parsed?.avatars || {}).map(([key, character]) => {
const lightcone = parsed?.lightcones.find((lightcone) => lightcone.equip_avatar === character.avatar_id)
const relics = parsed?.relics.filter((relic) => relic.equip_avatar === character.avatar_id)
return {
key: character.avatar_id,
avatar_id: character.avatar_id,
rank: character.data.rank ?? 0,
level: character.level,
lightcone: {
level: lightcone?.level ?? 0,
rank: lightcone?.rank ?? 0,
item_id: lightcone?.item_id ?? "",
},
relics: relics?.map((relic) => ({
level: relic.level,
relic_id: relic.relic_id,
relic_set_id: relic.relic_set_id,
})) ?? [],
} as CharacterInfoCardType
}));
} catch (error) {
setSelectedCharacters([])
setFreeSRData(null)
setError(transI18n("fileIsNotAValidFreeSRJsonFile"))
}
};
reader.readAsText(file);
setIsLoading(false)
}
setIsLoading(false)
};
const handleImport = () => {
if (selectedCharacters.length === 0) {
setError(transI18n("pleaseSelectAtLeastOneCharacter"));
return;
}
if (!freeSRData) {
setError(transI18n("noDataToImport"));
return;
}
setError("");
const listAvatars = { ...avatars }
const filterData = Object.entries(freeSRData?.avatars || {}).filter(([key, character]) => selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id))
filterData.forEach(([key, character]) => {
const newAvatar = { ...listAvatars[character.avatar_id] }
if (Object.keys(newAvatar).length !== 0) {
newAvatar.level = character.level
newAvatar.promotion = character.promotion
newAvatar.data = {
rank: character.data.rank ?? 0,
skills: character.data.skills
}
const newProfile = converterOneFreeSRDataToAvatarStore(freeSRData, newAvatar.profileList.length, character.avatar_id)
if (newProfile) {
newAvatar.profileList.push(newProfile)
newAvatar.profileSelect = newAvatar.profileList.length - 1
}
setAvatar(newAvatar)
}
})
setBattleConfig(freeSRData?.battle_config)
setIsOpenImport(false)
toast.success(transI18n("importFreeSRDataSuccess"))
}
return (
<div className="bg-slate-900 p-6">
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-white text-2xl font-bold mb-6">{transI18n("freeSRImport")}</h1>
<div className="flex gap-4 items-center mb-6">
<fieldset className="fieldset">
<legend className="fieldset-legend">{transI18n("pickAFile")}</legend>
<input type="file" accept=".json" className="file-input file-input-info" onChange={handlerReadFile} />
<label className="label">{transI18n("onlySupportFreeSRJsonFile")}</label>
</fieldset>
{selectedCharacters.length > 0 && (
<button onClick={handleImport} className="btn btn-primary rounded-lg transition-colors">
{transI18n("import")}
</button>
)}
</div>
{Error && (
<div className="text-error text-base mt-2">😭 {Error}</div>
)}
{/* Selection Info */}
<div className="mt-6 flex items-center gap-4 bg-slate-800/50 p-4 rounded-lg">
<span className="text-blue-400 font-medium">
{transI18n("selectedCharacters")}: {selectedCharacters.length}
</span>
<button
onClick={clearSelection}
className="text-error/75 hover:text-error text-sm font-medium px-3 py-1 border border-error/75 rounded hover:bg-error/10 transition-colors cursor-pointer"
>
{transI18n("clearAll")}
</button>
<button onClick={selectAll} className="text-primary/75 hover:text-primary text-sm font-medium px-3 py-1 border border-primary/75 rounded hover:bg-primary/10 transition-colors cursor-pointer">
{transI18n("selectAll")}
</button>
</div>
</div>
{isLoading && (
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
</div>
)}
{/* Character Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{Object.entries(freeSRData?.avatars || {}).map(([key, character]) => {
const lightcone = freeSRData?.lightcones.find((lightcone) => lightcone.equip_avatar === character.avatar_id)
const relics = freeSRData?.relics.filter((relic) => relic.equip_avatar === character.avatar_id)
return (
<CharacterInfoCard
key={character.avatar_id}
character={{
key: character.avatar_id,
avatar_id: character.avatar_id,
rank: character.data.rank ?? 0,
level: character.level,
lightcone: {
level: lightcone?.level ?? 0,
rank: lightcone?.rank ?? 0,
item_id: lightcone?.item_id ?? "",
},
relics: relics?.map((relic) => ({
level: relic.level,
relic_id: relic.relic_id,
relic_set_id: relic.relic_set_id,
})) ?? [],
} as CharacterInfoCardType
}
selectedCharacters={selectedCharacters}
onCharacterToggle={handleCharacterToggle}
/>
)
})}
</div>
</div>
</div>
)
}
+113
View File
@@ -0,0 +1,113 @@
"use client"
import { useEffect, useState } from "react"
import Image from "next/image";
import useLocaleStore from "@/stores/localeStore"
import useLightconeStore from "@/stores/lightconeStore";
import LightconeCard from "../card/lightconeCard";
import useUserDataStore from "@/stores/userDataStore";
import useAvatarStore from "@/stores/avatarStore";
import useModelStore from "@/stores/modelStore";
import { useTranslations } from "next-intl";
export default function LightconeBar() {
const [listPath, setListPath] = useState<Record<string, boolean>>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false })
const [listRank, setListRank] = useState<Record<string, boolean>>({ "3": false, "4": false, "5": false })
const { locale } = useLocaleStore()
const { listLightcone, filter, setFilter } = useLightconeStore()
const { setAvatar, avatars } = useUserDataStore()
const { avatarSelected } = useAvatarStore()
const { setIsOpenLightcone } = useModelStore()
const transI18n = useTranslations("DataPage")
useEffect(() => {
setFilter({
...filter,
locale: locale,
path: Object.keys(listPath).filter((key) => listPath[key]),
rarity: Object.keys(listRank).filter((key) => listRank[key])
})
}, [listPath, listRank, locale])
return (
<div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
{transI18n("lightConeSetting")}
</h3>
</div>
<div className="mt-2 w-full">
<div className="flex items-start flex-col gap-2">
<div>Search</div>
<input
value={filter.name}
onChange={(e) => setFilter({ ...filter, name: e.target.value, locale: locale })}
type="text" placeholder="LightCone Name" className="input input-accent mt-1 w-full"
/>
</div>
<div className="flex items-start flex-col gap-2 mt-2 w-full">
<div>Filter</div>
<div className="grid grid-cols-12 mt-1 w-full">
<div className="grid justify-items-start col-span-8">
<div className="grid grid-cols-3 md:grid-cols-6 lg:grid-cols-8 mb-1 mx-1 gap-2">
{Object.entries(listPath).map(([key, value], index) => (
<div
key={index}
onClick={() => {
setListPath((prev) => ({ ...prev, [key]: !prev[key] }))
}}
className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer"
style={{
backgroundColor: listPath[key] ? "#374151" : "#6B7280"
}}>
<Image src={"https://api.hakush.in/hsr/UI/pathicon/" + key + ".webp"}
alt={key}
className="h-[32px] w-[32px] object-contain rounded-md "
width={200}
height={200} />
</div>
))}
</div>
</div>
<div className="grid justify-items-end col-span-4 w-full">
<div className="grid grid-cols-1 sm:grid-cols-3 mb-1 mx-1 gap-2">
{Object.entries(listRank).map(([key, value], index) => (
<div
key={index}
onClick={() => {
setListRank((prev) => ({ ...prev, [key]: !prev[key] }))
}}
className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer"
style={{
backgroundColor: listRank[key] ? "#374151" : "#6B7280"
}}>
<div className="font-bold text-white h-[32px] w-[32px] text-center flex items-center justify-center">{key}*</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
<div className="grid grid-cols-3 md:grid-cols-6 lg:grid-cols-8 mt-2 gap-2">
{listLightcone.map((item, index) => (
<div key={index} onClick={() => {
if (avatarSelected) {
const avatar = avatars[avatarSelected.id]
avatar.profileList[avatar.profileSelect].lightcone = {
level: 80,
item_id: Number(item.id),
rank: 1,
promotion: 6
}
setAvatar({ ...avatar })
setIsOpenLightcone(false)
}
}}>
<LightconeCard data={item} />
</div>
))}
</div>
</div>
)
}
+16
View File
@@ -0,0 +1,16 @@
'use client'
import { parseRuby } from "@/helper";
interface TextProps {
text: string;
locale: string;
className?: string;
}
export default function ParseText({ text, locale, className }: TextProps) {
if (locale === "ja") {
return <div className={className} dangerouslySetInnerHTML={{ __html: parseRuby(text) }} />;
}
return <div className={className}>{text}</div>;
}
+439
View File
@@ -0,0 +1,439 @@
"use client";
import useRelicStore from '@/stores/relicStore';
import useUserDataStore from '@/stores/userDataStore';
import { AffixDetail, RelicDetail } from '@/types';
import React, { useEffect, useMemo } from 'react';
import SelectCustom from '../select';
import { calcAffixBonus, randomPartition, randomStep, replaceByParam } from '@/helper';
import useAffixStore from '@/stores/affixStore';
import { mapingStats } from '@/lib/constant';
import useAvatarStore from '@/stores/avatarStore';
import useModelStore from '@/stores/modelStore';
import useRelicMakerStore from '@/stores/relicMakerStore';
import { toast } from 'react-toastify';
import { useTranslations } from 'next-intl';
export default function RelicMaker() {
const { avatars, setAvatars } = useUserDataStore()
const { avatarSelected } = useAvatarStore()
const { setIsOpenRelic } = useModelStore()
const { mapRelicInfo } = useRelicStore()
const { mapMainAffix, mapSubAffix} = useAffixStore()
const transI18n = useTranslations("DataPage")
const {
selectedRelicSlot,
selectedRelicSet,
selectedMainStat,
listSelectedSubStats,
selectedRelicLevel,
preSelectedSubStats,
setSelectedRelicSet,
setSelectedMainStat,
setSelectedRelicLevel,
setListSelectedSubStats,
resetHistory,
popHistory,
addHistory,
} = useRelicMakerStore()
const relicSets = useMemo(() => {
const listSet: Record<string, RelicDetail> = {};
for (const [key, value] of Object.entries(mapRelicInfo || {})) {
let isOk = false;
for (const key2 of Object.keys(value.Parts)) {
if (key2.endsWith(selectedRelicSlot)) {
isOk = true;
break;
}
}
if (isOk) {
listSet[key] = value;
}
}
return listSet;
}, [mapRelicInfo, selectedRelicSlot]);
const subAffixOptions = useMemo(() => {
const listSet: Record<string, AffixDetail> = {};
const subAffixMap = mapSubAffix["5"];
const mainAffixMap = mapMainAffix["5" + selectedRelicSlot]
if (Object.keys(subAffixMap || {}).length === 0 || Object.keys(mainAffixMap || {}).length === 0) return listSet;
for (const [key, value] of Object.entries(subAffixMap)) {
if (value.property !== mainAffixMap[selectedMainStat]?.property) {
listSet[key] = value;
}
}
return listSet;
}, [mapSubAffix, mapMainAffix, selectedRelicSlot, selectedMainStat]);
useEffect(() => {
const subAffixMap = mapSubAffix["5"];
const mainAffixMap = mapMainAffix["5" + selectedRelicSlot];
if (!subAffixMap || !mainAffixMap) return;
const mainProp = mainAffixMap[selectedMainStat]?.property;
if (!mainProp) return;
const newSubAffixes = listSelectedSubStats.map(item => ({ ...item }));
let updated = false;
for (let i = 0; i < newSubAffixes.length; i++) {
if (newSubAffixes[i].property === mainProp) {
newSubAffixes[i].affixId = "";
newSubAffixes[i].property = "";
newSubAffixes[i].rollCount = 0;
newSubAffixes[i].stepCount = 0;
updated = true;
}
}
if (updated) setListSelectedSubStats(newSubAffixes);
}, [selectedMainStat, mapSubAffix, mapMainAffix, selectedRelicSlot]);
const exSubAffixOptions = useMemo(() => {
const listSet: Record<string, AffixDetail> = {};
const subAffixMap = mapSubAffix["5"];
const mainAffixMap = mapMainAffix["5" + selectedRelicSlot];
if (!subAffixMap || !mainAffixMap) return listSet;
for (const [key, value] of Object.entries(subAffixMap)) {
const subAffix = listSelectedSubStats.find((item) => item.property === value.property);
if (subAffix && value.property !== mainAffixMap[selectedMainStat]?.property) {
listSet[key] = value;
}
}
return listSet;
}, [mapSubAffix, listSelectedSubStats, mapMainAffix, selectedRelicSlot, selectedMainStat]);
const effectBonus = useMemo(() => {
const affixSet = mapMainAffix?.["5" + selectedRelicSlot];
if (!affixSet) return 0;
const data = affixSet[selectedMainStat];
if (!data) return 0;
const stat = mapingStats?.[data.property];
if (!stat) return 0;
const value = data.base + data.step * selectedRelicLevel;
if (stat.unit === "%") {
return (value * 100).toFixed(1) + "%";
}
return value.toFixed(0) + stat.unit;
}, [mapMainAffix, selectedRelicSlot, selectedMainStat, selectedRelicLevel]);
const handleSubStatChange = (key: string, index: number, rollCount: number, stepCount: number) => {
const newSubAffixes = listSelectedSubStats.map(item => ({ ...item }));
if (!subAffixOptions[key]) {
newSubAffixes[index].affixId = "";
newSubAffixes[index].property = "";
newSubAffixes[index].rollCount = rollCount;
newSubAffixes[index].stepCount = stepCount;
setListSelectedSubStats(newSubAffixes);
addHistory(index, newSubAffixes[index]);
return;
}
newSubAffixes[index].affixId = key;
newSubAffixes[index].property = subAffixOptions[key].property;
newSubAffixes[index].rollCount = rollCount;
newSubAffixes[index].stepCount = stepCount;
setListSelectedSubStats(newSubAffixes);
addHistory(index, newSubAffixes[index]);
};
const handlerRollback = (index: number) => {
if (!preSelectedSubStats[index]) return;
const keys = Object.keys(preSelectedSubStats[index]);
if (keys.length <= 1) return;
const newSubAffixes = listSelectedSubStats.map(item => ({ ...item }));
const listHistory = preSelectedSubStats[index].map(item => ({ ...item }));
const secondLastKey = listHistory.length - 2;
const preSubAffixes = { ...listHistory[secondLastKey] };
newSubAffixes[index].rollCount = preSubAffixes.rollCount;
newSubAffixes[index].stepCount = preSubAffixes.stepCount;
setListSelectedSubStats(newSubAffixes);
popHistory(index);
};
const resetSubStat = (index: number) => {
const newSubAffixes = listSelectedSubStats.map(item => ({ ...item }));
resetHistory(index);
newSubAffixes[index].affixId = "";
newSubAffixes[index].property = "";
newSubAffixes[index].rollCount = 0;
newSubAffixes[index].stepCount = 0;
setListSelectedSubStats(newSubAffixes);
};
const randomizeStats = () => {
const newSubAffixes = listSelectedSubStats.map(item => ({ ...item }));
const exKeys = Object.keys(exSubAffixOptions);
for (let i = 0; i < newSubAffixes.length; i++) {
const keys = Object.keys(subAffixOptions).filter((key) => !exKeys.includes(key));
const randomKey = keys[Math.floor(Math.random() * keys.length )];
exKeys.push(randomKey);
const randomValue = subAffixOptions[randomKey];
newSubAffixes[i].affixId = randomKey;
newSubAffixes[i].property = randomValue.property;
newSubAffixes[i].rollCount = 0;
newSubAffixes[i].stepCount = 0;
}
for (let i = 0; i < newSubAffixes.length; i++) {
addHistory(i, newSubAffixes[i]);
}
setListSelectedSubStats(newSubAffixes);
};
const randomizeRolls = () => {
const newSubAffixes = listSelectedSubStats.map(item => ({ ...item }));
const randomRolls = randomPartition(9, listSelectedSubStats.length);
for (let i = 0; i < listSelectedSubStats.length; i++) {
newSubAffixes[i].rollCount = randomRolls[i];
newSubAffixes[i].stepCount = randomStep(randomRolls[i]);
}
setListSelectedSubStats(newSubAffixes);
for (let i = 0; i < newSubAffixes.length; i++) {
addHistory(i, newSubAffixes[i]);
}
};
const handlerSaveRelic = () => {
const avatar = avatars[avatarSelected?.id || ""];
if (!selectedRelicSet || !selectedMainStat || !selectedRelicLevel || !selectedRelicSlot) {
toast.error(transI18n("pleaseSelectAllOptions"));
return;
};
if (avatar) {
avatar.profileList[avatar.profileSelect].relics[selectedRelicSlot] = {
level: selectedRelicLevel,
relic_id: Number(`6${selectedRelicSet}${selectedRelicSlot}`),
relic_set_id: Number(selectedRelicSet),
main_affix_id: Number(selectedMainStat),
sub_affixes: listSelectedSubStats.map((item) => {
return {
sub_affix_id: Number(item.affixId),
count: item.rollCount,
step: item.stepCount
}
})
}
}
setAvatars({ ...avatars });
setIsOpenRelic(false);
toast.success(transI18n("relicSavedSuccessfully"));
}
return (
<div className="">
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
{transI18n("relicMaker")}
</h3>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-7xl mx-auto">
{/* Left Panel */}
<div className="space-y-6">
{/* Set Configuration */}
<div className="bg-base-100 rounded-xl p-6 border border-slate-700">
<h2 className="text-xl font-bold mb-6 text-warning">{transI18n("mainSettings")}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
{/* Main Stat */}
<div>
<label className="block text-lg font-medium mb-2">{transI18n("mainStat")}</label>
<SelectCustom
customSet={Object.entries(mapMainAffix["5" + selectedRelicSlot] || {}).map(([key, value]) => ({
value: key,
label: mapingStats[value.property].name + " " + mapingStats[value.property].unit,
imageUrl: mapingStats[value.property].icon
}))}
excludeSet={[]}
selectedCustomSet={selectedMainStat}
placeholder={transI18n("selectAMainStat")}
setSelectedCustomSet={setSelectedMainStat}
/>
</div>
{/* Relic Set Selection */}
<div>
<label className="block text-lg font-medium mb-2">{transI18n("set")}</label>
<SelectCustom
customSet={Object.entries(relicSets).map(([key, value]) => ({
value: key,
label: value.Name,
imageUrl: `https://api.hakush.in/hsr/UI/itemfigures/${value.Icon.match(/\d+/)?.[0]}.webp`
}))}
excludeSet={[]}
selectedCustomSet={selectedRelicSet}
placeholder={transI18n("selectASet")}
setSelectedCustomSet={setSelectedRelicSet}
/>
</div>
</div>
{/* Set Bonus Display */}
<div className="mb-6 py-4 bg-base-100 rounded-lg">
{selectedRelicSet !== "" ? Object.entries(mapRelicInfo[selectedRelicSet].RequireNum).map(([key, value]) => (
<div key={key} className="text-blue-300 text-sm mb-1">
<span className="text-info font-bold">{key}-Pc:
<div
className="text-warning leading-relaxed font-bold"
dangerouslySetInnerHTML={{
__html: replaceByParam(
value.Desc,
value.ParamList || []
)
}}
/> </span>
</div>
)) : <p className="text-blue-300 text-sm font-bold mb-1">{transI18n("pleaseSelectASet")}</p>}
</div>
{/* Rarity */}
<div className="grid grid-cols-2 items-center gap-4 mb-6">
<label className="block text-lg font-medium mb-2">{transI18n("rarity")}: {5} </label>
<label className="block text-lg font-medium mb-2">{transI18n("effectBonus")}: <span className="text-warning font-bold">{effectBonus}</span></label>
</div>
{/* Level */}
<div className="mb-6">
<label className="block text-lg font-medium mb-2">{transI18n("level")}</label>
<div className="bg-base-200 rounded-lg p-4">
<input
type="range"
min="0"
max="15"
value={selectedRelicLevel}
onChange={(e) => setSelectedRelicLevel(parseInt(e.target.value))}
className="range range-primary w-full"
/>
<div className="text-center text-2xl font-bold mt-2">{selectedRelicLevel}</div>
</div>
</div>
{/* Save Button */}
<button onClick={handlerSaveRelic} className="btn btn-success w-full">
{transI18n("save")}
</button>
</div>
</div>
{/* Right Panel - Sub Stats */}
<div className="space-y-4">
{/* Total Roll */}
<div className="bg-base-100 rounded-xl p-6 border border-slate-700 z-[1]">
<h3 className="text-lg font-bold mb-4">{transI18n("totalRoll")} {listSelectedSubStats.reduce((a, b) => a + b.rollCount, 0)}</h3>
<div className="grid grid-cols-2 gap-4">
<button
className="btn btn-outline btn-success"
onClick={randomizeStats}
>
{transI18n("randomizeStats")}
</button>
<button
className="btn btn-outline btn-success"
onClick={randomizeRolls}
>
{transI18n("randomizeRolls")}
</button>
</div>
</div>
{listSelectedSubStats.map((v, index) => (
<div key={index} className={`bg-base-100 rounded-xl p-4 border border-slate-700`}>
<div className="grid grid-cols-12 gap-2 items-center">
{/* Stat Selection */}
<div className="col-span-8">
<SelectCustom
customSet={Object.entries(subAffixOptions).map(([key, value]) => ({
value: key,
label: mapingStats[value.property].name + " " + mapingStats[value.property].unit,
imageUrl: mapingStats[value.property].icon
}))}
excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({
value: key,
label: mapingStats[value.property].name + " " + mapingStats[value.property].unit,
imageUrl: mapingStats[value.property].icon
}))}
selectedCustomSet={v.affixId}
placeholder={transI18n("selectASubStat")}
setSelectedCustomSet={(key) => handleSubStatChange(key, index, 0, 0)}
/>
</div>
{/* Current Value */}
<div className="col-span-4 text-center flex items-center justify-center gap-2">
<span className="text-2xl font-mono">+{ }</span>
<div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}</div>
</div>
{/* Roll Values */}
<div className="col-span-12 grid grid-cols-3 gap-1">
<button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)}
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
>
{calcAffixBonus(subAffixOptions[v.affixId], 0 , v.rollCount + 1)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
</button>
<button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 1)}
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
>
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
</button>
<button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 2)}
className="btn btn-sm bg-white text-slate-800 hover:bg-gray-200 border-0"
>
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mapingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}
</button>
</div>
{/* Reset Button & Roll Info */}
<div className="col-span-12 text-center">
<div className="grid grid-cols-2 gap-1 items-center justify-items-start">
<div className="flex items-center gap-2">
<button
className="btn btn-error btn-sm mb-1"
onClick={() => resetSubStat(index)}
>
{transI18n("reset")}
</button>
<button
className="btn btn-warning btn-sm mb-1"
onClick={() => handlerRollback(index)}
>
-
</button>
</div>
<div className="text-lg flex items-center gap-2 text-gray-400">
<span className="font-bold">{transI18n("roll")}: <span className="text-info">{v.rollCount}</span></span>
<span className="font-bold">{transI18n("step")}: <span className="text-info">{v.stepCount}</span></span>
</div>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
};
+293
View File
@@ -0,0 +1,293 @@
"use client"
;
import { useRouter } from 'next/navigation'
import { useCallback, useEffect, useMemo, useState } from "react";
import RelicMaker from "../relicBar";
import { motion } from "framer-motion";
import useUserDataStore from "@/stores/userDataStore";
import useLocaleStore from "@/stores/localeStore";
import { useTranslations } from "next-intl";
import RelicCard from "../card/relicCard";
import useAvatarStore from "@/stores/avatarStore";
import useRelicStore from "@/stores/relicStore";
import useModelStore from '@/stores/modelStore';
import { replaceByParam } from '@/helper';
import useRelicMakerStore from '@/stores/relicMakerStore';
import useAffixStore from '@/stores/affixStore';
export default function RelicsInfo() {
const router = useRouter()
const { avatars, setAvatars } = useUserDataStore()
const { avatarSelected } = useAvatarStore()
const {
setSelectedRelicSlot,
setSelectedMainStat,
setSelectedRelicSet,
setSelectedRelicLevel,
setListSelectedSubStats,
resetHistory,
resetSubStat,
listSelectedSubStats,
} = useRelicMakerStore()
const { mapSubAffix } = useAffixStore()
const { isOpenRelic, setIsOpenRelic } = useModelStore()
const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore();
const { mapRelicInfo } = useRelicStore()
const handleShow = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
setIsOpenRelic(true);
modal.showModal();
}
};
// Close modal handler
const handleCloseModal = (modalId: string) => {
setIsOpenRelic(false);
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
modal.close();
}
};
// Handle ESC key to close modal
useEffect(() => {
if (!isOpenRelic) {
handleCloseModal("action_detail_modal");
return;
};
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpenRelic) {
handleCloseModal("action_detail_modal");
}
};
window.addEventListener('keydown', handleEscKey);
return () => window.removeEventListener('keydown', handleEscKey);
}, [isOpenRelic]);
const getRelic = useCallback((slot: string) => {
const avatar = avatars[avatarSelected?.id || ""];
if (avatar) {
return avatar.profileList[avatar.profileSelect]?.relics[slot] || null;
}
return null;
}, [avatars, avatarSelected]);
const handlerDeleteRelic = (slot: string) => {
const avatar = avatars[avatarSelected?.id || ""];
if (avatar) {
delete avatar.profileList[avatar.profileSelect].relics[slot]
setAvatars({ ...avatars });
}
}
const handlerChangeRelic = (slot: string) => {
const relic = getRelic(slot)
setSelectedRelicSlot(slot)
resetSubStat()
resetHistory(null)
if (relic) {
setSelectedMainStat(relic.main_affix_id.toString())
setSelectedRelicSet(relic.relic_set_id.toString())
setSelectedRelicLevel(relic.level)
const newSubAffixes: { affixId: string, property: string, rollCount: number, stepCount: number }[] = [...listSelectedSubStats];
relic.sub_affixes.forEach((item, index) => {
newSubAffixes[index].affixId = item.sub_affix_id.toString();
newSubAffixes[index].property = mapSubAffix["5"][item.sub_affix_id.toString()]?.property || "";
newSubAffixes[index].rollCount = item.count || 0;
newSubAffixes[index].stepCount = item.step || 0;
})
setListSelectedSubStats(newSubAffixes)
} else {
setSelectedMainStat("")
setSelectedRelicSet("")
setSelectedRelicLevel(15)
const newSubAffixes: { affixId: string, property: string, rollCount: number, stepCount: number }[] = [...listSelectedSubStats];
newSubAffixes.forEach((item) => {
item.affixId = ""
item.property = ""
item.rollCount = 0
item.stepCount = 0
})
setListSelectedSubStats(newSubAffixes)
}
handleShow("action_detail_modal")
}
const getEffects = useMemo(() => {
const avatar = avatars[avatarSelected?.id || ""];
const relicCount: { [key: string]: number } = {};
if (avatar) {
for (const [slot, relic] of Object.entries(avatar.profileList[avatar.profileSelect].relics)) {
if (relicCount[relic.relic_set_id]) {
relicCount[relic.relic_set_id]++;
} else {
relicCount[relic.relic_set_id] = 1;
}
}
}
const listEffects: { key: string, count: number }[] = [];
Object.entries(relicCount).forEach(([key, value]) => {
if (value >= 2) {
listEffects.push({ key: key, count: value });
}
});
return listEffects;
}, [avatars, avatarSelected]);
return (
<div className="max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
<div className="max-w-7xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left Section - Items Grid */}
<div className="lg:col-span-2">
<div className="bg-base-100 rounded-xl p-6 shadow-lg">
<h2 className="flex items-center gap-2 text-2xl font-bold mb-6 text-base-content">
<div className="w-2 h-6 bg-gradient-to-b from-primary to-primary/50 rounded-full"></div>
Equipment
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-2xl mx-auto">
{["1", "2", "3", "4", "5", "6"].map((item, index) => (
<div key={index} className="relative group">
<div
onClick={() => {
handlerChangeRelic(item)
}}
className="cursor-pointer"
>
<RelicCard
slot={item}
avatarId={avatarSelected?.id || ""}
/>
</div>
{/* Action buttons - chỉ hiện khi hover */}
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex gap-1">
{/* Nút chuyển relic */}
<button
onClick={(e) => {
e.stopPropagation()
handlerChangeRelic(item)
}}
className="btn btn-info p-1.5 rounded-full shadow-lg transition-colors duration-200"
title="Chuyển relic"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg>
</button>
{getRelic(item) && (
<button
onClick={(e) => {
e.stopPropagation()
if (window.confirm(`Bạn có chắc muốn xóa relic ở slot ${item}?`)) {
handlerDeleteRelic(item)
}
}}
className="btn btn-error p-1.5 rounded-full shadow-lg transition-colors duration-200"
title="Xóa relic"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
)}
</div>
</div>
))}
</div>
</div>
</div>
{/* Right Section - Stats and Set Effects */}
<div className="space-y-6">
{/* Set Effects Panel */}
<div className="bg-base-100 rounded-xl p-6 shadow-lg">
<h3 className="flex items-center gap-2 text-xl font-bold mb-4 text-base-content">
<div className="w-2 h-6 bg-gradient-to-b from-primary to-primary/50 rounded-full"></div>
Set Effects
</h3>
<div className="space-y-6">
{getEffects.map((setEffect, index) => {
const relicInfo = mapRelicInfo[setEffect.key];
if (!relicInfo) return null;
return (
<div key={index} className="space-y-3">
<div className="flex items-center gap-2">
<div
className="font-bold text-warning"
dangerouslySetInnerHTML={{
__html: replaceByParam(
relicInfo.Name,
[]
)
}}
/>
{setEffect.count && (
<span className={`text-sm text-info`}>
({setEffect.count})
</span>
)}
</div>
<div className="space-y-2 pl-4">
{Object.entries(relicInfo.RequireNum).map(([requireNum, value]) => {
if (Number(requireNum) > Number(setEffect.count)) return null;
return (
<div key={requireNum} className="space-y-1">
<div className={`font-medium text-success`}>
{requireNum}-PC:
</div>
<div
className="text-sm text-base-content/80 leading-relaxed pl-4"
dangerouslySetInnerHTML={{
__html: replaceByParam(
value.Desc,
value.ParamList || []
)
}}
/>
</div>
)
})}
</div>
</div>
)
})}
</div>
</div>
</div>
</div>
</div>
<dialog id="action_detail_modal" className="modal lg:backdrop-blur-sm z-10">
<div className="modal-box w-11/12 max-w-7xl bg-base-100 text-base-content border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="sticky top-0 z-10">
<motion.button
whileHover={{ scale: 1.1, rotate: 90 }}
transition={{ duration: 0.2 }}
className="btn btn-circle btn-md absolute right-2 top-2 bg-red-600 hover:bg-red-700 text-white border-none"
onClick={() => handleCloseModal("action_detail_modal")}
>
</motion.button>
</div>
<RelicMaker />
</div>
</dialog>
</div>
);
}
+67
View File
@@ -0,0 +1,67 @@
'use client'
import Select, { SingleValue } from 'react-select'
import Image from 'next/image'
import useLocaleStore from '@/stores/localeStore'
import ParseText from '../parseText'
export type SelectOption = {
value: string
label: string
imageUrl: string
}
type SelectCustomProp = {
customSet: SelectOption[]
excludeSet: SelectOption[]
selectedCustomSet: string
placeholder: string
setSelectedCustomSet: (value: string) => void
}
export default function SelectCustom({ customSet, excludeSet, selectedCustomSet, placeholder, setSelectedCustomSet }: SelectCustomProp) {
const options: SelectOption[] = customSet
const { locale } = useLocaleStore()
const customStyles = {
option: (provided: any) => ({
...provided,
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '8px'
}),
singleValue: (provided: any) => ({
...provided,
display: 'flex',
alignItems: 'center',
gap: '8px'
}),
menuPortal: (provided: any) => ({ ...provided, zIndex: 9999 }),
menu: (provided: any) => ({ ...provided, zIndex: 9999 })
}
const formatOptionLabel = (option: SelectOption) => (
<div className="flex items-center gap-1 bg-slate-400 w-full h-full z-50">
<Image src={option.imageUrl} alt="" width={125} height={125} className="w-8 h-8 object-contain" />
<ParseText className='text-black font-bold' text={option.label} locale={locale} />
</div>
)
return (
<Select
options={options.filter(opt => !excludeSet.some(ex => ex.value === opt.value))}
value={options.find(opt => {
return opt.value === selectedCustomSet}) || null}
onChange={(selected: SingleValue<SelectOption>) => {
console.log(selected);
setSelectedCustomSet(selected?.value || '')
}}
formatOptionLabel={formatOptionLabel}
styles={customStyles}
placeholder={placeholder}
className="my-react-select-container"
classNamePrefix="my-react-select"
isSearchable
isClearable
/>
)
}
@@ -0,0 +1,8 @@
"use client";
import { PropsWithChildren, useContext } from "react";
import { ThemeContext } from "./themeContext";
export function ClientThemeWrapper({ children }: PropsWithChildren) {
const { theme } = useContext(ThemeContext);
return <div data-theme={theme} className="h-full">{children}</div>;
}
+2
View File
@@ -0,0 +1,2 @@
export * from "./clientThemeWrapper"
export * from "./themeContext"
@@ -0,0 +1,41 @@
"use client";
import { createContext, PropsWithChildren, useEffect, useState } from "react";
interface ThemeContextType {
theme?: string;
changeTheme?: (nextTheme: string | null) => void;
}
export const ThemeContext = createContext<ThemeContextType>({});
export const ThemeProvider = ({ children }: PropsWithChildren) => {
const [theme, setTheme] = useState<string>("light");
useEffect(() => {
if (typeof window !== "undefined") {
const storedTheme = localStorage.getItem("theme");
if (storedTheme) setTheme(storedTheme);
}
}, []);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const changeTheme = (nextTheme: string | null) => {
console.log(nextTheme)
if (nextTheme) {
setTheme(nextTheme);
if (typeof window !== "undefined") {
localStorage.setItem("theme", nextTheme);
}
} else {
setTheme((prev) => (prev === "light" ? "night" : "light"));
if (typeof window !== "undefined") {
localStorage.setItem("theme", theme);
}
}
};
return (
<ThemeContext.Provider value={{ theme, changeTheme }}>
{children}
</ThemeContext.Provider>
);
};
+50
View File
@@ -0,0 +1,50 @@
import { mapingStats } from "@/lib/constant"
import { AffixDetail } from "@/types"
export function calcPromotion(level: number) {
if (level < 20) {
return 0
}
if (level < 30) {
return 1
}
if (level < 40) {
return 2
}
if (level < 50) {
return 3
}
if (level < 60) {
return 4
}
if (level < 70) {
return 5
}
return 6
}
export function calcRarity(rarity: string) {
if (rarity.includes("5")) {
return 5
}
if (rarity.includes("4")) {
return 4
}
if (rarity.includes("3")) {
return 3
}
return 1
}
export const calcAffixBonus = (affix: AffixDetail, stepCount: number, rollCount: number) => {
const data = affix;
if (!data) return 0;
if (mapingStats?.[data.property].unit === "%") {
return ((data.base * rollCount + data.step * stepCount) * 100).toFixed(1);
}
if (mapingStats?.[data.property].name === "SPD") {
return (data.base * rollCount + data.step * stepCount).toFixed(1);
}
return (data.base * rollCount + data.step * stepCount).toFixed(0);
}
+87
View File
@@ -0,0 +1,87 @@
"use client"
import { SendDataThroughProxy, SendDataToServer } from "@/lib/api"
import useConnectStore from "@/stores/connectStore"
import useUserDataStore from "@/stores/userDataStore"
import { converterToFreeSRJson } from "./converterToFreeSRJson"
import { psResponseSchema } from "@/zod"
export const connectToPS = async (): Promise<{ success: boolean, message: string }> => {
const {
connectionType,
privateType,
serverUrl,
username,
password
} = useConnectStore.getState()
let urlQuery = serverUrl
if (!urlQuery.startsWith("http://") && !urlQuery.startsWith("https://")) {
urlQuery = `http://${urlQuery}`
}
if (connectionType === "FireflyGo") {
urlQuery = "http://localhost:21000/sync"
} else if (connectionType === "Other" && privateType === "Server") {
const response = await SendDataThroughProxy({data: {username, password, serverUrl, data: null, method: "POST"}})
if (response instanceof Error) {
return { success: false, message: response.message }
} else if (response.error) {
return { success: false, message: response.error }
} else {
const parsed = psResponseSchema.safeParse(response.data)
if (!parsed.success) {
return { success: false, message: "Invalid response schema" }
}
return { success: true, message: "" }
}
}
const response = await SendDataToServer(username, password, urlQuery, null)
if (typeof response === "string") {
return { success: false, message: response }
} else if (response.status != 200) {
return { success: false, message: response.message }
} else {
return { success: true, message: "" }
}
}
export const syncDataToPS = async (): Promise<{ success: boolean, message: string }> => {
const {
connectionType,
privateType,
serverUrl,
username,
password
} = useConnectStore.getState()
const {avatars, battle_config} = useUserDataStore.getState()
const data = converterToFreeSRJson(avatars, battle_config)
let urlQuery = serverUrl
if (!urlQuery.startsWith("http://") && !urlQuery.startsWith("https://")) {
urlQuery = `http://${urlQuery}`
}
if (connectionType === "FireflyGo") {
urlQuery = "http://localhost:21000/sync"
} else if (connectionType === "Other" && privateType === "Server") {
const response = await SendDataThroughProxy({data: {username, password, serverUrl, data, method: "POST"}})
if (response instanceof Error) {
return { success: false, message: response.message }
} else if (response.error) {
return { success: false, message: response.error }
} else {
const parsed = psResponseSchema.safeParse(response.data)
if (!parsed.success) {
return { success: false, message: "Invalid response schema" }
}
return { success: true, message: "" }
}
}
const response = await SendDataToServer(username, password, urlQuery, data)
if (typeof response === "string") {
return { success: false, message: response }
} else if (response.status != 200) {
return { success: false, message: response.message }
} else {
return { success: true, message: "" }
}
}
+89
View File
@@ -0,0 +1,89 @@
import { CharacterBasic, CharacterBasicRaw, LightConeBasic, LightConeBasicRaw, RelicBasic, RelicBasicEffect, RelicBasicRaw } from "@/types";
export function convertRelicSet(id: string, item: RelicBasicRaw): RelicBasic {
let lang = new Map<string, string>([
['en', item.en],
['kr', item.kr],
['cn', item.cn],
['jp', item.jp]
]);
const setRelic = new Map<string, RelicBasicEffect>();
Object.entries(item.set).forEach(([key, value]) => {
setRelic.set(key, {
ParamList: value.ParamList,
lang: new Map<string, string>([
['en', value.en],
['kr', value.kr],
['cn', value.cn],
['jp', value.jp]
])
});
});
const result: RelicBasic = {
icon: item.icon,
lang: lang,
id: id,
set: setRelic
};
return result;
}
export function convertLightcone(id: string, item: LightConeBasicRaw): LightConeBasic {
let lang = new Map<string, string>([
['en', item.en],
['kr', item.kr],
['cn', item.cn],
['jp', item.jp]
]);
const result: LightConeBasic = {
rank: item.rank,
baseType: item.baseType,
desc: item.desc,
lang: lang,
id: id
};
return result;
}
export function convertAvatar(id: string, item: CharacterBasicRaw): CharacterBasic {
let lang = new Map<string, string>([
['en', item.en],
['kr', item.kr],
['cn', item.cn],
['jp', item.jp]
]);
let text = ""
if (Number(id) % 2 === 0 && Number(id) > 8000) {
text = `Female ${item.damageType} MC`
} else if (Number(id) > 8000) {
text = `Male ${item.damageType} MC`
}
if (text !== "") {
lang.set("en", text)
lang.set("kr", text)
lang.set("cn", text)
lang.set("jp", text)
}
const result: CharacterBasic = {
release: item.release,
icon: item.icon,
rank: item.rank,
baseType: item.baseType,
damageType: item.damageType,
desc: item.desc,
lang: lang,
id: id
};
return result;
}
+100
View File
@@ -0,0 +1,100 @@
import { AvatarEnkaDetail, AvatarProfileStore, AvatarStore, CharacterDetail, EnkaResponse, FreeSRJson, RelicStore } from "@/types";
function safeNumber(val: any, fallback = 0): number {
const num = Number(val);
return Number.isFinite(num) && num !== 0 ? num : fallback;
}
export function converterToAvatarStore(data: Record<string, CharacterDetail>): { [key: string]: AvatarStore } {
return Object.fromEntries(
Object.entries(data).map(([key, value]) => [
key,
{
owner_uid: 0,
avatar_id: Number(key),
data: {
rank: 0,
skills: Object.entries(value.SkillTrees).reduce((acc, [pointName, dataPointEntry]) => {
const firstEntry = Object.values(dataPointEntry)[0];
if (firstEntry) {
acc[firstEntry.PointID] = firstEntry.MaxLevel;
}
return acc;
}, {} as Record<string, number>)
},
level: 80,
promotion: 6,
techniques: [],
sp_max: safeNumber(value.SPNeed, 100),
can_change_sp: safeNumber(value.SPNeed) !== 0,
sp_value: Math.ceil(safeNumber(value.SPNeed) / 2),
profileSelect: 0,
enhanced: "",
profileList: [{
profile_name: "Default",
lightcone: null,
relics: {} as Record<string, RelicStore>
} as AvatarProfileStore]
}
])
) as { [key: string]: AvatarStore }
}
export function converterOneEnkaDataToAvatarStore(data: AvatarEnkaDetail, count: number): AvatarProfileStore | null {
if (!data.equipment && (!data.relicList || data.relicList.length === 0)) return null
const profile: AvatarProfileStore = {
profile_name: `Enka Profile ${count}`,
lightcone: {
level: data.equipment.level,
item_id: data.equipment.tid,
rank: data.equipment.rank,
promotion: data.equipment.promotion,
},
relics: Object.fromEntries(data.relicList.map((relic) => [relic.tid.toString()[relic.tid.toString().length - 1], {
level: relic.level,
relic_id: relic.tid,
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
main_affix_id: relic.mainAffixId,
sub_affixes: relic.subAffixList.map((subAffix) => ({
sub_affix_id: subAffix.affixId,
count: subAffix.cnt,
step: subAffix.step ?? 0
}))
}]))
}
return profile
}
export function converterOneFreeSRDataToAvatarStore(data: FreeSRJson, count: number , avatar_id: number): AvatarProfileStore | null {
const lightcone = data.lightcones.find((lightcone) => lightcone.equip_avatar === avatar_id)
const relics = data.relics.filter((relic) => relic.equip_avatar === avatar_id)
if (!lightcone && (!relics || relics.length === 0)) return null
const relicsMap = {} as Record<string, RelicStore>
relics.forEach((relic) => {
relicsMap[relic.relic_id.toString()[relic.relic_id.toString().length - 1]] = {
level: relic.level,
relic_id: relic.relic_id,
relic_set_id: relic.relic_set_id,
main_affix_id: relic.main_affix_id,
sub_affixes: relic.sub_affixes.map((subAffix) => ({
sub_affix_id: subAffix.sub_affix_id,
count: subAffix.count,
step: subAffix.step ?? 0
}))
}
})
const profile: AvatarProfileStore = {
profile_name: `FreeSR Profile ${count}`,
lightcone: {
level: lightcone?.level ?? 0,
item_id: lightcone?.item_id ?? 0,
rank: lightcone?.rank ?? 0,
promotion: lightcone?.promotion ?? 0,
},
relics: relicsMap
}
return profile
}
+71
View File
@@ -0,0 +1,71 @@
import { AvatarJson, AvatarStore, BattleConfigJson, BattleConfigStore, FreeSRJson, LightconeJson, RelicJson } from "@/types";
import useUserDataStore from "@/stores/userDataStore";
export function converterToFreeSRJson(avatars: Record<string, AvatarStore>, battle_config: BattleConfigStore): FreeSRJson {
const lightcones: LightconeJson[] = []
const relics: RelicJson[] = []
const battleJson: BattleConfigJson = {
battle_type: battle_config.battle_type,
blessings: battle_config.blessings,
custom_stats: battle_config.custom_stats,
cycle_count: battle_config.cycle_count,
stage_id: battle_config.stage_id,
path_resonance_id: battle_config.path_resonance_id,
monsters: battle_config.monsters,
}
const avatarsJson: { [key: string]: AvatarJson } = {}
let internalUidLightcone = 0
let internalUidRelic = 0
Object.entries(avatars).forEach(([avatarId, avatar]) => {
avatarsJson[avatarId] = {
owner_uid: avatar.owner_uid,
avatar_id: avatar.avatar_id,
data: avatar.data,
level: avatar.level,
promotion: avatar.promotion,
techniques: avatar.techniques,
sp_value: avatar.sp_value,
sp_max: avatar.sp_max,
}
const currentProfile = avatar.profileList[avatar.profileSelect]
if (currentProfile.lightcone && currentProfile.lightcone.item_id !== 0) {
const newLightcone: LightconeJson = {
level: currentProfile.lightcone.level,
item_id: currentProfile.lightcone.item_id,
rank: currentProfile.lightcone.rank,
promotion: currentProfile.lightcone.promotion,
internal_uid: internalUidLightcone,
equip_avatar: avatar.avatar_id,
}
internalUidLightcone++
lightcones.push(newLightcone)
}
if (currentProfile.relics) {
["1", "2", "3", "4", "5", "6"].forEach(slot => {
const relic = currentProfile.relics[slot]
if (relic && relic.relic_id !== 0) {
const newRelic: RelicJson = {
level: relic.level,
relic_id: relic.relic_id,
relic_set_id: relic.relic_set_id,
main_affix_id: relic.main_affix_id,
sub_affixes: relic.sub_affixes,
internal_uid: internalUidRelic,
equip_avatar: avatar.avatar_id,
}
internalUidRelic++
relics.push(newRelic)
}
})
}
})
return {
lightcones,
relics,
avatars: avatarsJson,
battle_config: battleJson,
}
}
+15
View File
@@ -0,0 +1,15 @@
import useUserDataStore from "@/stores/userDataStore";
import useAvatarStore from "@/stores/avatarStore";
import { CharacterDetail } from "@/types";
export function getAvatarNotExist(): Record<string, CharacterDetail> {
const { avatars } = useUserDataStore.getState()
const { mapAvatarInfo } = useAvatarStore.getState()
const listAvatarId = Object.keys(avatars)
const listAvatarNotExist = Object.keys(mapAvatarInfo).filter((avatarId) => !listAvatarId.includes(avatarId))
return listAvatarNotExist.reduce((acc, avatarId) => {
acc[avatarId] = mapAvatarInfo[avatarId]
return acc
}, {} as Record<string, CharacterDetail>)
}
+45
View File
@@ -0,0 +1,45 @@
import { listCurrentLanguage } from "@/lib/constant";
import { CharacterBasic, LightConeBasic } from "@/types";
export function getNameChar(locale: string, data: CharacterBasic | undefined): string {
if (!data) {
return ""
}
if (!listCurrentLanguage.hasOwnProperty(locale)) {
return ""
}
let text = data.lang.get(listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase()) ?? "";
if (!text) {
text = data.lang.get("en") ?? "";
}
if (Number(data.id) % 2 === 0 && Number(data.id) > 8000) {
text = `Female ${data.damageType} MC`
} else if (Number(data.id) > 8000) {
text = `Male ${data.damageType} MC`
}
return text
}
export function getNameLightcone(locale: string, data: LightConeBasic | undefined): string {
if (!data) {
return ""
}
if (!listCurrentLanguage.hasOwnProperty(locale)) {
return ""
}
let text = data.lang.get(listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase()) ?? "";
if (!text) {
text = data.lang.get("en") ?? "";
}
return text
}
export function parseRuby(text: string): string {
const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs;
return text.replace(rubyRegex, (_match, furigana, kanji) => {
return `<ruby>${kanji}<rt>${furigana}</rt></ruby>`;
});
}

Some files were not shown because too many files have changed in this diff Show More