ith5cn 1 miesiąc temu
commit
7786da847c
47 zmienionych plików z 6271 dodań i 0 usunięć
  1. 8 0
      .env
  2. 24 0
      .gitignore
  3. 13 0
      index.html
  4. 2835 0
      package-lock.json
  5. 32 0
      package.json
  6. 190 0
      public/demo-game.html
  7. 91 0
      public/game-sdk.js
  8. 1 0
      public/vite.svg
  9. 26 0
      readme.md
  10. 105 0
      src/App.tsx
  11. 27 0
      src/api/index.ts
  12. 67 0
      src/api/login.ts
  13. 24 0
      src/api/payment.ts
  14. 14 0
      src/api/register.ts
  15. 12 0
      src/api/role.ts
  16. 98 0
      src/components/FloatingButton.tsx
  17. 30 0
      src/components/forget-password/index.tsx
  18. 156 0
      src/components/forget-password/step1.tsx
  19. 131 0
      src/components/forget-password/step2.tsx
  20. 10 0
      src/components/init/index.ts
  21. 11 0
      src/components/init/native-init.ts
  22. 144 0
      src/components/login/account-login.tsx
  23. 41 0
      src/components/login/index.tsx
  24. 184 0
      src/components/login/phone-login.tsx
  25. 137 0
      src/components/login/select-account.tsx
  26. 158 0
      src/components/payment/index.tsx
  27. 78 0
      src/components/real-name/index.tsx
  28. 177 0
      src/components/register/account-register.tsx
  29. 22 0
      src/components/register/index.tsx
  30. 221 0
      src/components/register/phone-register.tsx
  31. 10 0
      src/components/report/index.ts
  32. 55 0
      src/components/slide-bar/index.tsx
  33. 69 0
      src/index.css
  34. 67 0
      src/lib/EventBus.ts
  35. 79 0
      src/lib/PostMessageBridge.ts
  36. 10 0
      src/main.tsx
  37. 6 0
      src/store/index.ts
  38. 38 0
      src/store/modal-atom.ts
  39. 45 0
      src/store/user-atom.ts
  40. 13 0
      src/types/window.d.ts
  41. 1 0
      src/typescript.svg
  42. 232 0
      src/utils/common-params.ts
  43. 25 0
      src/utils/index.ts
  44. 34 0
      src/utils/rem.ts
  45. 467 0
      src/utils/request.ts
  46. 27 0
      tsconfig.json
  47. 26 0
      vite.config.ts

+ 8 - 0
.env

@@ -0,0 +1,8 @@
+VITE_APP_SIGN_KEY=03fb565b8c319ceb3192f89cf11c0cc7
+VITE_APP_TITLE = H5游戏
+VITE_APP_OPEN_PROXY = false
+VITE_APP_BASE = /
+VITE_APP_TOKEN_PREFIX = token
+VITE_SDK_VERSION = 1.0.0
+VITE_APP_PROXY_PREFIX = /dev
+VITE_APP_BASE_URL = https://sdk-api.gzshisan.com/

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>h5sdk</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>

+ 2835 - 0
package-lock.json

@@ -0,0 +1,2835 @@
+{
+  "name": "h5sdk",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "h5sdk",
+      "version": "0.0.0",
+      "dependencies": {
+        "axios": "^1.13.6",
+        "clsx": "^2.1.1",
+        "crypto-js": "^4.2.0",
+        "jotai": "^2.18.1",
+        "lucide-react": "^0.577.0",
+        "react": "^19.2.4",
+        "react-dom": "^19.2.4",
+        "react-draggable": "^4.5.0",
+        "tailwind-merge": "^3.5.0"
+      },
+      "devDependencies": {
+        "@tailwindcss/vite": "^4.2.1",
+        "@types/crypto-js": "^4.2.2",
+        "@types/react": "^19.2.14",
+        "@types/react-dom": "^19.2.3",
+        "@vitejs/plugin-react": "^5.1.4",
+        "tailwindcss": "^4.2.1",
+        "typescript": "~5.9.3",
+        "vite": "^7.3.1"
+      }
+    },
+    "node_modules/@babel/code-frame": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+      "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.28.5",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/compat-data": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+      "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+      "devOptional": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+      "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.29.0",
+        "@babel/generator": "^7.29.0",
+        "@babel/helper-compilation-targets": "^7.28.6",
+        "@babel/helper-module-transforms": "^7.28.6",
+        "@babel/helpers": "^7.28.6",
+        "@babel/parser": "^7.29.0",
+        "@babel/template": "^7.28.6",
+        "@babel/traverse": "^7.29.0",
+        "@babel/types": "^7.29.0",
+        "@jridgewell/remapping": "^2.3.5",
+        "convert-source-map": "^2.0.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/babel"
+      }
+    },
+    "node_modules/@babel/generator": {
+      "version": "7.29.1",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+      "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.0",
+        "@babel/types": "^7.29.0",
+        "@jridgewell/gen-mapping": "^0.3.12",
+        "@jridgewell/trace-mapping": "^0.3.28",
+        "jsesc": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-compilation-targets": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+      "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/compat-data": "^7.28.6",
+        "@babel/helper-validator-option": "^7.27.1",
+        "browserslist": "^4.24.0",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-globals": {
+      "version": "7.28.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+      "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+      "devOptional": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-imports": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+      "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/traverse": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-transforms": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+      "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.28.6",
+        "@babel/helper-validator-identifier": "^7.28.5",
+        "@babel/traverse": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-plugin-utils": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+      "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "devOptional": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "devOptional": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-option": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+      "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+      "devOptional": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helpers": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+      "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/template": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+      "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-self": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+      "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-source": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+      "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/template": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+      "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.28.6",
+        "@babel/parser": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+      "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.29.0",
+        "@babel/generator": "^7.29.0",
+        "@babel/helper-globals": "^7.28.0",
+        "@babel/parser": "^7.29.0",
+        "@babel/template": "^7.28.6",
+        "@babel/types": "^7.29.0",
+        "debug": "^4.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+      "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+      "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+      "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+      "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+      "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+      "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+      "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+      "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+      "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+      "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+      "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+      "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+      "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+      "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+      "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+      "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+      "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+      "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+      "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+      "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+      "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openharmony-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+      "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+      "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+      "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+      "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+      "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.13",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+      "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/remapping": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+      "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "devOptional": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "devOptional": true,
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.31",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+      "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-rc.3",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+      "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+      "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+      "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+      "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+      "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+      "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+      "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+      "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+      "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+      "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+      "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+      "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-musl": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+      "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+      "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-musl": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+      "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+      "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+      "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+      "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+      "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+      "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openbsd-x64": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+      "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+      "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+      "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+      "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+      "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+      "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@tailwindcss/node": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
+      "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/remapping": "^2.3.5",
+        "enhanced-resolve": "^5.19.0",
+        "jiti": "^2.6.1",
+        "lightningcss": "1.31.1",
+        "magic-string": "^0.30.21",
+        "source-map-js": "^1.2.1",
+        "tailwindcss": "4.2.1"
+      }
+    },
+    "node_modules/@tailwindcss/oxide": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
+      "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 20"
+      },
+      "optionalDependencies": {
+        "@tailwindcss/oxide-android-arm64": "4.2.1",
+        "@tailwindcss/oxide-darwin-arm64": "4.2.1",
+        "@tailwindcss/oxide-darwin-x64": "4.2.1",
+        "@tailwindcss/oxide-freebsd-x64": "4.2.1",
+        "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
+        "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
+        "@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
+        "@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
+        "@tailwindcss/oxide-linux-x64-musl": "4.2.1",
+        "@tailwindcss/oxide-wasm32-wasi": "4.2.1",
+        "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
+        "@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-android-arm64": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz",
+      "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-darwin-arm64": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz",
+      "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-darwin-x64": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz",
+      "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-freebsd-x64": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz",
+      "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz",
+      "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz",
+      "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz",
+      "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
+      "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
+      "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz",
+      "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==",
+      "bundleDependencies": [
+        "@napi-rs/wasm-runtime",
+        "@emnapi/core",
+        "@emnapi/runtime",
+        "@tybys/wasm-util",
+        "@emnapi/wasi-threads",
+        "tslib"
+      ],
+      "cpu": [
+        "wasm32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@emnapi/core": "^1.8.1",
+        "@emnapi/runtime": "^1.8.1",
+        "@emnapi/wasi-threads": "^1.1.0",
+        "@napi-rs/wasm-runtime": "^1.1.1",
+        "@tybys/wasm-util": "^0.10.1",
+        "tslib": "^2.8.1"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
+      "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz",
+      "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 20"
+      }
+    },
+    "node_modules/@tailwindcss/vite": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
+      "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@tailwindcss/node": "4.2.1",
+        "@tailwindcss/oxide": "4.2.1",
+        "tailwindcss": "4.2.1"
+      },
+      "peerDependencies": {
+        "vite": "^5.2.0 || ^6 || ^7"
+      }
+    },
+    "node_modules/@types/babel__core": {
+      "version": "7.20.5",
+      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+      "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.20.7",
+        "@babel/types": "^7.20.7",
+        "@types/babel__generator": "*",
+        "@types/babel__template": "*",
+        "@types/babel__traverse": "*"
+      }
+    },
+    "node_modules/@types/babel__generator": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+      "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__template": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+      "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__traverse": {
+      "version": "7.28.0",
+      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+      "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.28.2"
+      }
+    },
+    "node_modules/@types/crypto-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
+      "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/react": {
+      "version": "19.2.14",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+      "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "csstype": "^3.2.2"
+      }
+    },
+    "node_modules/@types/react-dom": {
+      "version": "19.2.3",
+      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+      "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "^19.2.0"
+      }
+    },
+    "node_modules/@vitejs/plugin-react": {
+      "version": "5.1.4",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
+      "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/core": "^7.29.0",
+        "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+        "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+        "@rolldown/pluginutils": "1.0.0-rc.3",
+        "@types/babel__core": "^7.20.5",
+        "react-refresh": "^0.18.0"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+      }
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/axios": {
+      "version": "1.13.6",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
+      "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.11",
+        "form-data": "^4.0.5",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/baseline-browser-mapping": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
+      "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
+      "devOptional": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "baseline-browser-mapping": "dist/cli.cjs"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+      "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+      "devOptional": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "baseline-browser-mapping": "^2.9.0",
+        "caniuse-lite": "^1.0.30001759",
+        "electron-to-chromium": "^1.5.263",
+        "node-releases": "^2.0.27",
+        "update-browserslist-db": "^1.2.0"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001777",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
+      "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
+      "devOptional": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "CC-BY-4.0"
+    },
+    "node_modules/clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "devOptional": true,
+      "license": "MIT"
+    },
+    "node_modules/crypto-js": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+      "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+      "license": "MIT"
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+      "devOptional": true,
+      "license": "MIT"
+    },
+    "node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/detect-libc": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.307",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
+      "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==",
+      "devOptional": true,
+      "license": "ISC"
+    },
+    "node_modules/enhanced-resolve": {
+      "version": "5.20.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
+      "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.2.4",
+        "tapable": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+      "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.27.3",
+        "@esbuild/android-arm": "0.27.3",
+        "@esbuild/android-arm64": "0.27.3",
+        "@esbuild/android-x64": "0.27.3",
+        "@esbuild/darwin-arm64": "0.27.3",
+        "@esbuild/darwin-x64": "0.27.3",
+        "@esbuild/freebsd-arm64": "0.27.3",
+        "@esbuild/freebsd-x64": "0.27.3",
+        "@esbuild/linux-arm": "0.27.3",
+        "@esbuild/linux-arm64": "0.27.3",
+        "@esbuild/linux-ia32": "0.27.3",
+        "@esbuild/linux-loong64": "0.27.3",
+        "@esbuild/linux-mips64el": "0.27.3",
+        "@esbuild/linux-ppc64": "0.27.3",
+        "@esbuild/linux-riscv64": "0.27.3",
+        "@esbuild/linux-s390x": "0.27.3",
+        "@esbuild/linux-x64": "0.27.3",
+        "@esbuild/netbsd-arm64": "0.27.3",
+        "@esbuild/netbsd-x64": "0.27.3",
+        "@esbuild/openbsd-arm64": "0.27.3",
+        "@esbuild/openbsd-x64": "0.27.3",
+        "@esbuild/openharmony-arm64": "0.27.3",
+        "@esbuild/sunos-x64": "0.27.3",
+        "@esbuild/win32-arm64": "0.27.3",
+        "@esbuild/win32-ia32": "0.27.3",
+        "@esbuild/win32-x64": "0.27.3"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "devOptional": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/fdir": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.11",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+      "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+      "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+      "devOptional": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "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"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/jiti": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+      "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "jiti": "lib/jiti-cli.mjs"
+      }
+    },
+    "node_modules/jotai": {
+      "version": "2.18.1",
+      "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.18.1.tgz",
+      "integrity": "sha512-e0NOzK+yRFwHo7DOp0DS0Ycq74KMEAObDWFGmfEL28PD9nLqBTt3/Ug7jf9ca72x0gC9LQZG9zH+0ISICmy3iA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.20.0"
+      },
+      "peerDependencies": {
+        "@babel/core": ">=7.0.0",
+        "@babel/template": ">=7.0.0",
+        "@types/react": ">=17.0.0",
+        "react": ">=17.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@babel/core": {
+          "optional": true
+        },
+        "@babel/template": {
+          "optional": true
+        },
+        "@types/react": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "license": "MIT"
+    },
+    "node_modules/jsesc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+      "devOptional": true,
+      "license": "MIT",
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "devOptional": true,
+      "license": "MIT",
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/lightningcss": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
+      "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
+      "dev": true,
+      "license": "MPL-2.0",
+      "dependencies": {
+        "detect-libc": "^2.0.3"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "lightningcss-android-arm64": "1.31.1",
+        "lightningcss-darwin-arm64": "1.31.1",
+        "lightningcss-darwin-x64": "1.31.1",
+        "lightningcss-freebsd-x64": "1.31.1",
+        "lightningcss-linux-arm-gnueabihf": "1.31.1",
+        "lightningcss-linux-arm64-gnu": "1.31.1",
+        "lightningcss-linux-arm64-musl": "1.31.1",
+        "lightningcss-linux-x64-gnu": "1.31.1",
+        "lightningcss-linux-x64-musl": "1.31.1",
+        "lightningcss-win32-arm64-msvc": "1.31.1",
+        "lightningcss-win32-x64-msvc": "1.31.1"
+      }
+    },
+    "node_modules/lightningcss-android-arm64": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
+      "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-darwin-arm64": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
+      "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-darwin-x64": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
+      "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-freebsd-x64": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
+      "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm-gnueabihf": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
+      "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm64-gnu": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
+      "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm64-musl": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
+      "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-x64-gnu": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
+      "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-x64-musl": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
+      "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-win32-arm64-msvc": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
+      "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-win32-x64-msvc": {
+      "version": "1.31.1",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
+      "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "devOptional": true,
+      "license": "ISC",
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/lucide-react": {
+      "version": "0.577.0",
+      "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz",
+      "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==",
+      "license": "ISC",
+      "peerDependencies": {
+        "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "devOptional": true,
+      "license": "MIT"
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.36",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+      "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
+      "devOptional": true,
+      "license": "MIT"
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "devOptional": true,
+      "license": "ISC"
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.8",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+      "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/prop-types": {
+      "version": "15.8.1",
+      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.4.0",
+        "object-assign": "^4.1.1",
+        "react-is": "^16.13.1"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
+    "node_modules/react": {
+      "version": "19.2.4",
+      "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+      "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "19.2.4",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+      "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+      "license": "MIT",
+      "dependencies": {
+        "scheduler": "^0.27.0"
+      },
+      "peerDependencies": {
+        "react": "^19.2.4"
+      }
+    },
+    "node_modules/react-draggable": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz",
+      "integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==",
+      "license": "MIT",
+      "dependencies": {
+        "clsx": "^2.1.1",
+        "prop-types": "^15.8.1"
+      },
+      "peerDependencies": {
+        "react": ">= 16.3.0",
+        "react-dom": ">= 16.3.0"
+      }
+    },
+    "node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+      "license": "MIT"
+    },
+    "node_modules/react-refresh": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+      "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.59.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+      "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.59.0",
+        "@rollup/rollup-android-arm64": "4.59.0",
+        "@rollup/rollup-darwin-arm64": "4.59.0",
+        "@rollup/rollup-darwin-x64": "4.59.0",
+        "@rollup/rollup-freebsd-arm64": "4.59.0",
+        "@rollup/rollup-freebsd-x64": "4.59.0",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+        "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+        "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+        "@rollup/rollup-linux-arm64-musl": "4.59.0",
+        "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+        "@rollup/rollup-linux-loong64-musl": "4.59.0",
+        "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+        "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+        "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+        "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+        "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+        "@rollup/rollup-linux-x64-gnu": "4.59.0",
+        "@rollup/rollup-linux-x64-musl": "4.59.0",
+        "@rollup/rollup-openbsd-x64": "4.59.0",
+        "@rollup/rollup-openharmony-arm64": "4.59.0",
+        "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+        "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+        "@rollup/rollup-win32-x64-gnu": "4.59.0",
+        "@rollup/rollup-win32-x64-msvc": "4.59.0",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.27.0",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+      "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+      "license": "MIT"
+    },
+    "node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "devOptional": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/tailwind-merge": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
+      "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/dcastil"
+      }
+    },
+    "node_modules/tailwindcss": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
+      "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/tapable": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+      "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.15",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+      "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+      "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+      "devOptional": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/vite": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+      "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.27.0",
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3",
+        "postcss": "^8.5.6",
+        "rollup": "^4.43.0",
+        "tinyglobby": "^0.2.15"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^20.19.0 || >=22.12.0",
+        "jiti": ">=1.21.0",
+        "less": "^4.0.0",
+        "lightningcss": "^1.21.0",
+        "sass": "^1.70.0",
+        "sass-embedded": "^1.70.0",
+        "stylus": ">=0.54.8",
+        "sugarss": "^5.0.0",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "devOptional": true,
+      "license": "ISC"
+    }
+  }
+}

+ 32 - 0
package.json

@@ -0,0 +1,32 @@
+{
+  "name": "h5sdk",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "tsc && vite build",
+    "preview": "vite preview"
+  },
+  "devDependencies": {
+    "@tailwindcss/vite": "^4.2.1",
+    "@types/crypto-js": "^4.2.2",
+    "@types/react": "^19.2.14",
+    "@types/react-dom": "^19.2.3",
+    "@vitejs/plugin-react": "^5.1.4",
+    "tailwindcss": "^4.2.1",
+    "typescript": "~5.9.3",
+    "vite": "^7.3.1"
+  },
+  "dependencies": {
+    "axios": "^1.13.6",
+    "clsx": "^2.1.1",
+    "crypto-js": "^4.2.0",
+    "jotai": "^2.18.1",
+    "lucide-react": "^0.577.0",
+    "react": "^19.2.4",
+    "react-dom": "^19.2.4",
+    "react-draggable": "^4.5.0",
+    "tailwind-merge": "^3.5.0"
+  }
+}

+ 190 - 0
public/demo-game.html

@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+  <title>演示游戏内嵌页</title>
+  <!-- Tailwind for quick styling inside iframe -->
+  <script src="https://cdn.tailwindcss.com"></script>
+  <!-- Import SDK -->
+  <script src="/game-sdk.js"></script>
+  <style>
+    body {
+      background-color: #f8fafc;
+      font-family: system-ui, -apple-system, sans-serif;
+    }
+  </style>
+</head>
+<body class="p-4 flex flex-col min-h-screen">
+  
+  <div class="text-center mb-8 mt-4">
+    <div class="inline-block p-3 rounded-full bg-indigo-100 text-indigo-600 mb-3">
+      <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h4"/><path d="M8 10v4"/><path d="M15 13h.01"/><path d="M18 11h.01"/></svg>
+    </div>
+    <h1 class="text-2xl font-bold text-slate-800">异界修仙传 H5</h1>
+    <p class="text-slate-500 text-sm mt-1">游戏客户端 (Iframe内)</p>
+  </div>
+
+  <div class="space-y-4 flex-1">
+    
+    <!-- 初始化模块 -->
+    <div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-100">
+      <div class="flex justify-between items-center mb-3">
+        <h2 class="font-semibold text-slate-700">1. 初始化模块</h2>
+      </div>
+      <button id="btn-init" class="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-xl font-medium transition-colors shadow-sm shadow-blue-200 active:scale-95 duration-150">
+        调用 SDK 初始化
+      </button>
+      <div id="init-res" class="mt-3 text-xs text-slate-600 bg-slate-50 p-2 rounded hidden break-all"></div>
+    </div>
+
+    <!-- 登录模块 -->
+    <div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-100">
+      <div class="flex justify-between items-center mb-3">
+        <h2 class="font-semibold text-slate-700">2. 登录模块</h2>
+        <span id="login-status" class="text-xs px-2 py-1 rounded-full bg-slate-100 text-slate-500">未登录</span>
+      </div>
+      <button id="btn-login" class="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-xl font-medium transition-colors shadow-sm shadow-blue-200 active:scale-95 duration-150">
+        调用 SDK 登录
+      </button>
+      <div id="login-res" class="mt-3 text-xs text-slate-600 bg-slate-50 p-2 rounded hidden break-all"></div>
+    </div>
+
+    <!-- 支付模块 -->
+    <div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-100">
+      <div class="flex justify-between items-center mb-3">
+        <h2 class="font-semibold text-slate-700">3. 支付模块</h2>
+      </div>
+      <button id="btn-pay" class="w-full py-3 bg-emerald-500 hover:bg-emerald-600 text-white rounded-xl font-medium transition-colors shadow-sm shadow-emerald-200 active:scale-95 duration-150">
+        购买「绝世神兵」(¥99)
+      </button>
+      <div id="pay-res" class="mt-3 text-xs text-slate-600 bg-slate-50 p-2 rounded hidden break-all"></div>
+    </div>
+
+    <!-- 数据上报模块 -->
+    <div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-100">
+      <div class="flex justify-between items-center mb-3">
+        <h2 class="font-semibold text-slate-700">4. 数据上报</h2>
+      </div>
+      <button id="btn-report" class="w-full py-3 bg-purple-500 hover:bg-purple-600 text-white rounded-xl font-medium transition-colors shadow-sm shadow-purple-200 active:scale-95 duration-150">
+        上报「角色升级」事件
+      </button>
+      <div id="report-res" class="mt-3 text-xs text-slate-600 bg-slate-50 p-2 rounded hidden break-all"></div>
+    </div>
+
+  </div>
+
+  <script>
+    // 绑定事件到 GameSDK
+    document.addEventListener('DOMContentLoaded', () => {
+      
+      const setLog = (id, result, isError = false) => {
+        const el = document.getElementById(id);
+        el.classList.remove('hidden');
+        el.className = `mt-3 text-xs p-3 rounded break-all border ${isError ? 'bg-red-50 text-red-600 border-red-100' : 'bg-emerald-50 text-emerald-700 border-emerald-100'}`;
+        el.textContent = typeof result === 'object' ? JSON.stringify(result, null, 2) : result;
+      };
+
+      // 1. 初始化
+      document.getElementById('btn-init').addEventListener('click', async () => {
+        const btn = document.getElementById('btn-init');
+        btn.disabled = true;
+        btn.innerHTML = '初始化中...';
+        
+        try {
+          // 调用全局注入的 GameSDK,返回 Promise 并等待
+          const result = await GameSDK.init({app_version:'1.0.0'});
+          
+          setLog('init-res', result);
+          btn.innerHTML = '初始化成功';
+          btn.className = 'w-full py-3 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl font-medium transition-colors shadow-sm shadow-emerald-200 active:scale-95 duration-150';
+        } catch (error) {
+          setLog('init-res', error.message, true);
+          btn.disabled = false;
+          btn.innerHTML = '调用 SDK 初始化';
+        }
+      });
+
+      // 2. 登录
+      document.getElementById('btn-login').addEventListener('click', async () => {
+        const btn = document.getElementById('btn-login');
+        const status = document.getElementById('login-status');
+        btn.disabled = true;
+        btn.innerHTML = '登录中...';
+        
+        try {
+          // 调用全局注入的 GameSDK,返回 Promise 并等待
+          const result = await GameSDK.login();
+          
+          setLog('login-res', result);
+          status.textContent = '已登录';
+          status.className = 'text-xs px-2 py-1 rounded-full bg-emerald-100 text-emerald-600 font-medium';
+          btn.innerHTML = `你好, ${result.username}`;
+          btn.className = 'w-full py-3 bg-slate-100 text-slate-400 rounded-xl font-medium';
+        } catch (error) {
+          setLog('login-res', error.message, true);
+          btn.disabled = false;
+          btn.innerHTML = '调用 SDK 登录';
+        }
+      });
+
+      // 2. 支付
+      document.getElementById('btn-pay').addEventListener('click', async () => {
+        const btn = document.getElementById('btn-pay');
+        const originalText = btn.innerHTML;
+        btn.disabled = true;
+        btn.innerHTML = '支付中...';
+        
+        try {
+          const result = await GameSDK.pay({
+            cp_order_id:String(new Date().getTime()/1000),
+            server_id:'1',
+            server_name: '服务器1',
+            role_id:'1000001',
+            role_name:'角色1',
+            money:'1', // 元
+            product_id:'1',
+            product_name:'商品1',
+            ext:'11111111'
+
+          });
+          
+          setLog('pay-res', result);
+        } catch (error) {
+          setLog('pay-res', error.message, true);
+        } finally {
+          btn.disabled = false;
+          btn.innerHTML = originalText;
+        }
+      });
+
+      // 3. 上报
+      document.getElementById('btn-report').addEventListener('click', async () => {
+        const btn = document.getElementById('btn-report');
+        const originalText = btn.innerHTML;
+        btn.disabled = true;
+        btn.innerHTML = '上报中...';
+        
+        try {
+          const result = await GameSDK.report({
+            data_type: '1',
+            server_id: '1',
+            server_name: '服务器1',
+            role_id: '1000001',
+            role_name: '角色1',
+            role_level: '1'
+          });
+          
+          setLog('report-res', result);
+        } catch (error) {
+          setLog('report-res', error.message, true);
+        } finally {
+          btn.disabled = false;
+          btn.innerHTML = originalText;
+        }
+      });
+
+    });
+  </script>
+</body>
+</html>

+ 91 - 0
public/game-sdk.js

@@ -0,0 +1,91 @@
+/**
+ * 游戏端 SDK
+ * 注入到游戏 iframe 内,封装常用业务 API
+ */
+(function() {
+  const pendingRequests = new Map();
+
+  // 生成唯一 ID
+  function generateId() {
+    return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
+  }
+
+  // 监听来自父级的消息
+  window.addEventListener('message', (event) => {
+    try {
+      const message = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
+      if (message && message.requestId) {
+        console.log('[SDK] Received message from parent:', message);
+        const request = pendingRequests.get(message.requestId);
+        
+        if (request) {
+          if (message.type.endsWith('_SUCCESS')) {
+            request.resolve(message.data);
+          } else if (message.type.endsWith('_FAIL') || message.type.endsWith('_CANCEL')) {
+            request.reject(new Error(message.data?.message || 'Operation failed or cancelled'));
+          } else {
+            // 某些情况下可能只是通用回复
+            request.resolve(message.data);
+          }
+          pendingRequests.delete(message.requestId);
+        }
+      }
+    } catch (error) {
+      // 解析失败忽略
+    }
+  });
+
+  // 基础发送方法,返回 Promise
+  function sendRequest(type, data = {}) {
+    return new Promise((resolve, reject) => {
+      const requestId = generateId();
+      pendingRequests.set(requestId, { resolve, reject });
+
+      const message = { type, data, requestId };
+      console.log('[SDK] Requesting parent:', message);
+      window.parent.postMessage(JSON.stringify(message), '*');
+    });
+  }
+
+  // 暴露全局对象 GameSDK
+  window.GameSDK = {
+    /**
+     * 调用登录
+     * @returns {Promise<{token: string, userId: string, username: string}>}
+     */
+    login: function() {
+      return sendRequest('LOGIN_REQUEST');
+    },
+
+    /**
+     * 调起支付
+     * @param {Object} orderInfo 订单信息
+     * @param {string} orderInfo.productId 商品ID
+     * @param {string} orderInfo.productName 商品名称
+     * @param {number} orderInfo.amount 支付金额
+     * @returns {Promise<{orderId: string, status: string}>}
+     */
+    pay: function(orderInfo) {
+      return sendRequest('PAY_REQUEST', orderInfo);
+    },
+
+    /**
+     * 数据上报
+     * @param {Object} reportData 上报数据
+     * @param {string} reportData.action 行为类型
+     * @param {string} reportData.payload 上报内容
+     * @returns {Promise<{success: boolean}>}
+     */
+    report: function(reportData) {
+      return sendRequest('REPORT_REQUEST', reportData);
+    },
+
+    /**
+     * 初始化
+     * @returns {Promise<{success: boolean}>}
+     */
+    init: function(initData) {
+      return sendRequest('INIT_REQUEST', initData);
+    }
+  };
+})();

+ 1 - 0
public/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 26 - 0
readme.md

@@ -0,0 +1,26 @@
+# H5 SDK
+
+## 说明
+
+通过链接携带的game_id参数,来请求加载游戏链接,然后通过iframe加载游戏,进行调用SDK
+
+- 开发框架:react
+- 开发环境:node 20.19.0
+
+## 依赖
+```js
+npm install
+```
+## 使用
+
+> 开发编译
+
+```js
+npm run dev
+```
+
+> 打包
+
+```js
+npm run build
+```

+ 105 - 0
src/App.tsx

@@ -0,0 +1,105 @@
+import { useEffect, useRef, useState } from 'react';
+import { eventBus } from './lib/EventBus';
+import { pmBridge } from './lib/PostMessageBridge';
+import { useSetAtom } from 'jotai'
+import { openModalAction } from './store';
+import { LoginIndex } from './components/login';
+import { RealNameIndex } from './components/real-name';
+import { PaymentIndex } from './components/payment';
+import { report } from './components/report';
+import { getH5GameLinkApi } from './api';
+import { getURLparams } from './utils';
+import { init } from './components/init';
+import { nativeInit } from './components/init/native-init';
+import SlideBar from './components/slide-bar';
+function App() {
+  const iframeRef = useRef<HTMLIFrameElement>(null);
+  const [iframeUrl, setIframeUrl] = useState<string>('');
+
+  const openModal = useSetAtom(openModalAction);
+  // const modalState = useAtomValue(modalStackAtom);
+ 
+  const getGameUrl = async () => {
+    const res = await getH5GameLinkApi({game_id: getURLparams('game_id') || ''})
+    setIframeUrl(`${res.game_url}?game_id=${getURLparams('game_id') || ''}&time=${Math.round(new Date().getTime()/1000)}`)
+  }
+
+
+  // 初始化通信桥
+  useEffect(() => {
+    if (iframeRef.current) {
+      pmBridge.init(iframeRef.current);
+    }
+    return () => pmBridge.destroy();
+  }, []);
+
+  // EventBus 事件订阅
+  useEffect(() => {
+    // 处理登录请求
+    const handleLoginReq = (payload: any) => {
+      openModal({name:'login',item:{isOpen:true,requestId:payload.requestId,data:payload.data}})
+      console.log("登录")
+    };
+
+    // 处理支付请求
+    const handlePayReq = (payload: any) => {
+      openModal({name:'payment',item:{isOpen:true,requestId:payload.requestId,data:payload.data}})
+    };
+
+    // 处理上报请求
+    const handleReportReq = (payload: any) => {
+      report(payload)
+    };
+    
+    // 处理初始化请求
+    const handleInitReq = (payload: any) => {
+      // 微端是真
+      if(getURLparams('is_native') === 'true'){
+        nativeInit(payload)
+      }else{
+        init(payload)
+      }
+    }    
+
+    eventBus.on('LOGIN_REQUEST', handleLoginReq);
+    eventBus.on('PAY_REQUEST', handlePayReq);
+    eventBus.on('REPORT_REQUEST', handleReportReq);
+    eventBus.on('INIT_REQUEST', handleInitReq);
+
+    return () => {
+      eventBus.off('LOGIN_REQUEST', handleLoginReq);
+      eventBus.off('PAY_REQUEST', handlePayReq);
+      eventBus.off('REPORT_REQUEST', handleReportReq);
+      eventBus.off('INIT_REQUEST', handleInitReq);
+    };
+  }, []);
+
+
+  // 请求记载游戏链接
+  useEffect(() => {
+    getGameUrl()
+  }, []);
+  return (
+    <div className="w-full h-screen relative bg-white font-sans overflow-hidden">
+      
+      {/* Iframe 游戏容器 */}
+      <iframe 
+        ref={iframeRef}
+        src={iframeUrl} 
+        title="Game Container"
+        className="w-full h-full border-none"
+        sandbox="allow-scripts allow-same-origin allow-forms"
+      />
+
+      {/* 渲染弹窗 */}
+      <LoginIndex />
+      <RealNameIndex />
+      <PaymentIndex />
+      <SlideBar />
+     
+
+    </div>
+  );
+}
+
+export default App;

+ 27 - 0
src/api/index.ts

@@ -0,0 +1,27 @@
+import { request } from '../utils/request'
+import { initSDKConfig } from '../utils/common-params'
+import type { InitConfig } from '../utils/common-params'
+
+/**
+ * 初始化 SDK
+ * @param config 初始化配置
+ */
+export function initSDK(config: InitConfig) {
+  initSDKConfig(config)
+}
+
+// 初始化 SDK 接口
+export const initSDKApi = (params: {
+    app_version: string
+}) => {
+  initSDK(params)
+  return request.post('/sdk/init', params)
+}
+
+
+// 获取H5游戏链接接口
+export const getH5GameLinkApi = (params: {
+    game_id: string
+}) => {
+  return request.post('/sdk/init/game_url', params)
+}

+ 67 - 0
src/api/login.ts

@@ -0,0 +1,67 @@
+import { request } from '../utils/request'
+
+// 登录账号密码接口
+export const loginAccountApi = (data: {
+    user_name: string,
+    user_pwd: string
+}) => {
+  return request.post('/sdk/auth/login', data)
+}
+
+// 登录手机号, 获取验证码
+export const loginSendCodeApi = (data: {
+  mobile: string
+}) => {
+  return request.post('/sdk/auth/send_code', data)
+}
+
+// 验证登录验证码,并返回账号列表
+export const getAccountListByCodeApi = (data: {
+  mobile: string,
+  code: string
+}) => {
+  return request.post('/sdk/auth/verify_code', data)
+}
+
+// 手机验证码登录
+export const loginPhoneCodeLoginApi = (data: {
+  mobile: string,
+  code: string,
+  user_name: string
+}) => {
+  return request.post('/sdk/auth/sms_login', data)
+}
+
+// 实名认证
+export const realNameAuthApi = (data: {
+  real_name: string,
+  id_card: string
+}) => {
+  return request.post('/sdk/user/real_name', data)
+}
+
+// 忘记密码-发送验证码
+export const forgetPasswordSendCodeApi = (data: {
+    user_name: string,
+    mobile: string
+  }) => {
+  return request.post('/sdk/auth/update_pwd_send_code', data)
+}
+
+// 第一步:忘记密码-验证验证码
+export const forgetPasswordVerifyCodeApi = (data: {
+  user_name: string,
+  mobile: string,
+  code: string
+})=>{
+  return request.post('/sdk/auth/update_pwd_check_code', data)
+}
+
+// 第二步:忘记密码-修改密码
+export const forgetPasswordUpdatePasswordApi = (data: {
+  user_name: string,
+  new_user_pwd: string
+  mobile:string
+})=>{
+  return request.post('/sdk/auth/update_pwd_by_code', data)
+}

+ 24 - 0
src/api/payment.ts

@@ -0,0 +1,24 @@
+import { request } from '../utils/request'
+
+// 创建支付订单
+export const createPaymentOrderApi = (data: {
+    cp_order_id: string,
+    server_id: string,
+    server_name: string,
+    role_id: string,
+    role_name: string,
+    money: string,
+    product_id: string,
+    product_name: string,
+    ext: string,
+}) => {
+  return request.post('/payment/order/create', data)
+}
+
+// 支付获取支付链接
+export const getPaymentLinkApi = (data: {
+  order_id: string,
+  pay_channel_id: string
+}) => {
+return request.post('/payment/order/pay', data)
+}

+ 14 - 0
src/api/register.ts

@@ -0,0 +1,14 @@
+import { request } from '../utils/request'
+
+// 注册账号接口
+export const registerAccountApi = (data: {
+    user_name: string,
+    user_pwd: string
+}) => {
+  return request.post('/sdk/auth/register', data)
+}
+
+// 获取随机账号接口
+export const getRandomAccountApi = (data:{is_activate:number}) => {
+  return request.post('/sdk/auth/register_user', data)
+}

+ 12 - 0
src/api/role.ts

@@ -0,0 +1,12 @@
+import { request } from '../utils/request'
+// 角色上报接口
+export const reportRoleApi = (data: {
+    data_type: string,
+    server_id: string,
+    server_name: string,
+    role_id: string,
+    role_name: string,
+    role_level: string
+}) => {
+  return request.post('/sdk/role/report', data)
+}

+ 98 - 0
src/components/FloatingButton.tsx

@@ -0,0 +1,98 @@
+import { useState, useRef } from 'react';
+import Draggable, { type DraggableEvent, type DraggableData } from 'react-draggable';
+import { Gamepad2 } from 'lucide-react';
+
+export function FloatingButton() {
+  const [position, setPosition] = useState({ x: 20, y: 100 });
+  const [isDragging, setIsDragging] = useState(false);
+  const [isIdle, setIsIdle] = useState(false);
+  const nodeRef = useRef<HTMLDivElement>(null);
+  
+  const idleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+  // 清除空闲倒计时
+  const clearIdleTimer = () => {
+    if (idleTimerRef.current) {
+      clearTimeout(idleTimerRef.current);
+      idleTimerRef.current = null;
+    }
+    setIsIdle(false);
+  };
+
+  const handleDragStart = () => {
+    clearIdleTimer();
+    setIsDragging(true);
+    
+    // 如果处于空闲状态(半隐藏),视觉上立刻恢复原状
+    if (isIdle) {
+      setPosition(prev => {
+        const btnWidth = nodeRef.current ? nodeRef.current.offsetWidth : 48;
+        return {
+          ...prev,
+          x: prev.x < 0 ? 0 : window.innerWidth - btnWidth
+        };
+      });
+    }
+  };
+
+  const handleDrag = (e: DraggableEvent, data: DraggableData) => {
+    setPosition({ x: data.x, y: data.y });
+  };
+
+  const handleDragStop = (e: DraggableEvent, data: DraggableData) => {
+    setIsDragging(false);
+
+    // 自动吸附屏幕左右边缘
+    const btnWidth = nodeRef.current ? nodeRef.current.offsetWidth : 48; // 默认 48px
+    const btnHeight = nodeRef.current ? nodeRef.current.offsetHeight : 48;
+
+    // 限制不要拖出上下边缘
+    let endY = data.y;
+    const maxY = window.innerHeight - btnHeight;
+    endY = Math.max(0, Math.min(endY, maxY));
+    
+    let endX = data.x;
+    const centerX = data.x + (btnWidth / 2);
+    const windowCenterX = window.innerWidth / 2;
+    endX = centerX < windowCenterX ? 0 : window.innerWidth - btnWidth;
+    
+    setPosition({ x: endX, y: endY });
+
+    // 启动 3 秒空闲隐藏计时器
+    idleTimerRef.current = setTimeout(() => {
+      setIsIdle(true);
+      // 将半个按钮宽度移出屏幕之外
+      setPosition(prev => {
+        const halfWidth = btnWidth / 2;
+        const newX = prev.x < windowCenterX ? -halfWidth : window.innerWidth - halfWidth;
+        return { ...prev, x: newX };
+      });
+    }, 3000);
+  };
+
+  const handleClick = () => {
+    // 待办:展开菜单等操作
+    console.log('Floating button clicked');
+  };
+
+  return (
+    <Draggable
+      nodeRef={nodeRef}
+      position={position}
+      onStart={handleDragStart}
+      onDrag={handleDrag}
+      onStop={handleDragStop}
+    >
+      <div
+        ref={nodeRef}
+        onClick={handleClick}
+        className={`fixed top-0 left-0 z-50 flex items-center justify-center w-12 h-12 bg-blue-600 rounded-full shadow-lg cursor-move active:scale-95 
+          ${isDragging ? 'opacity-80 scale-105 shadow-xl transition-none' : 'opacity-100 hover:scale-110 transition-all duration-300 ease-out'}
+          ${isIdle ? 'opacity-50' : ''}`}
+        style={{ touchAction: 'none' }}
+      >
+        <Gamepad2 className="text-white w-6 h-6 pointer-events-none" />
+      </div>
+    </Draggable>
+  );
+}

+ 30 - 0
src/components/forget-password/index.tsx

@@ -0,0 +1,30 @@
+import { useState } from "react"
+import { Step1 } from "./step1"
+import { Step2 } from "./step2"
+
+export const ForgetPasswordIndex = ({close}: {close: () => void}) => {
+    const [step, setStep] = useState(1)
+    const [userName, setUserName] = useState("")
+    const [mobile, setMobile] = useState("")
+
+    return (
+        <>
+            {
+                step === 1 ? 
+                <Step1 
+                    onNext={(u, m) => {
+                        setUserName(u)
+                        setMobile(m)
+                        setStep(2)
+                    }} 
+                    onClose={close} 
+                /> : 
+                <Step2 
+                    userName={userName}
+                    onBack={() => {setStep(1)}} 
+                    onClose={close} 
+                />
+            }
+        </>
+    )
+}

+ 156 - 0
src/components/forget-password/step1.tsx

@@ -0,0 +1,156 @@
+import { useState, useEffect } from "react"
+import { ChevronRight, CircleX, ShieldCheck, Smartphone, User } from "lucide-react"
+import { forgetPasswordSendCodeApi, forgetPasswordVerifyCodeApi } from "../../api/login"
+
+export const Step1 = ({onNext, onClose}: {onNext: (userName: string, mobile: string) => void, onClose: () => void}) => {
+    const [userName, setUserName] = useState("")
+    const [mobile, setMobile] = useState("")
+    const [code, setCode] = useState("")
+    const [loading, setLoading] = useState(false)
+    const [countdown, setCountdown] = useState(0)
+
+    useEffect(() => {
+        let timer: any
+        if (countdown > 0) {
+            timer = setInterval(() => {
+                setCountdown(prev => prev - 1)
+            }, 1000)
+        }
+        return () => clearInterval(timer)
+    }, [countdown])
+
+    const handleSendCode = async () => {
+        if (!userName || !mobile) {
+            alert("请输入账号和手机号")
+            return
+        }
+        if (countdown > 0) return
+
+        try {
+            const res: any = await forgetPasswordSendCodeApi({ user_name: userName, mobile })
+            if (res.code === 200) {
+                setCountdown(60)
+                alert("验证码已发送")
+            } else {
+                alert(res.msg || "发送失败")
+            }
+        } catch (error) {
+            alert("网络错误")
+        }
+    }
+
+    const handleNext = async () => {
+        if (!userName || !mobile || !code) {
+            alert("请完整填写信息")
+            return
+        }
+        setLoading(true)
+        try {
+            await forgetPasswordVerifyCodeApi({ user_name: userName, mobile, code })
+            onNext(userName, mobile)
+        } catch (error) {
+            alert("网络错误")
+        } finally {
+            setLoading(false)
+        }
+    }
+
+    return (
+        <div className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-9999 w-full max-w-[360px] bg-white rounded-xl shadow-2xl overflow-hidden flex flex-col">
+
+            <div className="flex items-center bg-white p-4 border-b border-slate-100 justify-between">
+                <h2 className="text-slate-900 text-lg font-bold leading-tight tracking-tight flex-1 text-center">找回密码</h2>
+                <div className="text-slate-900 flex size-10 shrink-0 items-center justify-center cursor-pointer hover:bg-slate-50 rounded-full transition-colors" onClick={()=>{onClose()}}>
+                    <span className="material-symbols-outlined text-2xl text-slate-400"><CircleX /></span>
+                </div>
+            </div>
+
+            <div className="flex w-full flex-col items-center gap-3 py-6 px-6">
+                <div className="flex flex-row items-center justify-center gap-3 w-full">
+                    <div className="h-1.5 flex-1 rounded-full bg-primary"></div>
+                    <div className="h-1.5 flex-1 rounded-full bg-slate-100"></div>
+                </div>
+                <p className="text-slate-900 text-sm font-semibold mt-1">第一步:验证账号</p>
+            </div>
+
+            <div className="px-6 pb-8 space-y-5">
+                <div className="space-y-1.5">
+                    <label className="text-slate-600 text-sm font-medium px-1">账号</label>
+                    <div className="relative">
+                        <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+                            <span className="material-symbols-outlined text-slate-400 text-xl"><User size={20} /></span>
+                        </div>
+                        <input 
+                            className="block w-full pl-10 pr-3 py-3 bg-slate-50 border border-slate-200 rounded-lg text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-base" 
+                            placeholder="请输入账号" 
+                            value={userName}
+                            onChange={(e) => setUserName(e.target.value)}
+                        />
+                    </div>
+                </div>
+
+                <div className="space-y-1.5">
+                    <label className="text-slate-600 text-sm font-medium px-1">手机号</label>
+                    <div className="relative">
+                        <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+                            <span className="material-symbols-outlined text-slate-400 text-xl"><Smartphone size={20} /></span>
+                        </div>
+                        <input 
+                            className="block w-full pl-10 pr-3 py-3 bg-slate-50 border border-slate-200 rounded-lg text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-base" 
+                            placeholder="请输入绑定的手机号" 
+                            type="tel" 
+                            value={mobile}
+                            onChange={(e) => setMobile(e.target.value)}
+                        />
+                    </div>
+                </div>
+
+                <div className="space-y-1.5">
+                    <label className="text-slate-600 text-sm font-medium px-1">验证码</label>
+                    <div className="flex gap-2">
+                        <div className="relative flex-1">
+                            <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+                                <span className="material-symbols-outlined text-slate-400 text-xl"><ShieldCheck size={20} /></span>
+                            </div>
+                            <input 
+                                className="block w-full pl-10 pr-3 py-3 bg-slate-50 border border-slate-200 rounded-lg text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-base" 
+                                placeholder="验证码" 
+                                type="text" 
+                                value={code}
+                                onChange={(e) => setCode(e.target.value)}
+                            />
+                        </div>
+                        <button 
+                            className="shrink-0 px-4 py-3 bg-primary/10 text-primary font-medium text-sm rounded-lg hover:bg-primary/20 transition-colors whitespace-nowrap min-w-[100px] disabled:opacity-50"
+                            onClick={handleSendCode}
+                            disabled={countdown > 0}
+                        >
+                            {countdown > 0 ? `${countdown}s` : "获取验证码"}
+                        </button>
+                    </div>
+                </div>
+
+                <p className="text-slate-400 text-xs px-1">
+                    验证码将发送至您的绑定手机,请注意查收。
+                </p>
+
+                <div className="pt-4">
+                    <button 
+                        className="w-full bg-primary hover:bg-primary/90 text-white font-bold py-3.5 rounded-lg shadow-lg shadow-primary/20 transition-all flex items-center justify-center gap-2 disabled:opacity-50" 
+                        onClick={handleNext}
+                        disabled={loading}
+                    >
+                        <span>{loading ? "校验中..." : "下一步"}</span>
+                        {!loading && <span className="material-symbols-outlined text-lg"><ChevronRight /></span>}
+                    </button>
+                </div>
+            </div>
+
+            <div className="h-1 bg-gradient-to-r from-transparent via-primary/10 to-transparent"></div>
+
+            <div className="py-4 text-center">
+                <p className="text-slate-400 text-xs">遇到问题? <span className="text-primary cursor-pointer font-medium">联系客服</span></p>
+            </div>
+        </div>
+    )
+}

+ 131 - 0
src/components/forget-password/step2.tsx

@@ -0,0 +1,131 @@
+import { useState } from "react"
+import { ArrowLeft, Lock, Eye, EyeOff } from "lucide-react"
+import { forgetPasswordUpdatePasswordApi } from "../../api/login"
+
+export const Step2 = ({userName, mobile, onBack, onClose}: {userName: string, mobile: string, onBack: () => void, onClose: () => void}) => {
+    const [password, setPassword] = useState("")
+    const [confirmPassword, setConfirmPassword] = useState("")
+    const [loading, setLoading] = useState(false)
+    const [showPassword, setShowPassword] = useState(false)
+    const [showConfirmPassword, setShowConfirmPassword] = useState(false)
+
+    const handleSubmit = async () => {
+        if (!password || !confirmPassword) {
+            alert("请输入新密码")
+            return
+        }
+        if (password !== confirmPassword) {
+            alert("两次输入的密码不一致")
+            return
+        }
+        if (password.length < 6 || password.length > 16) {
+            alert("密码长度应为6-16位")
+            return
+        }
+
+        setLoading(true)
+        try {
+            await forgetPasswordUpdatePasswordApi({ 
+                user_name: userName, 
+                new_user_pwd: password,
+                mobile: mobile
+            })
+            alert("修改成功")
+            onClose()
+        } catch (error) {
+            alert(error?.message || "修改失败")
+        } finally {
+            setLoading(false)
+        }
+    }
+
+    return (
+        <div className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-9999 w-full max-w-[360px] bg-white rounded-xl shadow-2xl overflow-hidden flex flex-col">
+
+            <div className="flex items-center bg-white p-4 border-b border-slate-100 justify-between">
+                <div className="text-slate-900 flex size-10 shrink-0 items-center justify-center cursor-pointer hover:bg-slate-50 rounded-full transition-colors" onClick={onBack}>
+                    <span className="material-symbols-outlined text-2xl"><ArrowLeft /></span>
+                </div>
+                <h2 className="text-slate-900 text-lg font-bold leading-tight tracking-tight flex-1 text-center">重置密码</h2>
+                <div className="size-10"></div>
+            </div>
+
+            <div className="flex w-full flex-col items-center gap-3 py-6 px-6">
+                <div className="flex flex-row items-center justify-center gap-3 w-full">
+                    <div className="h-1.5 flex-1 rounded-full bg-primary"></div>
+                    <div className="h-1.5 flex-1 rounded-full bg-primary"></div>
+                </div>
+                <p className="text-slate-900 text-sm font-semibold mt-1">第二步:设置新密码</p>
+            </div>
+
+            <div className="px-6 pb-8 space-y-5">
+
+                <div className="space-y-1.5">
+                    <label className="text-slate-600 text-sm font-medium px-1">新密码</label>
+                    <div className="relative">
+                        <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+                            <span className="material-symbols-outlined text-slate-400 text-xl"><Lock size={20} /></span>
+                        </div>
+                        <input 
+                            className="block w-full pl-10 pr-10 py-3 bg-slate-50 border border-slate-200 rounded-lg text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-base" 
+                            placeholder="6-16位字母、数字或符号组合" 
+                            type={showPassword ? "text" : "password"}
+                            value={password}
+                            onChange={(e) => setPassword(e.target.value)}
+                        />
+                        <div 
+                            className="absolute inset-y-0 right-0 pr-3 flex items-center cursor-pointer"
+                            onClick={() => setShowPassword(!showPassword)}
+                        >
+                            {showPassword ? <EyeOff size={18} className="text-slate-400" /> : <Eye size={18} className="text-slate-400" />}
+                        </div>
+                    </div>
+                </div>
+
+                <div className="space-y-1.5">
+                    <label className="text-slate-600 text-sm font-medium px-1">确认新密码</label>
+                    <div className="relative">
+                        <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+                            <span className="material-symbols-outlined text-slate-400 text-xl"><Lock size={20} /></span>
+                        </div>
+                        <input 
+                            className="block w-full pl-10 pr-10 py-3 bg-slate-50 border border-slate-200 rounded-lg text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-base" 
+                            placeholder="请再次输入新密码" 
+                            type={showConfirmPassword ? "text" : "password"}
+                            value={confirmPassword}
+                            onChange={(e) => setConfirmPassword(e.target.value)}
+                        />
+                         <div 
+                            className="absolute inset-y-0 right-0 pr-3 flex items-center cursor-pointer"
+                            onClick={() => setShowConfirmPassword(!showConfirmPassword)}
+                        >
+                            {showConfirmPassword ? <EyeOff size={18} className="text-slate-400" /> : <Eye size={18} className="text-slate-400" />}
+                        </div>
+                    </div>
+                </div>
+
+                <div className="pt-4 space-y-3">
+                    <button 
+                        className="w-full bg-primary hover:bg-primary/90 text-white font-bold py-3.5 rounded-lg shadow-lg shadow-primary/20 transition-all flex items-center justify-center gap-2 disabled:opacity-50"
+                        onClick={handleSubmit}
+                        disabled={loading}
+                    >
+                        <span>{loading ? "提交中..." : "确认提交"}</span>
+                    </button>
+                    <button 
+                        className="w-full bg-white border border-slate-200 text-slate-600 font-bold py-3.5 rounded-lg hover:bg-slate-50 transition-all flex items-center justify-center gap-2" 
+                        onClick={onClose}
+                    >
+                        <span>取消重置</span>
+                    </button>
+                </div>
+            </div>
+
+            <div className="h-1 bg-gradient-to-r from-transparent via-primary/10 to-transparent"></div>
+
+            <div className="py-4 text-center">
+                <p className="text-slate-400 text-xs">遇到问题? <span className="text-primary cursor-pointer font-medium">联系客服</span></p>
+            </div>
+        </div>
+    )
+}

+ 10 - 0
src/components/init/index.ts

@@ -0,0 +1,10 @@
+import { pmBridge } from "../../lib/PostMessageBridge";
+import { initSDKApi } from "../../api";
+
+export const init = async ({data,requestId}: {data: any,requestId: string})=>{
+
+    // todo 初始化接口
+    await initSDKApi(data)
+    // 成功
+     pmBridge.sendToIframe('INIT_SUCCESS', { success: true, message:"初始化成功" }, requestId);
+}

+ 11 - 0
src/components/init/native-init.ts

@@ -0,0 +1,11 @@
+import { getOS } from "../../utils/common-params"
+
+export const nativeInit = (payload: any) => {
+    if(getOS() === 2){
+        // 如果是IOS
+        let method = {'method':'gameBridge', type:'INIT_REQUEST', data:payload.data, requestId:payload.requestId}
+        window.webkit.messageHandlers.GAMESDK.postMessage(JSON.stringify(method))
+    }else{
+        
+    }
+}

+ 144 - 0
src/components/login/account-login.tsx

@@ -0,0 +1,144 @@
+import { useAtomValue, useSetAtom } from "jotai"
+import { modalStackAtom, openModalAction } from "../../store"
+import { ForgetPasswordIndex } from "../forget-password";
+import { useState } from "react";
+import { loginAccountApi } from "../../api/login"
+import { pmBridge } from "../../lib/PostMessageBridge"
+
+
+export interface AccountLoginProps {
+    switchPhoneLogin: () => void;
+    switchRegister: () => void;
+}
+
+export const AccountLogin = ({ switchPhoneLogin, switchRegister }: AccountLoginProps) => {
+    const modalState = useAtomValue(modalStackAtom)
+    const openModal = useSetAtom(openModalAction)
+    const [userName, setUserName] = useState('')
+    const [userPwd, setUserPwd] = useState('')
+    const [loading, setLoading] = useState(false)
+    const [isShowForgetPassword, setIsshowPassword] = useState(false)
+
+    const handleLogin = async () => {
+        if (!userName) {
+            alert('请输入账号')
+            return
+        }
+        if (!userPwd) {
+            alert('请输入密码')
+            return
+        }
+
+        setLoading(true)
+        try {
+            const res = await loginAccountApi({
+                user_name: userName,
+                user_pwd: userPwd
+            })
+
+            if (res.token) {
+                // 存储 token
+                localStorage.setItem('token', res.token)
+
+                // 使用 pmBridge 回传给父 iframe
+                const requestId = modalState.login.requestId
+                pmBridge.sendToIframe("LOGIN SUCCESS", res, requestId)
+
+                // 关闭登录弹窗
+                openModal({ name: 'login', item: { isOpen: false } })
+            } else {
+                alert('登录失败:未返回 token')
+            }
+        } catch (error: any) {
+            console.error('登录失败:', error)
+            alert(error.message || '登录失败')
+        } finally {
+            setLoading(false)
+        }
+    }
+
+    return (
+        <>
+            <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm">
+
+                <div className="w-full max-w-[320px] mx-5 bg-[#f5f7f8] rounded-2xl shadow-2xl overflow-hidden flex flex-col scale-100">
+
+                    <div className="flex flex-col items-center pt-4 pb-6 px-6">
+
+                        <h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">游戏登录</h1>
+                    </div>
+
+                    {/* Tabs */}
+                    <div className="flex border-b border-slate-200 dark:border-slate-800 px-6 gap-8">
+                        <button onClick={switchPhoneLogin} className="flex flex-col items-center justify-center border-b-[3px] border-transparent text-primary pb-3 pt-2  hover:text-primary transition-colors">
+                            <p className="text-sm font-bold leading-normal tracking-wide">手机号登录</p>
+                        </button>
+                        <button className="flex flex-col items-center justify-center border-b-[3px] border-primary text-primary pb-3 pt-2">
+                            <p className="text-sm font-bold leading-normal tracking-wide">账号登录</p>
+                        </button>
+                    </div>
+
+                    {/* Form Content */}
+                    <div className="p-6 space-y-4">
+                        {/* Country Code & Phone Input */}
+                        <div className="flex gap-2">
+
+                            <div className="flex-1">
+                                <input 
+                                    className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
+                                    placeholder="请输入账号" 
+                                    type="text" 
+                                    value={userName}
+                                    onChange={(e) => setUserName(e.target.value)}
+                                />
+                            </div>
+                        </div>
+
+                        {/* Verification Code */}
+                        <div className="flex gap-2">
+                            <div className="flex-1">
+                                <input 
+                                    className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
+                                    placeholder="请输入密码" 
+                                    type="password" 
+                                    value={userPwd}
+                                    onChange={(e) => setUserPwd(e.target.value)}
+                                />
+                            </div>
+
+                        </div>
+                        <div onClick={() => { setIsshowPassword(true) }}  className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors text-right cursor-pointer">忘记密码?</div>
+                        {/* Action Button */}
+                        <button 
+                            onClick={handleLogin}
+                            disabled={loading}
+                            className={`w-full bg-primary hover:bg-primary/90 text-white font-bold h-12 rounded-lg shadow-lg shadow-primary/30 transition-all flex items-center justify-center gap-2 ${
+                                loading ? 'opacity-70 cursor-not-allowed' : ''
+                            }`}
+                        >
+                            {loading ? '处理中...' : '进入游戏'}
+                        </button>
+
+                        {/* Secondary Links */}
+                        <div className="flex justify-between items-center px-1">
+                            <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors" onClick={() => { switchRegister() }}>快速注册</a>
+                            <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors" href="#">遇到问题?</a>
+                        </div>
+                    </div>
+
+                    {/* Footer Privacy */}
+                    <div className="px-6 pb-6 text-center">
+                        <p className="text-[10px] text-slate-400 leading-relaxed">
+                            登录即代表您已同意 <a className="text-primary hover:underline" href="#">服务协议</a> 和 <a className="text-primary hover:underline" href="#">隐私政策</a>
+                        </p>
+                    </div>
+                </div>
+            </div>
+            {
+                isShowForgetPassword ? <ForgetPasswordIndex close={() => { setIsshowPassword(false) }} /> : <></>
+            }
+
+        </>
+
+    );
+}

+ 41 - 0
src/components/login/index.tsx

@@ -0,0 +1,41 @@
+
+import { useAtomValue } from "jotai"
+import { modalStackAtom } from "../../store"
+import { PhoneLogin } from "./phone-login"
+import { useState } from "react"
+import { AccountLogin } from "./account-login"
+import { RegisterIndex } from "../register"
+
+export const LoginIndex = () => {
+    const modalState = useAtomValue(modalStackAtom)
+    const [loginType, setLoginType] = useState("phone")
+    const [comType, setComType] = useState("login")
+    return (
+        <>
+
+            {
+                modalState.login.isOpen ?
+                    <>
+                        {
+                            comType === 'login' ?
+                                <>
+                                    {
+                                        loginType === 'phone' ?
+                                            <PhoneLogin switchAccountLogin={() => { setLoginType('account') }} switchRegister={() => { setComType('register') }} />
+                                            :
+                                            <AccountLogin switchPhoneLogin={() => { setLoginType('phone') }} switchRegister={() => { setComType('register') }} />
+                                    }
+                                </>
+                                :
+                                <RegisterIndex switchLogin={() => { setComType('login') }} />
+                        }
+
+                    </>
+                    :
+                    <></>
+            }
+
+
+        </>
+    )
+}

+ 184 - 0
src/components/login/phone-login.tsx

@@ -0,0 +1,184 @@
+import { useState, useEffect } from 'react';
+import { loginSendCodeApi, getAccountListByCodeApi } from '../../api/login';
+import { SelectAccount, type Account } from './select-account';
+
+export interface phoneLoginProps {
+    switchAccountLogin: () => void;
+    switchRegister:()=> void;
+}
+
+export const PhoneLogin = ({ switchAccountLogin, switchRegister }: phoneLoginProps) => {
+    const [mobile, setMobile] = useState('');
+    const [code, setCode] = useState('');
+    const [countdown, setCountdown] = useState(0);
+    const [loading, setLoading] = useState(false);
+    const [accountList, setAccountList] = useState<Account[]>([]);
+    const [showSelectAccount, setShowSelectAccount] = useState(false);
+
+    useEffect(() => {
+        let timer: ReturnType<typeof setInterval>;
+        if (countdown > 0) {
+            timer = setInterval(() => {
+                setCountdown((prev) => prev - 1);
+            }, 1000);
+        }
+        return () => {
+            if (timer) clearInterval(timer);
+        };
+    }, [countdown]);
+
+    const handleGetCode = async () => {
+        if (!mobile) {
+            alert('请输入手机号');
+            return;
+        }
+        if (!/^1[3-9]\d{9}$/.test(mobile)) {
+            alert('请输入正确的手机号');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            await loginSendCodeApi({ mobile });
+            setCountdown(60);
+        } catch (error: any) {
+            console.error('获取验证码失败:', error);
+            alert(error.message || '获取验证码失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleLogin = async () => {
+        if (!mobile) {
+            alert('请输入手机号');
+            return;
+        }
+        if (!code) {
+            alert('请输入验证码');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            const res = await getAccountListByCodeApi({ mobile, code });
+            console.log('验证成功1:', res);
+            if (res.length > 0) {
+                setAccountList(res);
+                setShowSelectAccount(true);
+            } else {
+                alert('登录成功');
+            }
+        } catch (error: any) {
+            console.error('验证失败:', error);
+            alert(error.message || '验证失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleSelectConfirm = (account: Account) => {
+        console.log('选择账号确认:', account);
+        setShowSelectAccount(false);
+        alert(`已选择账号: ${account.user_name}`);
+    };
+
+    return (
+        <>
+         <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm">
+
+            <div className="w-full max-w-[320px] mx-5 bg-[#f5f7f8] rounded-2xl shadow-2xl overflow-hidden flex flex-col scale-100">
+
+                <div className="flex flex-col items-center pt-4 pb-6 px-6">
+
+                    <h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">游戏登录</h1>
+                </div>
+
+                {/* Tabs */}
+                <div className="flex border-b border-slate-200 dark:border-slate-800 px-6 gap-8">
+                    <button className="flex flex-col items-center justify-center border-b-[3px] border-primary text-primary pb-3 pt-2">
+                        <p className="text-sm font-bold leading-normal tracking-wide">手机号登录</p>
+                    </button>
+                    <button onClick={switchAccountLogin} className="flex flex-col items-center justify-center border-b-[3px] border-transparent text-slate-500 dark:text-slate-400 pb-3 pt-2 hover:text-primary transition-colors">
+                        <p className="text-sm font-bold leading-normal tracking-wide">账号登录</p>
+                    </button>
+                </div>
+
+                {/* Form Content */}
+                <div className="p-6 space-y-4">
+                    {/* Country Code & Phone Input */}
+                    <div className="flex gap-2">
+
+                        <div className="flex-1">
+                            <input 
+                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
+                                placeholder="请输入手机号" 
+                                type="tel" 
+                                value={mobile}
+                                onChange={(e) => setMobile(e.target.value)}
+                            />
+                        </div>
+                    </div>
+
+                    {/* Verification Code */}
+                    <div className="flex gap-2">
+                        <div className="flex-1">
+                            <input 
+                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
+                                placeholder="请输入验证码" 
+                                type="text" 
+                                value={code}
+                                onChange={(e) => setCode(e.target.value)}
+                            />
+                        </div>
+                        <button 
+                            className={`px-4 h-12 text-primary font-medium text-sm rounded-lg whitespace-nowrap transition-colors border border-primary/20 ${
+                                countdown > 0 || loading ? 'bg-slate-100 text-slate-400 border-slate-200 cursor-not-allowed' : 'bg-primary/10 hover:bg-primary/20'
+                            }`}
+                            onClick={handleGetCode}
+                            disabled={countdown > 0 || loading}
+                        >
+                            {countdown > 0 ? `${countdown}s` : loading ? '发送中...' : '获取验证码'}
+                        </button>
+                    </div>
+
+                    {/* Action Button */}
+                    <button 
+                        className={`w-full bg-primary hover:bg-primary/90 text-white font-bold h-12 rounded-lg shadow-lg shadow-primary/30 transition-all flex items-center justify-center gap-2 ${
+                            loading ? 'opacity-70 cursor-not-allowed' : ''
+                        }`}
+                        onClick={handleLogin}
+                        disabled={loading}
+                    >
+                        {loading ? '处理中...' : '进入游戏'}
+                    </button>
+
+                    {/* Secondary Links */}
+                    <div className="flex justify-between items-center px-1">
+                        <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors cursor-pointer" onClick={switchRegister}>快速注册</a>
+                        <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors" href="#">遇到问题?</a>
+                    </div>
+                </div>
+
+
+
+                {/* Footer Privacy */}
+                <div className="px-6 pb-6 text-center">
+                    <p className="text-[10px] text-slate-400 leading-relaxed">
+                        登录即代表您已同意 <a className="text-primary hover:underline" href="#">服务协议</a> 和 <a className="text-primary hover:underline" href="#">隐私政策</a>
+                    </p>
+                </div>
+            </div>
+        </div>
+        {showSelectAccount && (
+            <SelectAccount 
+                mobile={mobile}
+                code={code}
+                accounts={accountList} 
+                onConfirm={handleSelectConfirm} 
+                onClose={() => setShowSelectAccount(false)} 
+            />
+        )}
+        </>
+    );
+};

+ 137 - 0
src/components/login/select-account.tsx

@@ -0,0 +1,137 @@
+import { useState, useEffect } from "react"
+import { Circle, CircleCheck } from "lucide-react"
+import { loginPhoneCodeLoginApi } from "../../api/login"
+import { pmBridge } from "../../lib/PostMessageBridge"
+import { useAtomValue, useSetAtom } from "jotai"
+import { modalStackAtom, openModalAction } from "../../store"
+
+export interface Account {
+    uid: string;
+    user_name: string;
+}
+
+interface SelectAccountProps {
+    mobile: string;
+    code: string;
+    accounts: Account[];
+    onConfirm: (account: Account) => void;
+    onClose: () => void;
+}
+
+export const SelectAccount = ({ mobile, code, accounts, onConfirm, onClose }: SelectAccountProps) => {
+    const [selectedId, setSelectedId] = useState('')
+    const [loading, setLoading] = useState(false)
+    const modalState = useAtomValue(modalStackAtom)
+    const openModal = useSetAtom(openModalAction)
+
+    useEffect(() => {
+        if (accounts && accounts.length > 0) {
+            setSelectedId(accounts[0].uid)
+        }
+    }, [accounts])
+
+    const handleConfirm = async () => {
+        const selected = accounts.find(a => a.uid === selectedId)
+        if (selected) {
+            setLoading(true)
+            try {
+                const res = await loginPhoneCodeLoginApi({
+                    mobile,
+                    code,
+                    user_name: selected.user_name
+                })
+                
+                if (res.token) {
+                    // 存储 token
+                    localStorage.setItem('token', res.token)
+                    
+                    // 使用 pmBridge 回传给父 iframe
+                    const requestId = modalState.login.requestId
+                    pmBridge.sendToIframe("LOGIN SUCCESS", res, requestId)
+
+                    // 关闭登录弹窗
+                    openModal({ name: 'login', item: { isOpen: false } })
+
+                    onConfirm(selected)
+                } else {
+                    alert('登录失败:未返回 token')
+                }
+            } catch (error: any) {
+                console.error('登录失败:', error)
+                alert(error.message || '登录失败')
+            } finally {
+                setLoading(false)
+            }
+        }
+    }
+
+    if (!accounts || accounts.length === 0) return null
+
+    return (
+        <div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm">
+            <main className="bg-white rounded-2xl shadow-2xl overflow-hidden flex flex-col border border-slate-200 w-full max-w-[360px] mx-5">
+
+                <header className="flex items-center justify-between px-4 py-4 border-b border-slate-100">
+                    <div className="flex items-center gap-3">
+                        <h1 className="text-lg font-bold text-slate-900 tracking-tight">选择账号</h1>
+                    </div>
+                </header>
+
+                <section className="p-5 flex flex-col gap-4">
+                    <div className="mb-2">
+                        <h2 className="text-sm font-medium text-slate-500">检测到手机号关联多个账号</h2>
+                        <p className="text-xs text-slate-400">请选择您要进入游戏的账号</p>
+                    </div>
+
+                    {accounts.map((account) => {
+                        const isSelected = selectedId === account.uid
+                        return (
+                            <div
+                                key={account.uid}
+                                onClick={() => setSelectedId(account.uid)}
+                                className={`p-4 rounded-xl border-2 transition-all cursor-pointer ${
+                                    isSelected
+                                        ? "border-primary bg-highlight ring-4 ring-primary/5"
+                                        : "border-slate-100 bg-slate-50 hover:border-slate-300"
+                                }`}
+                            >
+                                <div className="flex items-center gap-4">
+                                    <div className="flex-1">
+                                        <h3 className={`font-bold text-base ${isSelected ? "text-slate-900" : "text-slate-700"}`}>
+                                            {account.user_name}
+                                        </h3>
+                                        <p className="text-xs text-slate-500 font-mono">ID: {account.uid}</p>
+                                    </div>
+                                    <div className={isSelected ? "text-primary" : "text-slate-300"}>
+                                        <span className="material-symbols-outlined">
+                                            {isSelected ? <CircleCheck size={20} /> : <Circle size={20} />}
+                                        </span>
+                                    </div>
+                                </div>
+                            </div>
+                        )
+                    })}
+                </section>
+
+                <footer className="p-5 pt-2 flex flex-col gap-3">
+                    <button 
+                        onClick={handleConfirm}
+                        disabled={loading}
+                        className={`w-full bg-primary hover:bg-blue-800 text-white font-bold py-3.5 rounded-xl shadow-lg shadow-blue-200 transition-all active:scale-[0.98] ${
+                            loading ? 'opacity-70 cursor-not-allowed' : ''
+                        }`}
+                    >
+                        {loading ? '进入中...' : '确认进入'}
+                    </button>
+                    <button 
+                        onClick={onClose}
+                        disabled={loading}
+                        className="w-full py-2 text-sm font-medium text-slate-400 hover:text-slate-600 transition-colors"
+                    >
+                        使用其他账号登录
+                    </button>
+                </footer>
+            </main>
+        </div>
+    )
+}

+ 158 - 0
src/components/payment/index.tsx

@@ -0,0 +1,158 @@
+import { CircleX, ShoppingBag } from "lucide-react"
+import { useState, useEffect } from "react"
+import { useAtomValue, useSetAtom } from "jotai"
+import { modalStackAtom, openModalAction } from "../../store"
+import { createPaymentOrderApi, getPaymentLinkApi } from "../../api/payment"
+
+export const PaymentIndex = () => {
+    const modalState = useAtomValue(modalStackAtom)
+    const openModal = useSetAtom(openModalAction)
+    const [method, setMethod] = useState<'alipay' | 'wechat'>('alipay')
+    const [loading, setLoading] = useState(false)
+    const [orderId, setOrderId] = useState('')
+    const [payChannel, setPayChannel] = useState<any[]>([])
+
+    const paymentData = modalState.payment.data
+
+    useEffect(() => {
+        const createOrder = async () => {
+            if (modalState.payment.isOpen && paymentData && !orderId) {
+                setLoading(true)
+                try {
+                    const res: any = await createPaymentOrderApi(paymentData)
+                    if (res.order_id) {
+                        setOrderId(res.order_id)
+                        setPayChannel(res.pay_channel || [])
+                    }
+                } catch (error) {
+                    console.error('创建订单失败:', error)
+                } finally {
+                    setLoading(false)
+                }
+            }
+        }
+        createOrder()
+    }, [modalState.payment.isOpen, paymentData, orderId])
+
+    const handleClose = () => {
+        setOrderId('')
+        setPayChannel([])
+        openModal({ name: 'payment', item: { isOpen: false } })
+    }
+
+    const handlePay = async () => {
+        if (!orderId) return
+        setLoading(true)
+        try {
+            // 根据 method 找到对应的 channel_id
+            const selectedChannel = payChannel.find(c => c.name.includes(method === 'alipay' ? '支付宝' : '微信'))
+            const pay_channel_id = selectedChannel ? selectedChannel.id : (method === 'alipay' ? 'alipay' : 'wechat')
+
+            const res: any = await getPaymentLinkApi({
+                order_id: orderId,
+                pay_channel_id: pay_channel_id
+            })
+
+            if (res.url) {
+                window.location.href = res.url
+            } else {
+                alert('获取支付链接失败')
+            }
+        } catch (error: any) {
+            console.error('获取支付链接失败:', error)
+            alert(error.message || '获取支付链接失败')
+        } finally {
+            setLoading(false)
+        }
+    }
+
+    if (!modalState.payment.isOpen) return null
+
+    return (
+        <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm">
+            <div className="w-full max-w-[350px] mx-5 bg-white dark:bg-slate-900 rounded-xl shadow-2xl overflow-hidden border border-slate-200 dark:border-slate-800">
+
+                <div className="flex items-center bg-white dark:bg-slate-900 p-2 border-b border-slate-100 dark:border-slate-800 justify-between">
+                    <button 
+                        onClick={handleClose}
+                        className="text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 p-2 rounded-full transition-colors"
+                    >
+                        <span className="material-symbols-outlined block"><CircleX /></span>
+                    </button>
+                    <h2 className="text-slate-900 dark:text-slate-100 text-lg font-bold leading-tight tracking-tight flex-1 text-center pr-10">确认付款</h2>
+                </div>
+
+                <div className="flex flex-col items-center py-4 px-2 bg-slate-50 dark:bg-slate-800/50">
+                    <div className="bg-primary/10 p-4 rounded-full mb-4">
+                        <span className="material-symbols-outlined text-primary text-4xl"><ShoppingBag /></span>
+                    </div>
+                    <div className="text-center">
+                        <h1 className="text-slate-900 dark:text-slate-100 tracking-tight text-4xl font-bold leading-tight">¥{paymentData?.money || '0.00'}</h1>
+                        <p className="text-slate-500 dark:text-slate-400 text-sm mt-1">{paymentData?.product_name || '商品名称'}</p>
+                    </div>
+                </div>
+
+                <div className="p-6">
+                    <h3 className="text-slate-900 dark:text-slate-100 text-sm font-bold uppercase tracking-wider mb-4">选择支付方式</h3>
+                    <div className="flex flex-col gap-3">
+
+                        <label 
+                            onClick={() => setMethod('alipay')}
+                            className={`group relative flex items-center gap-4 rounded-xl border-2 p-4 cursor-pointer transition-all ${
+                                method === 'alipay' ? 'border-primary bg-primary/5' : 'border-slate-200 dark:border-slate-800 hover:border-primary/50'
+                            }`}
+                        >
+                            <input 
+                                className="h-5 w-5 border-2 border-slate-300 dark:border-slate-600 text-primary focus:ring-primary focus:ring-offset-0" 
+                                name="payment_method" 
+                                type="radio" 
+                                checked={method === 'alipay'}
+                                onChange={() => setMethod('alipay')}
+                            />
+                            <div className="flex grow flex-col">
+                                <div className="flex items-center gap-2">
+                                    <p className="text-slate-900 dark:text-slate-100 text-sm font-bold">支付宝</p>
+                                </div>
+                                <p className="text-slate-500 dark:text-slate-400 text-xs mt-1">推荐使用支付宝支付,即时到账</p>
+                            </div>
+                        </label>
+
+                        <label 
+                            onClick={() => setMethod('wechat')}
+                            className={`group relative flex items-center gap-4 rounded-xl border-2 p-4 cursor-pointer transition-all ${
+                                method === 'wechat' ? 'border-primary bg-primary/5' : 'border-slate-200 dark:border-slate-800 hover:border-primary/50'
+                            }`}
+                        >
+                            <input 
+                                className="h-5 w-5 border-2 border-slate-300 dark:border-slate-600 text-primary focus:ring-primary focus:ring-offset-0" 
+                                name="payment_method" 
+                                type="radio" 
+                                checked={method === 'wechat'}
+                                onChange={() => setMethod('wechat')}
+                            />
+                            <div className="flex grow flex-col">
+                                <div className="flex items-center gap-2">
+                                    <p className="text-slate-900 dark:text-slate-100 text-sm font-bold">微信支付</p>
+                                </div>
+                                <p className="text-slate-500 dark:text-slate-400 text-xs mt-1">使用微信快捷支付</p>
+                            </div>
+                        </label>
+                    </div>
+                </div>
+
+                <div className="p-6 pt-0">
+                    <button 
+                        onClick={handlePay}
+                        disabled={loading || !orderId}
+                        className={`w-full bg-primary hover:bg-primary/90 text-white font-bold py-4 rounded-xl shadow-lg shadow-primary/20 transition-all active:scale-[0.98] ${(loading || !orderId) ? 'opacity-50 cursor-not-allowed' : ''}`}
+                    >
+                        {loading ? '处理中...' : `立即支付 ¥${paymentData?.money || '0.00'}`}
+                    </button>
+                    <p className="text-center text-xs text-slate-400 dark:text-slate-500 mt-4">
+                        支付即视为同意 <a className="text-primary hover:underline" href="#">服务协议</a> 与 <a className="text-primary hover:underline" href="#">隐私政策</a>
+                    </p>
+                </div>
+            </div>
+        </div>
+    )
+}

+ 78 - 0
src/components/real-name/index.tsx

@@ -0,0 +1,78 @@
+import { useAtomValue } from "jotai"
+import { modalStackAtom } from "../../store"
+import { IdCard, ShieldCheck, SquareUserRound, Lock } from "lucide-react";
+
+export const RealNameIndex = () => {
+    const modalState = useAtomValue(modalStackAtom)
+    return (
+        <>
+            {
+                modalState.realName.isOpen ?
+                    <div className="relative min-h-screen w-full flex items-center justify-center p-4 bg-slate-200 dark:bg-slate-900">
+
+                        
+
+                        <div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/60 backdrop-blur-sm px-4">
+
+                            <div className="w-full max-w-[400px] bg-white dark:bg-slate-900 rounded-xl shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-300">
+
+                                <div className="flex items-center justify-center px-6 pt-6 pb-2">
+                                   
+                                    <h2 className="text-slate-900 dark:text-slate-100 text-xl font-bold">实名认证</h2>
+                                   
+                                </div>
+
+                                <div className="flex justify-center py-4">
+                                    <div className="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center">
+                                        <span className="material-symbols-outlined text-primary">  <ShieldCheck size={40}/></span>
+                                    </div>
+                                </div>
+                                <div className="px-6 pb-6 text-center">
+                                    <p className="text-slate-500 dark:text-slate-400 text-sm leading-relaxed">
+                                        根据国家新闻出版署《关于防止未成年人沉迷网络游戏的通知》,游戏用户需进行实名注册。我们承诺会对您的个人信息严格保密。
+                                    </p>
+                                </div>
+
+                                <div className="px-6 space-y-4">
+
+                                    <div className="space-y-1.5">
+                                        <label className="text-slate-700 dark:text-slate-300 text-sm font-semibold flex items-center gap-1">
+                                            姓名
+                                        </label>
+                                        <div className="relative group">
+                                            <span className="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-xl group-focus-within:text-primary transition-colors"><SquareUserRound /></span>
+                                            <input className="w-full pl-10 pr-4 py-3 bg-slate-50 dark:bg-slate-800 border-slate-200 dark:border-slate-700 rounded-lg focus:ring-2 focus:ring-sky-400 focus:border-sky-400 outline-none transition-all text-slate-900 dark:text-slate-100 placeholder:text-slate-400" placeholder="请输入真实姓名" type="text" />
+                                        </div>
+                                    </div>
+
+                                    <div className="space-y-1.5">
+                                        <label className="text-slate-700 dark:text-slate-300 text-sm font-semibold flex items-center gap-1">
+                                            身份证号
+                                        </label>
+                                        <div className="relative group">
+                                            <span className="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-xl group-focus-within:text-primary transition-colors"><IdCard /></span>
+                                            <input className="w-full pl-10 pr-4 py-3 bg-slate-50 dark:bg-slate-800 border-slate-200 dark:border-slate-700 rounded-lg focus:ring-2 focus:ring-sky-400 focus:border-sky-400 outline-none transition-all text-slate-900 dark:text-slate-100 placeholder:text-slate-400" placeholder="请输入18位身份证号码" type="text" />
+                                        </div>
+                                    </div>
+
+                                    
+                                </div>
+
+                                <div className="p-6">
+                                    <button className="w-full bg-primary hover:bg-primary/90 text-white font-bold py-4 rounded-xl shadow-lg shadow-primary/30 transition-all active:scale-[0.98]">
+                                        提交认证
+                                    </button>
+                                    <p className="mt-4 text-center text-xs text-slate-400 dark:text-slate-500 flex items-center justify-center gap-1">
+                                        <span className="material-symbols-outlined text-sm"><Lock size={14}/></span>
+                                        您的信息将进行加密传输以保证安全
+                                    </p>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    :
+                    <></>
+            }
+        </>
+    )
+}

+ 177 - 0
src/components/register/account-register.tsx

@@ -0,0 +1,177 @@
+import { CirclePlay, Send } from "lucide-react";
+import { useState } from "react";
+import { getRandomAccountApi, registerAccountApi } from "../../api/register";
+import { loginAccountApi } from "../../api/login";
+import { useAtomValue, useSetAtom } from "jotai";
+import { modalStackAtom, openModalAction } from "../../store";
+import { pmBridge } from "../../lib/PostMessageBridge";
+
+export interface AccountRegisterProps {
+    switchPhoneRegister: () => void;
+    switchLogin: () => void;
+}
+
+export const AccountRegister = ({ switchPhoneRegister, switchLogin }: AccountRegisterProps) => {
+    const [userName, setUserName] = useState('');
+    const [userPwd, setUserPwd] = useState('');
+    const [loading, setLoading] = useState(false);
+    const [agreed, setAgreed] = useState(false);
+
+    const modalState = useAtomValue(modalStackAtom);
+    const openModal = useSetAtom(openModalAction);
+
+    const handleRandomAccount = async () => {
+        setLoading(true);
+        try {
+            const res = await getRandomAccountApi({ is_activate: 1 });
+            if (res.user_name && res.user_pwd) {
+                setUserName(res.user_name);
+                setUserPwd(res.user_pwd);
+            }
+        } catch (error: any) {
+            console.error('获取随机账号失败:', error);
+            alert(error.message || '获取随机账号失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleRegister = async () => {
+        if (!userName) {
+            alert('请输入账号');
+            return;
+        }
+        if (!userPwd) {
+            alert('请输入密码');
+            return;
+        }
+        if (!agreed) {
+            alert('请先同意服务协议与隐私权政策');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            // 首先执行注册
+            await registerAccountApi({
+                user_name: userName,
+                user_pwd: userPwd
+            });
+
+            // 注册成功后立即执行登录
+            const loginRes = await loginAccountApi({
+                user_name: userName,
+                user_pwd: userPwd
+            });
+
+            if (loginRes.token) {
+                // 存储 token
+                localStorage.setItem('token', loginRes.token);
+
+                // 使用 pmBridge 回传给父 iframe
+                const requestId = modalState.login.requestId;
+                pmBridge.sendToIframe("LOGIN SUCCESS", loginRes, requestId);
+
+                // 关闭登录弹窗
+                openModal({ name: 'login', item: { isOpen: false } });
+            } else {
+                alert('登录失败:未返回 token');
+            }
+        } catch (error: any) {
+            console.error('流程失败:', error);
+            alert(error.message || '操作失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    return (
+        <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm">
+
+            <div className="w-full max-w-[320px] mx-5 bg-[#f5f7f8] rounded-2xl shadow-2xl overflow-hidden flex flex-col scale-100">
+
+                <div className="flex flex-col items-center pt-4 pb-6 px-6">
+
+                    <h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">创建账号</h1>
+                </div>
+
+                {/* Tabs */}
+                <div className="flex border-b border-slate-200 dark:border-slate-800 px-6 gap-8">
+                    <button onClick={switchPhoneRegister} className="flex flex-col items-center justify-center border-b-[3px] border-transparent text-[#64748b] pb-3 pt-2  hover:text-primary transition-colors">
+                        <p className="text-sm font-bold leading-normal tracking-wide">手机号注册</p>
+                    </button>
+                    <button className="flex flex-col items-center justify-center border-b-[3px] border-primary text-primary pb-3 pt-2">
+                        <p className="text-sm font-bold leading-normal tracking-wide">账号注册</p>
+                    </button>
+                </div>
+
+                {/* Form Content */}
+                <div className="p-6 space-y-4">
+                    {/* Account Input */}
+                    <label className="text-xs font-semibold uppercase tracking-wider text-slate-400 ml-1">账号</label>
+                    <div className="flex gap-2">
+
+                        <div className="flex-1">
+                            <input 
+                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
+                                placeholder="请输入账号" 
+                                type="text"
+                                value={userName}
+                                onChange={(e) => setUserName(e.target.value)}
+                            />
+                        </div>
+                    </div>
+
+                    {/* Password Input */}
+                    <label className="text-xs font-semibold uppercase tracking-wider text-slate-400 ml-1">密码</label>
+                    <div className="flex gap-2">
+                        <div className="flex-1">
+                            <input 
+                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
+                                placeholder="请输入密码" 
+                                type="text" 
+                                value={userPwd}
+                                onChange={(e) => setUserPwd(e.target.value)}
+                            />
+                        </div>
+                    </div>
+                    <div className="flex justify-between items-center px-1">
+                        <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors cursor-pointer" onClick={switchLogin}>已有账号</a>
+                        <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors" href="#">遇到问题?</a>
+                    </div>
+
+                    {/* Action Button */}
+                    <button 
+                        onClick={handleRegister}
+                        disabled={loading}
+                        className={`w-full bg-primary hover:bg-primary/90 text-white font-bold h-12 rounded-lg shadow-lg  transition-all flex items-center justify-center gap-2 ${loading ? 'opacity-70 cursor-not-allowed' : ''}`}
+                    >
+                        {loading ? '注册中...' : '立即注册并进入游戏'} <CirclePlay size={18} />
+                    </button>
+                    <button 
+                        onClick={handleRandomAccount}
+                        disabled={loading}
+                        className="w-full text-primary font-bold h-12 rounded-lg shadow-lg transition-all flex items-center justify-center gap-2 border border-primary/20 hover:bg-primary/5"
+                    >
+                        <Send size={18} />一键生成账号密码
+                    </button>
+                    {/* Secondary Links */}
+                  
+                    <div className="flex items-center gap-2 px-1 pt-2">
+                        <input 
+                            className="h-4 w-4 rounded border-slate-300 text-primary focus:ring-primary cursor-pointer" 
+                            id="tos" 
+                            type="checkbox" 
+                            checked={agreed}
+                            onChange={(e) => setAgreed(e.target.checked)}
+                        />
+                        <label htmlFor="tos" className="text-xs leading-relaxed text-slate-500 cursor-pointer select-none">
+                            我已阅读并同意 <a className="text-primary hover:underline" href="#">服务协议</a> 与 <a className="text-primary hover:underline" href="#">隐私权政策</a>
+                        </label>
+                    </div>
+                </div>
+
+            </div>
+        </div>
+    );
+}

+ 22 - 0
src/components/register/index.tsx

@@ -0,0 +1,22 @@
+import { useState } from "react"
+import { PhoneRegister } from "./phone-register"
+import { AccountRegister } from "./account-register"
+
+export const RegisterIndex = ({switchLogin}: {switchLogin: () => void})=>{
+    const [registerType, setRegisterType] = useState('phone')
+    const switchPhoneRegister = () => {
+        setRegisterType('phone')
+    }
+    const switchAccountRegister = () => {
+        setRegisterType('account')
+    }
+    return (
+        <>
+            {registerType === 'phone' ? (
+                <PhoneRegister switchAccountRegister={switchAccountRegister} switchLogin={switchLogin} />
+            ) : (
+                <AccountRegister switchPhoneRegister={switchPhoneRegister} switchLogin={switchLogin} />
+            )}
+        </>
+    )
+}

+ 221 - 0
src/components/register/phone-register.tsx

@@ -0,0 +1,221 @@
+import { CirclePlay, Send } from 'lucide-react';
+import { useState, useEffect } from 'react';
+import { loginSendCodeApi, getAccountListByCodeApi, loginPhoneCodeLoginApi } from '../../api/login';
+import { SelectAccount, type Account } from '../login/select-account';
+import { useAtomValue, useSetAtom } from 'jotai';
+import { modalStackAtom, openModalAction } from '../../store';
+import { pmBridge } from '../../lib/PostMessageBridge';
+
+export interface RegisterProps {
+    switchAccountRegister: () => void;
+    switchLogin: () => void;
+}
+
+export const PhoneRegister = ({ switchAccountRegister, switchLogin }: RegisterProps) => {
+    const [mobile, setMobile] = useState('');
+    const [code, setCode] = useState('');
+    const [agreed, setAgreed] = useState(false);
+    const [countdown, setCountdown] = useState(0);
+    const [loading, setLoading] = useState(false);
+    const [showSelectAccount, setShowSelectAccount] = useState(false);
+    const [accountList, setAccountList] = useState<Account[]>([]);
+
+    const modalState = useAtomValue(modalStackAtom);
+    const openModal = useSetAtom(openModalAction);
+
+    useEffect(() => {
+        let timer: any;
+        if (countdown > 0) {
+            timer = setInterval(() => {
+                setCountdown(prev => prev - 1);
+            }, 1000);
+        }
+        return () => clearInterval(timer);
+    }, [countdown]);
+
+    const handleSendCode = async () => {
+        if (!mobile || !/^1[3-9]\d{9}$/.test(mobile)) {
+            alert('请输入正确的手机号');
+            return;
+        }
+
+        try {
+            await loginSendCodeApi({ mobile });
+            setCountdown(60);
+        } catch (error: any) {
+            alert(error.message || '发送失败');
+        }
+    };
+
+    const handleSuccess = (res: any) => {
+        if (res.token) {
+            localStorage.setItem('token', res.token);
+            const requestId = modalState.login.requestId;
+            pmBridge.sendToIframe("LOGIN SUCCESS", res, requestId);
+            openModal({ name: 'login', item: { isOpen: false } });
+        } else {
+            alert('登录失败:未返回 token');
+        }
+    };
+
+    const handleRegisterSubmit = async () => {
+        if (!mobile || !code) {
+            alert('请输入手机号和验证码');
+            return;
+        }
+        if (!agreed) {
+            alert('请先同意服务协议与隐私权政策');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            const res: any = await getAccountListByCodeApi({ mobile, code });
+            const accounts = res || [];
+
+            if (accounts.length > 1) {
+                setAccountList(accounts);
+                setShowSelectAccount(true);
+            } else if (accounts.length === 1) {
+                // 如果只有一个账号,直接登录
+                const loginRes = await loginPhoneCodeLoginApi({
+                    mobile,
+                    code,
+                    user_name: accounts[0].user_name
+                });
+                handleSuccess(loginRes);
+            } else {
+                // 如果没有账号,通常这个接口在注册流程中也可能返回新创建的账号信息
+                // 如果后端逻辑是 verify_code 同时也处理注册,则直接使用 res
+                if (res.token) {
+                    handleSuccess(res);
+                } else {
+                    alert('获取账号列表失败');
+                }
+            }
+        } catch (error: any) {
+            alert(error.message || '操作失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleSelectConfirm = async (account: Account) => {
+        setLoading(true);
+        try {
+            const res = await loginPhoneCodeLoginApi({
+                mobile,
+                code,
+                user_name: account.user_name
+            });
+            handleSuccess(res);
+        } catch (error: any) {
+            alert(error.message || '登录失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    if (showSelectAccount) {
+        return (
+            <SelectAccount
+                accounts={accountList}
+                mobile={mobile}
+                code={code}
+                onConfirm={handleSelectConfirm}
+                onClose={() => setShowSelectAccount(false)}
+            />
+        );
+    }
+    return (
+        <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm">
+
+            <div className="w-full max-w-[320px] mx-5 bg-[#f5f7f8] rounded-2xl shadow-2xl overflow-hidden flex flex-col scale-100">
+
+                <div className="flex flex-col items-center pt-4 pb-6 px-6">
+
+                    <h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">创建账号</h1>
+                </div>
+
+                {/* Tabs */}
+                <div className="flex border-b border-slate-200 dark:border-slate-800 px-6 gap-8">
+                    <button className="flex flex-col items-center justify-center border-b-[3px] border-primary text-primary pb-3 pt-2 border-primary text-primary transition-colors">
+                        <p className="text-sm font-bold leading-normal tracking-wide">手机号注册</p>
+                    </button>
+                    <button onClick={switchAccountRegister} className="flex flex-col items-center justify-center border-b-[3px] border-transparent text-[#64748b]   hover:text-primary pb-3 pt-2">
+                        <p className="text-sm font-bold leading-normal tracking-wide">账号注册</p>
+                    </button>
+                </div>
+
+                {/* Form Content */}
+                <div className="p-6 space-y-4">
+                    {/* Country Code & Phone Input */}
+                    <label className="text-xs font-semibold uppercase tracking-wider text-slate-400 ml-1">手机号码</label>
+                    <div className="flex gap-2">
+
+                        <div className="flex-1">
+
+                            <input 
+                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
+                                placeholder="请输入手机号" 
+                                type="tel" 
+                                value={mobile}
+                                onChange={(e) => setMobile(e.target.value)}
+                            />
+                        </div>
+                    </div>
+
+                    {/* Verification Code */}
+                    <label className="text-xs font-semibold uppercase tracking-wider text-slate-400 ml-1">验证码</label>
+                    <div className="flex gap-2">
+                        <div className="flex-1">
+                            <input 
+                                className="p-2 form-input input-field w-full rounded-lg border-slate-200 bg-white text-slate-900 h-12 text-sm" 
+                                placeholder="请输入验证码" 
+                                type="text" 
+                                value={code}
+                                onChange={(e) => setCode(e.target.value)}
+                            />
+                        </div>
+                        <button 
+                            onClick={handleSendCode}
+                            disabled={countdown > 0}
+                            className={`px-4 h-12 text-primary font-medium text-sm bg-primary/10 hover:bg-primary/20 rounded-lg whitespace-nowrap transition-colors border border-primary/20 ${countdown > 0 ? 'opacity-50 cursor-not-allowed' : ''}`}
+                        >
+                            {countdown > 0 ? `${countdown}s` : '获取验证码'}
+                        </button>
+                    </div>
+                    <div className="flex justify-between items-center px-1">
+                        <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors cursor-pointer" onClick={switchLogin}>已有账号</a>
+                        <a className="text-xs text-slate-500 dark:text-slate-400 hover:text-primary transition-colors hover:underline" href="#">遇到问题?</a>
+                    </div>
+                    {/* Action Button */}
+                    <button 
+                        onClick={handleRegisterSubmit}
+                        disabled={loading}
+                        className={`w-full bg-primary hover:bg-primary/90 text-white font-bold h-12 rounded-lg shadow-lg  transition-all flex items-center justify-center gap-2 ${loading ? 'opacity-70 cursor-not-allowed' : ''}`}
+                    >
+                        {loading ? '处理中...' : '立即注册并进入游戏'} <CirclePlay size={18} />
+                    </button>
+
+                    <div className="flex items-center gap-2 px-1 pt-2">
+                        <input 
+                            className="h-4 w-4 rounded border-slate-300 text-primary focus:ring-primary cursor-pointer" 
+                            id="tos-phone" 
+                            type="checkbox" 
+                            checked={agreed}
+                            onChange={(e) => setAgreed(e.target.checked)}
+                        />
+                        <label htmlFor="tos-phone" className="text-xs leading-relaxed text-slate-500 cursor-pointer select-none">
+                            我已阅读并同意 <a className="text-primary hover:underline" href="#">服务协议</a> 与 <a className="text-primary hover:underline" href="#">隐私权政策</a>
+                        </label>
+                    </div>
+                </div>
+
+
+
+
+            </div>
+        </div>
+    );
+};

+ 10 - 0
src/components/report/index.ts

@@ -0,0 +1,10 @@
+import { pmBridge } from "../../lib/PostMessageBridge";
+import { reportRoleApi } from "../../api/role";
+
+export const report = async ({data,requestId}: {data: any,requestId: string})=>{
+
+    // todo 上报接口
+    await reportRoleApi(data)
+    // 成功
+     pmBridge.sendToIframe('REPORT_SUCCESS', { success: true, message:"上报成功" }, requestId);
+}

+ 55 - 0
src/components/slide-bar/index.tsx

@@ -0,0 +1,55 @@
+import { Bolt, ChevronRight, IdCard, Key, Lock, Smartphone, SquareArrowRightExit } from "lucide-react";
+
+export default function SlideBar() {
+    return (
+        <>
+            <div className="fixed left-0 top-0 flex h-screen w-[80%] overflow-hidden">
+
+                <div className="absolute inset-0 z-10 bg-white backdrop-blur-sm"></div>
+
+                <div className="relative z-20 flex h-full w-full">
+                    <div className="flex h-full w-80 flex-col bg-background-light dark:bg-background-dark shadow-2xl">
+
+                        <div className="p-6 pb-4 border-b border-slate-200 dark:border-slate-800">
+                            <div className="flex">
+                                <div className="flex flex-col">
+                                    <h2 className="text-xl font-bold tracking-tight text-slate-900 dark:text-slate-100">CyberWarrior_99</h2>
+                                    <p className="text-xs font-medium text-primary bg-primary/10 px-2 py-0.5 rounded-full inline-block mt-1">ID: #8842-X01</p>
+                                </div>
+                            </div>
+                        </div>
+
+                        <div className="flex-1 overflow-y-auto py-4">
+                            <nav className="space-y-2">
+                                <p className="px-3 pb-2 text-xs font-semibold uppercase tracking-wider text-slate-400">账户安全</p>
+                                <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary">
+                                    <span className="material-symbols-outlined"><Smartphone size="16" /></span>
+                                    <span className="text-[14px] font-bold">修改手机</span>
+                                    <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
+                                </button>
+                                <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary">
+                                    <span className="material-symbols-outlined"><Key size="16" /></span>
+                                    <span className="text-[14px] font-bold">绑定密码</span>
+                                    <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
+                                </button>
+                                <button className="border-b border-slate-200 dark:border-slate-800 flex w-full items-center gap-4  px-3 py-3 text-slate-700 transition-colors hover:bg-primary/10 hover:text-primary dark:text-slate-300 dark:hover:bg-primary/20 dark:hover:text-primary">
+                                    <span className="material-symbols-outlined"><IdCard size="16" /></span>
+                                    <span className="text-[14px] font-bold">实名认证</span>
+                                    <span className="material-symbols-outlined ml-auto text-slate-300"><ChevronRight size="16" /></span>
+                                </button>
+                               </nav>
+                        </div>
+                        <div className="p-4 border-t border-slate-200 dark:border-slate-800">
+                            <button className="flex w-full items-center justify-center gap-2 rounded-xl bg-slate-100 py-3 text-[14px] font-bold text-slate-900 transition-colors hover:bg-red-50 hover:text-red-600 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-red-900/30 dark:hover:text-red-400">
+                                <span className="material-symbols-outlined text-xl"><SquareArrowRightExit size="16" /></span>
+                                <span>退出登录</span>
+                            </button>
+                            <p className="mt-4 text-center text-[10px] text-slate-400">SDK Version {import.meta.env.VITE_SDK_VERSION}</p>
+                        </div>
+                    </div>
+                    <div className="flex-1 cursor-pointer"></div>
+                </div>
+            </div>
+        </>
+    );
+}

+ 69 - 0
src/index.css

@@ -0,0 +1,69 @@
+@import "tailwindcss";
+
+@theme {
+  --color-primary: #0d7ff2;
+  --color-primary-50: #eff6ff;
+  --color-primary-100: #dbeafe;
+  --color-primary-200: #bfdbfe;
+  --color-primary-300: #93c5fd;
+  --color-primary-400: #60a5fa;
+  --color-primary-500: #3b82f6;
+  --color-primary-600: #2563eb;
+  --color-primary-700: #1d4ed8;
+  --color-primary-800: #1e40af;
+  --color-primary-900: #1e3a8a;
+  --color-primary-950: #172554;
+
+  
+  --animate-glow: glow 3s infinite;
+}
+
+
+.accent-glow {
+  animation: var(--animate-glow);
+}
+
+.tab-transition {
+  transition: all 0.3s ease-in-out;
+}
+
+.modal-backdrop {
+  background: radial-gradient(circle, rgba(245,247,248, 0.7) 0%, rgba(245,247,248, 0.95) 100%);
+}
+
+.glass-panel {
+  background: rgba(245,247,248, 0.85);
+  backdrop-filter: blur(16px);
+  border: 1px solid rgba(255, 255, 255, 0.15);
+}
+
+.input-field {
+  background: rgba(255, 255, 255 0.6) !important;
+  border: 1px solid rgba(245, 247, 248, 0.5) !important;
+  color: #7b818d !important;
+}
+
+.input-field:focus {
+  border-color: #38bdf8 !important;
+  outline: none;
+  box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.2);
+}
+
+.tab-active {
+  color: #38bdf8;
+  border-bottom: 2px solid #38bdf8;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+  background-color: #f3f4f6;
+}
+
+#root {
+  width: 100%;
+  height: 100vh;
+}

+ 67 - 0
src/lib/EventBus.ts

@@ -0,0 +1,67 @@
+type EventHandler<T = any> = (data: T) => void;
+
+interface EventMap {
+  [eventName: string]: EventHandler[];
+}
+
+class EventBus {
+  private events: EventMap = {};
+
+  /**
+   * 监听事件
+   * @param event 事件名称
+   * @param handler 处理函数
+   */
+  on<T = any>(event: string, handler: EventHandler<T>): void {
+    if (!this.events[event]) {
+      this.events[event] = [];
+    }
+    this.events[event].push(handler);
+  }
+
+  /**
+   * 取消监听事件
+   * @param event 事件名称
+   * @param handler 处理函数(可选,不传则清空该事件所有监听)
+   */
+  off<T = any>(event: string, handler?: EventHandler<T>): void {
+    if (!this.events[event]) return;
+    if (handler) {
+      this.events[event] = this.events[event].filter((h) => h !== handler);
+    } else {
+      delete this.events[event];
+    }
+  }
+
+  /**
+   * 触发事件
+   * @param event 事件名称
+   * @param data 传递的数据
+   */
+  emit<T = any>(event: string, data?: T): void {
+    if (!this.events[event]) return;
+    this.events[event].forEach((handler) => {
+      try {
+        handler(data);
+      } catch (error) {
+        console.error(`Error executing event handler for ${event}:`, error);
+      }
+    });
+  }
+
+  /**
+   * 只监听一次事件
+   * @param event 事件名称
+   * @param handler 处理函数
+   */
+  once<T = any>(event: string, handler: EventHandler<T>): void {
+    const onceHandler = (data: T) => {
+      handler(data);
+      this.off(event, onceHandler);
+    };
+    this.on(event, onceHandler);
+  }
+}
+
+// 导出单例
+export const eventBus = new EventBus();

+ 79 - 0
src/lib/PostMessageBridge.ts

@@ -0,0 +1,79 @@
+import { eventBus } from './EventBus';
+
+export interface SDKMessage {
+  type: string;
+  requestId?: string;
+  data?: any;
+}
+
+class PostMessageBridge {
+  private iframeWindow: Window | null = null;
+  private origin: string = '*';
+
+  /**
+   * 初始化并绑定 iframe
+   */
+  init(iframe: HTMLIFrameElement) {
+    if (iframe.contentWindow) {
+      this.iframeWindow = iframe.contentWindow;
+    }
+    
+    // 监听来自 iframe 的消息
+    window.addEventListener('message', this.handleMessage.bind(this));
+  }
+
+  /**
+   * 销毁并解绑监听
+   */
+  destroy() {
+    window.removeEventListener('message', this.handleMessage.bind(this));
+    this.iframeWindow = null;
+  }
+
+  /**
+   * 处理接收到的 iframe 消息
+   */
+  private handleMessage(event: MessageEvent) {
+    // 安全起见,实际项目中最好验证 event.origin
+    // if (event.origin !== this.origin && this.origin !== '*') return;
+
+    try {
+      const message: SDKMessage = typeof event.data === 'string' 
+        ? JSON.parse(event.data) 
+        : event.data;
+      
+      // 简单验证是否是 SDK 产生的消息(要求包含 type)
+      if (message && message.type) {
+        console.log('[Parent] Received message from iframe:', message);
+        // 使用 EventBus 转发事件供 UI 组件订阅
+        // 为了方便将结果回传给 iframe,附带 requestId
+        eventBus.emit(message.type, {
+          requestId: message.requestId,
+          data: message.data
+        });
+      }
+    } catch (error) {
+      // 解析失败忽略,可能是非 SDK 发送的常规 postMessage
+    }
+  }
+
+  /**
+   * 发送消息给 iframe
+   * @param type 消息类型
+   * @param data 数据载荷
+   * @param requestId 请求 ID(如果是对端发起的回复,必须带上)
+   */
+  sendToIframe(type: string, data?: any, requestId?: string) {
+    if (!this.iframeWindow) {
+      console.warn('[Parent] Cannot send message: iframe not initialized');
+      return;
+    }
+    
+    const message: SDKMessage = { type, data, requestId };
+    console.log('[Parent] Sending message to iframe:', message);
+    this.iframeWindow.postMessage(JSON.stringify(message), this.origin);
+  }
+}
+
+// 导出单例
+export const pmBridge = new PostMessageBridge();

+ 10 - 0
src/main.tsx

@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+  <StrictMode>
+    <App />
+  </StrictMode>,
+)

+ 6 - 0
src/store/index.ts

@@ -0,0 +1,6 @@
+import { openModalAction, modalStackAtom } from "./modal-atom";
+
+export {
+    modalStackAtom,
+    openModalAction
+}

+ 38 - 0
src/store/modal-atom.ts

@@ -0,0 +1,38 @@
+
+import { atom } from "jotai";
+
+
+interface ModalItem{
+    isOpen:boolean,
+    requestId:string,
+    data:any
+}
+
+interface ModalState {
+    login: ModalItem;
+    payment: ModalItem;
+    report: ModalItem;
+    realName: ModalItem;
+    init: ModalItem;
+}
+
+// 原始原子:弹窗状态
+export const modalStackAtom = atom<ModalState>({
+    login: { isOpen: false, requestId: '', data: null },
+    payment: { isOpen: false, requestId: '', data: null },
+    report: { isOpen: false, requestId: '', data: null },
+    realName: { isOpen: false, requestId: '', data: null },
+    init: { isOpen: true, requestId: '', data: null },
+    
+});
+
+export const openModalAction = atom(
+  null,
+  (get, set, payload: { name: keyof ModalState; item: Partial<ModalItem> }) => {
+    const state = get(modalStackAtom);
+    set(modalStackAtom, {
+      ...state,
+      [payload.name]: { ...state[payload.name], ...payload.item }
+    });
+  }
+);

+ 45 - 0
src/store/user-atom.ts

@@ -0,0 +1,45 @@
+import { atom, getDefaultStore } from "jotai";
+
+interface UserState {
+    id: string;
+    username: string;
+    avatar: string;
+    token: string;
+    phone: string;
+}
+
+export const userStateAtom = atom<UserState>({
+    id: '',
+    username: '',
+    avatar: '',
+    token: '',
+    phone: '',
+})
+
+export const setUserStateAtom = atom(null, (get, set, user: UserState) => {
+    set(userStateAtom, user);
+})
+
+export const clearUserStateAtom = atom(null, (get, set) => {
+    set(userStateAtom, {
+        id: '',
+        username: '',
+        avatar: '',
+        token: '',
+        phone: '',
+    });
+})
+
+export const getUserStateAtom = atom((get) => {
+    return get(userStateAtom);
+})
+
+export const updateUserStateAtom = atom(null, (get, set, user: Partial<UserState>) => {
+    set(userStateAtom, { ...get(userStateAtom), ...user });
+})
+
+export const logout = () => {
+    const store = getDefaultStore()
+    store.set(clearUserStateAtom)
+    localStorage.removeItem('token')
+}

+ 13 - 0
src/types/window.d.ts

@@ -0,0 +1,13 @@
+export {}
+
+declare global {
+  interface Window {
+    webkit: {
+      messageHandlers: {
+        [key: string]: {
+          postMessage: (message: any) => void
+        }
+      }
+    }
+  }
+}

+ 1 - 0
src/typescript.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>

+ 232 - 0
src/utils/common-params.ts

@@ -0,0 +1,232 @@
+import CryptoJS from 'crypto-js'
+import { getURLparams } from './index'
+
+// 初始化配置接口
+export interface InitConfig {
+  app_version: string
+}
+
+// 缓存 key
+const CACHE_KEYS = {
+  GAME_ID: 'sdk_game_id',
+  PACKAGE_NAME: 'sdk_package_name',
+  APPKEY: 'sdk_appkey',
+  TRACK_ID: 'sdk_track_id',
+  SDK_VERSION: 'sdk_version',
+  APP_VERSION: 'sdk_app_version',
+  IMEI: 'sdk_imei'
+}
+
+/**
+ * 初始化 SDK 配置
+ */
+export function initSDKConfig(config: InitConfig) {
+
+  if (config.app_version) {
+    localStorage.setItem(CACHE_KEYS.APP_VERSION, config.app_version)
+  }
+}
+
+/**
+ * 获取缓存的配置
+ */
+export function getCachedConfig() {
+  return {
+    game_id: getURLparams('game_id') || '',
+    package_name: localStorage.getItem(CACHE_KEYS.PACKAGE_NAME) || '',
+    appkey: localStorage.getItem(CACHE_KEYS.APPKEY) || '',
+    track_id: localStorage.getItem(CACHE_KEYS.TRACK_ID) || '',
+    sdk_version: localStorage.getItem(CACHE_KEYS.SDK_VERSION) || '',
+    app_version: localStorage.getItem(CACHE_KEYS.APP_VERSION) || '1.0.0'
+  }
+}
+
+/**
+ * 生成或获取唯一标识符 (imei/utma)
+ */
+export function getImei(): string {
+  let imei = localStorage.getItem(CACHE_KEYS.IMEI)
+  if (!imei) {
+    // 生成唯一标识符
+    imei = generateUniqueId()
+    localStorage.setItem(CACHE_KEYS.IMEI, imei)
+  }
+  return imei
+}
+
+/**
+ * 生成唯一 ID
+ */
+function generateUniqueId(): string {
+  const timestamp = Date.now().toString(36)
+  const randomStr = Math.random().toString(36).substring(2, 15)
+  const userAgent = navigator.userAgent.replace(/\s/g, '').substring(0, 10)
+  return `${timestamp}-${randomStr}-${userAgent}`.substring(0, 32)
+}
+
+/**
+ * 获取操作系统类型
+ * 1(Android) 或 2(iOS) 或 3(and-小程序) 或 4(ios-小程序) 或 5(h5)
+ */
+export function getOS(): number {
+  // H5 环境默认为 5
+  // 可以根据实际需求判断是否为小程序
+  const ua = navigator.userAgent.toLowerCase()
+  
+  // 判断是否为小程序环境(需要根据实际情况调整)
+  if (ua.includes('miniprogram')) {
+    if (ua.includes('android')) {
+      return 3 // and-小程序
+    } else if (ua.includes('iphone') || ua.includes('ios')) {
+      return 4 // ios-小程序
+    }
+  }
+  
+  // 判断是否为原生 App 环境
+  if (ua.includes('android')) {
+    return 1 // Android
+  } else if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ios')) {
+    return 2 // iOS
+  }
+  
+  // 默认 H5
+  return 5
+}
+
+/**
+ * 获取设备品牌
+ */
+export function getBrand(): string {
+  const ua = navigator.userAgent
+  if (ua.includes('iPhone')) {
+    return 'Apple'
+  } else if (ua.includes('iPad')) {
+    return 'Apple'
+  } else if (ua.includes('Android')) {
+    // 尝试从 UA 中提取品牌
+    const match = ua.match(/([A-Za-z]+)\s+\d+/)
+    if (match) {
+      return match[1]
+    }
+    return 'Android'
+  }
+  return 'Unknown'
+}
+
+/**
+ * 获取设备型号
+ */
+export function getModel(): string {
+  const ua = navigator.userAgent
+  if (ua.includes('iPhone')) {
+    const match = ua.match(/iPhone\s*([^;)]+)/)
+    return match ? match[1].trim() : 'iPhone'
+  } else if (ua.includes('iPad')) {
+    const match = ua.match(/iPad\s*([^;)]+)/)
+    return match ? match[1].trim() : 'iPad'
+  } else if (ua.includes('Android')) {
+    // Android 设备型号通常在 UA 中
+    const match = ua.match(/;\s*([^;)]+)\s*Build/)
+    if (match) {
+      return match[1].trim()
+    }
+    return 'Android Device'
+  }
+  return 'Unknown'
+}
+
+/**
+ * 获取系统版本
+ */
+export function getSystemVersion(): string {
+  const ua = navigator.userAgent
+  if (ua.includes('iPhone') || ua.includes('iPad')) {
+    const match = ua.match(/OS\s+([\d_]+)/)
+    if (match) {
+      return match[1].replace(/_/g, '.')
+    }
+  } else if (ua.includes('Android')) {
+    const match = ua.match(/Android\s+([\d.]+)/)
+    if (match) {
+      return match[1]
+    }
+  }
+  return navigator.platform || 'Unknown'
+}
+
+/**
+ * 获取系统语言
+ */
+export function getSystemLang(): string {
+  return navigator.language || navigator.languages?.[0] || 'zh-CN'
+}
+
+/**
+ * 获取 OAID (H5 环境可能无法获取,返回空字符串或默认值)
+ */
+export function getOaid(): string {
+  // H5 环境通常无法获取 OAID
+  // 如果需要,可以通过其他方式获取或返回默认值
+  return localStorage.getItem('sdk_oaid') || ''
+}
+
+/**
+ * 设置 OAID
+ */
+export function setOaid(oaid: string) {
+  localStorage.setItem('sdk_oaid', oaid)
+}
+
+/**
+ * 生成 sign
+ * 1、按key排序,拼接参数 key1=aa&key2=bbb
+ * 2、拼appkey key1=aa&key2=bbb&secret=appkey
+ * 3、md5 sign=md5(key1=aa&key2=bbb&secret=appkey)
+ */
+export function generateSign(params: Record<string, any>, appkey: string): string {
+  // 1、过滤掉空值和 sign 字段
+  const filteredParams: Record<string, any> = {}
+  for (const key in params) {
+    if (params[key] !== null && params[key] !== undefined && params[key] !== '' && key !== 'sign') {
+      filteredParams[key] = params[key]
+    }
+  }
+
+  // 2、按 key 排序
+  const sortedKeys = Object.keys(filteredParams).sort()
+  
+  // 3、拼接参数
+  const paramString = sortedKeys
+    .map(key => `${key}=${filteredParams[key]}`)
+    .join('&')
+  
+  // 4、拼 appkey
+  const signString = `${paramString}&secret=${appkey}`
+  
+  // 5、MD5 加密
+  return CryptoJS.MD5(signString).toString()
+}
+
+/**
+ * 获取所有公共参数
+ */
+export function getCommonParams(): Record<string, any> {
+  const config = getCachedConfig()
+  
+  return {
+    game_id: getURLparams('game_id'),
+    track_id: "",
+    os: 5,
+    imei: getImei(),
+    oaid: getImei(),
+    package_name: "h5",
+    sdk_version: import.meta.env.VITE_SDK_VERSION,
+    app_version: config.app_version,
+    system_lang: getSystemLang(),
+    system_version: getSystemVersion(),
+    brand: getBrand(),
+    model: getModel(),
+    time: Math.floor(Date.now() / 1000) // 时间戳(秒)
+  }
+}
+

+ 25 - 0
src/utils/index.ts

@@ -0,0 +1,25 @@
+const userAgent = navigator.userAgent.toLowerCase()
+/**
+ * 获取链接参数
+ * @param key 
+ * @returns 返回对应的参数
+ */
+export const getURLparams = (key: string) => {
+    const url = window.location.href
+    const params = url.split('?')[1]
+    if (!params) {
+        return undefined
+    }
+    return params.split('&').find(item => item.startsWith(key + '='))?.split('=')[1]
+}
+
+/**
+ * 存储session
+ * @param key 
+ * @param value 
+ */
+export const saveSession = (key: string, value: string) => {
+    sessionStorage.setItem(key,value);
+}
+
+export const isIOS = /iphone|ipad|ipod|macintosh/i.test(userAgent)

+ 34 - 0
src/utils/rem.ts

@@ -0,0 +1,34 @@
+/**
+ * rem 适配工具
+ * 基于设计稿宽度 750px,设置根元素 font-size
+ */
+
+function setRem() {
+  // 设计稿宽度(通常为 750)
+  const designWidth = 750
+  // 基准值,1rem = 75px (750/10)
+  const baseSize = 75
+
+  // 获取当前屏幕宽度
+  let width = document.documentElement.clientWidth || document.body.clientWidth
+  if (width > 750) {
+    width = 375
+  }
+  // 计算根元素 font-size
+  const scale = width / designWidth
+  const fontSize = baseSize * scale
+
+  // 设置根元素 font-size
+  document.documentElement.style.fontSize = fontSize + 'px'
+}
+
+// 初始化
+setRem()
+
+// 监听窗口大小变化
+window.addEventListener('resize', setRem)
+window.addEventListener('orientationchange', setRem)
+
+// 页面加载完成后再次设置,确保准确
+window.addEventListener('load', setRem)
+

+ 467 - 0
src/utils/request.ts

@@ -0,0 +1,467 @@
+import axios from 'axios'
+import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
+import CryptoJS from 'crypto-js'
+import { logout } from '../store/user-atom'
+
+// 定义 WordArray 类型别名
+type WordArray = CryptoJS.lib.WordArray
+import { getCommonParams, generateSign, getCachedConfig } from './common-params'
+
+
+const env = import.meta.env
+// 响应数据接口
+export interface ResponseData<T = any> {
+  code: number
+  message: string
+  data: T
+  [key: string]: any
+}
+
+// 扩展 AxiosRequestConfig
+export interface RequestConfig extends AxiosRequestConfig {
+  // 是否显示 loading
+  showLoading?: boolean
+  // 是否显示错误提示
+  showError?: boolean
+  // 是否跳过 token 验证
+  skipAuth?: boolean
+  // 是否跳过公共参数
+  skipCommonParams?: boolean
+}
+
+// 创建 axios 实例
+const service: AxiosInstance = axios.create({
+  baseURL: env.VITE_APP_OPEN_PROXY === 'true'
+    ? env.VITE_APP_PROXY_PREFIX
+    : env.VITE_APP_BASE_URL,
+  timeout: 30000, // 30秒超时
+  headers: {
+    'Content-Type': 'application/json;charset=UTF-8'
+  }
+})
+
+// 请求拦截器
+service.interceptors.request.use(
+  (config: any) => {
+    // 添加 token - 每次请求时从 localStorage 获取最新的 token
+    if (!config.skipAuth) {
+      const token = localStorage.getItem('token')
+      if (token) {
+        config.headers = config.headers || {}
+        config.headers.Authorization = `Bearer ${token}`
+      }
+    }
+
+    // 添加公共参数和 sign
+    if (!config.skipCommonParams) {
+      const commonParams = getCommonParams()
+      const cachedConfig = getCachedConfig()
+
+      // 合并公共参数到请求参数中
+      let allParams: Record<string, any> = {}
+
+      if (config.method?.toLowerCase() === 'get') {
+        // GET 请求:合并到 params
+        config.params = {
+          ...commonParams,
+          ...(config.params || {})
+        }
+        allParams = { ...config.params }
+      } else {
+        // POST/PUT/PATCH 等请求
+        if (config.data instanceof FormData) {
+          // FormData:公共参数添加到 URL 参数中
+          config.params = {
+            ...commonParams,
+            ...(config.params || {})
+          }
+          allParams = { ...config.params }
+        } else if (config.data && typeof config.data === 'object') {
+          // 对象类型:合并到 data
+          config.data = {
+            ...commonParams,
+            ...config.data
+          }
+          allParams = { ...config.data }
+        } else if (!config.data) {
+          // 没有 data:公共参数添加到 URL 参数中
+          config.params = {
+            ...commonParams,
+            ...(config.params || {})
+          }
+          allParams = { ...config.params }
+        } else {
+          // 其他类型(字符串、数字等):包装到 data 对象中
+          config.data = {
+            ...commonParams,
+            data: config.data
+          }
+          allParams = { ...config.data }
+        }
+      }
+
+      // 生成 sign
+
+      const sign = generateSign(allParams, import.meta.env.VITE_APP_SIGN_KEY)
+
+      if (config.method?.toLowerCase() === 'get') {
+        config.params.sign = sign
+      } else {
+        if (config.data instanceof FormData) {
+          config.params.sign = sign
+        } else {
+          config.data = {
+            ...allParams,
+            sign
+          }
+        }
+      }
+
+      // 加密请求参数
+      // 打印加密前的数据
+      console.log('🔒 加密前的数据:', JSON.stringify(config.data, null, 2))
+      const encryptedData = Request.addCommonEncryptData(config.data, import.meta.env.VITE_APP_SIGN_KEY)
+      console.log('🔐 加密后的数据:', encryptedData)
+
+      config.data = { encrypt_data: encryptedData }
+
+    }
+
+    // 可以在这里添加 loading 显示逻辑
+    if (config.showLoading !== false) {
+      // 显示 loading,需要根据项目实际情况实现
+      // showLoading()
+    }
+
+    return config
+  },
+  (error: AxiosError) => {
+    console.error('请求错误:', error)
+    return Promise.reject(error)
+  }
+)
+
+// 响应拦截器
+service.interceptors.response.use(
+  (response: AxiosResponse<ResponseData>) => {
+    const res = response.data
+
+    // 隐藏 loading
+    // hideLoading()
+
+    // 根据业务状态码处理
+    if (res.code !== undefined && res.code !== 200) {
+      // 业务错误
+      const message = res.message || '请求失败'
+
+      // 根据错误码处理特殊逻辑
+      if (res.code === 401) {
+        // token 过期,跳转登录
+        logout()
+        // 可以在这里添加路由跳转到登录页
+        // router.push('/login')
+      }
+
+      // 显示错误提示
+      // ElMessage.error(message)
+      console.error('请求失败:', message)
+
+      return Promise.reject(new Error(message))
+    }
+
+    // 返回数据
+    if (res.data !== undefined) {
+      // 打印解密前的数据(加密后的数据)
+      console.log('🔐 解密前的数据(加密后的):', res.data)
+      const decryptedData = Request.decryptCommonEncryptData(res.data, import.meta.env.VITE_APP_SIGN_KEY)
+      // 打印解密后的数据
+      console.log('🔓 解密后的数据:', JSON.stringify(decryptedData, null, 2))
+      // 返回解密后的数据(保持原有逻辑)
+      return decryptedData as any
+    }
+    return res as any
+  },
+  (error: AxiosError) => {
+    // 隐藏 loading
+    // hideLoading()
+
+    let message = '请求失败'
+
+    if (error.response) {
+      // 服务器返回了错误状态码
+      const status = error.response.status
+      switch (status) {
+        case 400:
+          message = '请求参数错误'
+          break
+        case 401:
+          message = '未授权,请重新登录'
+          logout()
+          window.location.reload();
+          // router.push('/login')
+          break
+        case 403:
+          message = '拒绝访问'
+          break
+        case 404:
+          message = '请求地址不存在'
+          break
+        case 500:
+          message = '服务器内部错误'
+          break
+        case 502:
+          message = '网关错误'
+          break
+        case 503:
+          message = '服务不可用'
+          break
+        case 504:
+          message = '网关超时'
+          break
+        default:
+          message = `请求失败 (${status})`
+      }
+    } else if (error.request) {
+      // 请求已发出,但没有收到响应
+      message = '网络连接失败,请检查网络'
+    } else {
+      // 其他错误
+      message = error.message || '请求失败'
+    }
+
+    // 显示错误提示
+    // ElMessage.error(message)
+    console.error('请求错误:', message)
+
+    return Promise.reject(error)
+  }
+)
+
+// 封装请求方法
+class Request {
+  /**
+   * GET 请求
+   */
+  get<T = any>(url: string, config?: RequestConfig): Promise<T> {
+    return service.get<ResponseData<T>>(url, config).then(res => res as unknown as T)
+  }
+
+  /**
+   * POST 请求
+   */
+  post<T = any>(url: string, data?: any, config?: RequestConfig): Promise<T> {
+    return service.post<ResponseData<T>>(url, data, config).then(res => res as unknown as T)
+  }
+
+  /**
+   * PUT 请求
+   */
+  put<T = any>(url: string, data?: any, config?: RequestConfig): Promise<T> {
+    return service.put<ResponseData<T>>(url, data, config).then(res => res as unknown as T)
+  }
+
+  /**
+   * DELETE 请求
+   */
+  delete<T = any>(url: string, config?: RequestConfig): Promise<T> {
+    return service.delete<ResponseData<T>>(url, config).then(res => res as unknown as T)
+  }
+
+  /**
+   * PATCH 请求
+   */
+  patch<T = any>(url: string, data?: any, config?: RequestConfig): Promise<T> {
+    return service.patch<ResponseData<T>>(url, data, config).then(res => res as unknown as T)
+  }
+
+  /**
+   * 上传文件
+   */
+  upload<T = any>(url: string, formData: FormData, config?: RequestConfig): Promise<T> {
+    return service.post<ResponseData<T>>(url, formData, {
+      ...config,
+      headers: {
+        'Content-Type': 'multipart/form-data',
+        ...config?.headers
+      }
+    }).then(res => res as unknown as T)
+  }
+
+  /**
+   * 下载文件
+   */
+  download(url: string, config?: RequestConfig): Promise<Blob> {
+    return service.get(url, {
+      ...config,
+      responseType: 'blob'
+    }).then(res => res as unknown as Blob)
+  }
+
+  /**
+   * 生成随机 IV(适用于浏览器环境)
+   * 在浏览器环境中,优先使用 crypto.getRandomValues,否则使用组合随机数生成方案
+   * @param {number} length - 字节长度
+   * @returns {WordArray} WordArray 对象
+   */
+  static generateRandomIV(length: number): WordArray {
+    // 优先使用浏览器原生的 crypto.getRandomValues(更安全)
+    if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
+      const randomBytes = new Uint8Array(length)
+      crypto.getRandomValues(randomBytes)
+      // 将 Uint8Array 转换为 WordArray
+      const words: number[] = []
+      for (let i = 0; i < length; i += 4) {
+        let word = 0
+        for (let j = 0; j < 4 && i + j < length; j++) {
+          word |= randomBytes[i + j] << (j * 8)
+        }
+        words.push(word >>> 0)
+      }
+      return CryptoJS.lib.WordArray.create(words, length)
+    }
+
+    // 降级方案:使用组合随机数生成(兼容性更好)
+    const words: number[] = []
+    const now = Date.now()
+    // 使用性能计数器增加随机性(如果可用)
+    const perfNow = (typeof performance !== 'undefined' && performance.now) ? performance.now() : 0
+
+    for (let i = 0; i < length; i += 4) {
+      // 组合多个随机源以增加随机性
+      const random1 = Math.random() * 0xffffffff
+      const random2 = Math.random() * 0xffffffff
+      const timePart = (now + i) & 0xffffffff
+      const perfPart = (perfNow * 1000 + i) & 0xffffffff
+
+      // 使用异或运算组合多个随机源
+      let combined = (random1 ^ random2 ^ timePart ^ perfPart) >>> 0
+
+      // 如果长度不是 4 的倍数,需要处理最后一个不完整的 word
+      if (i + 4 > length) {
+        // 只使用需要的字节数
+        const remainingBytes = length - i
+        combined = combined >>> (32 - remainingBytes * 8)
+        combined = combined << (32 - remainingBytes * 8)
+      }
+
+      words.push(combined)
+    }
+
+    return CryptoJS.lib.WordArray.create(words, length)
+  }
+
+  /**
+   * 通用类数据加密 (AES-256-CBC)
+   * 对应 PHP: openssl_encrypt($data, 'AES-256-CBC', KEY, OPENSSL_RAW_DATA, $iv)
+   * @param {Object} params - 需要加密的参数对象
+   * @param {string} key - 加密密钥,如果不传则从配置中获取
+   * @returns {string} Base64 编码的加密数据(包含 IV + 密文)
+   */
+  static addCommonEncryptData(params: Record<string, any>, key: string | null = null): string {
+    // 将参数对象转换为 JSON 字符串
+    const data = JSON.stringify(params)
+
+    // 生成 16 字节的随机 IV (AES-256-CBC 需要 16 字节 IV)
+    const iv = Request.generateRandomIV(16)
+
+    // 将密钥转换为 WordArray
+    // AES-256 需要 32 字节 (256 位) 的密钥
+    // 如果 key 长度不够,使用 SHA256 哈希来生成 32 字节的密钥
+    let keyWordArray: WordArray
+    if (!key) {
+      throw new Error('加密密钥不能为空')
+    }
+
+    if (key.length === 32) {
+      // 如果 key 正好是 32 字节,直接使用 UTF8 解析
+      keyWordArray = CryptoJS.enc.Utf8.parse(key)
+    } else {
+      // 否则使用 SHA256 哈希生成 32 字节的密钥
+      keyWordArray = CryptoJS.SHA256(key)
+    }
+
+    // 使用 AES-256-CBC 加密,返回原始二进制数据
+    const encrypted = CryptoJS.AES.encrypt(data, keyWordArray, {
+      iv: iv,
+      mode: CryptoJS.mode.CBC,
+      padding: CryptoJS.pad.Pkcs7
+    })
+
+    // 将 IV 和密文拼接(都是 WordArray,直接拼接)
+    // PHP 中:$iv . $encrypted (直接拼接二进制数据)
+    const combined = iv.concat(encrypted.ciphertext)
+
+    // 将拼接后的数据转换为 Base64 编码
+    // PHP 中:base64_encode($iv . $encrypted)
+    const base64Encoded = CryptoJS.enc.Base64.stringify(combined)
+
+    // 返回加密后的数据
+    return base64Encoded
+  }
+
+  /**
+   * 通用类数据解密 (AES-256-CBC)
+   * 对应 PHP: openssl_decrypt(base64_decode($data), 'AES-256-CBC', KEY, OPENSSL_RAW_DATA, $iv)
+   * @param {string} encryptedData - Base64 编码的加密数据(包含 IV + 密文)
+   * @param {string} key - 解密密钥,如果不传则从配置中获取
+   * @returns {Object} 解密后的参数对象
+   */
+  static decryptCommonEncryptData(encryptedData: string, key: string | null = null): Record<string, any> {
+    try {
+      // 如果数据为空或无效,直接返回
+      if (!encryptedData || typeof encryptedData !== 'string') {
+        return encryptedData as any
+      }
+
+      // Base64 解码
+      const combined = CryptoJS.enc.Base64.parse(encryptedData)
+
+      // 提取前 16 字节作为 IV(AES-256-CBC 需要 16 字节 IV)
+      const iv = CryptoJS.lib.WordArray.create(combined.words.slice(0, 4), 16)
+
+      // 提取剩余部分作为密文
+      const ciphertext = CryptoJS.lib.WordArray.create(combined.words.slice(4), combined.sigBytes - 16)
+
+      // 将密钥转换为 WordArray(与加密方法保持一致)
+      if (!key) {
+        throw new Error('解密密钥不能为空')
+      }
+
+      let keyWordArray: WordArray
+      if (key.length === 32) {
+        // 如果 key 正好是 32 字节,直接使用 UTF8 解析
+        keyWordArray = CryptoJS.enc.Utf8.parse(key)
+      } else {
+        // 否则使用 SHA256 哈希生成 32 字节的密钥
+        keyWordArray = CryptoJS.SHA256(key)
+      }
+
+      // 创建 CipherParams 对象用于解密
+      const cipherParams = CryptoJS.lib.CipherParams.create({
+        ciphertext: ciphertext
+      })
+
+      // 使用 AES-256-CBC 解密
+      const decrypted = CryptoJS.AES.decrypt(cipherParams, keyWordArray, {
+        iv: iv,
+        mode: CryptoJS.mode.CBC,
+        padding: CryptoJS.pad.Pkcs7
+      })
+
+      // 将解密后的数据转换为 UTF8 字符串
+      const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8)
+
+      // 解析 JSON 字符串并返回对象
+      return JSON.parse(decryptedStr)
+    } catch (error) {
+      console.error('解密失败:', error)
+      // 如果解密失败,返回原始数据
+      return encryptedData as any
+    }
+  }
+}
+
+// 导出实例和方法
+export const request = new Request()
+export default service
+

+ 27 - 0
tsconfig.json

@@ -0,0 +1,27 @@
+{
+  "compilerOptions": {
+    "target": "ES2022",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "lib": ["ES2022", "DOM", "DOM.Iterable"],
+    "types": ["vite/client"],
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "verbatimModuleSyntax": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+    "jsx": "react-jsx",
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "erasableSyntaxOnly": true,
+    "noFallthroughCasesInSwitch": true,
+    "noUncheckedSideEffectImports": true
+  },
+  "include": ["src"]
+}

+ 26 - 0
vite.config.ts

@@ -0,0 +1,26 @@
+import { defineConfig, loadEnv } from 'vite'
+import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
+
+// https://vite.dev/config/
+export default defineConfig(({ mode }) => {
+  const env = loadEnv(mode, process.cwd())
+  const proxyPrefix = env.VITE_APP_PROXY_PREFIX
+  const target = env.VITE_APP_BASE_URL
+
+  return {
+    plugins: [
+      react(),
+      tailwindcss(),
+    ],
+    server: {
+      proxy: {
+        [proxyPrefix]: {
+          target: target,
+          changeOrigin: true,
+          rewrite: (path) => path.replace(new RegExp(`^${proxyPrefix}`), '')
+        }
+      }
+    }
+  }
+})