前端简单界面

Change-Id: I7df9774daf4df8d92b13e659effe426ab0b6180b
diff --git a/pt--frontend/.gitignore b/pt--frontend/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/pt--frontend/.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?
diff --git a/pt--frontend/README.md b/pt--frontend/README.md
new file mode 100644
index 0000000..7059a96
--- /dev/null
+++ b/pt--frontend/README.md
@@ -0,0 +1,12 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
diff --git a/pt--frontend/eslint.config.js b/pt--frontend/eslint.config.js
new file mode 100644
index 0000000..ec2b712
--- /dev/null
+++ b/pt--frontend/eslint.config.js
@@ -0,0 +1,33 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+
+export default [
+  { ignores: ['dist'] },
+  {
+    files: ['**/*.{js,jsx}'],
+    languageOptions: {
+      ecmaVersion: 2020,
+      globals: globals.browser,
+      parserOptions: {
+        ecmaVersion: 'latest',
+        ecmaFeatures: { jsx: true },
+        sourceType: 'module',
+      },
+    },
+    plugins: {
+      'react-hooks': reactHooks,
+      'react-refresh': reactRefresh,
+    },
+    rules: {
+      ...js.configs.recommended.rules,
+      ...reactHooks.configs.recommended.rules,
+      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
+      'react-refresh/only-export-components': [
+        'warn',
+        { allowConstantExport: true },
+      ],
+    },
+  },
+]
diff --git a/pt--frontend/index.html b/pt--frontend/index.html
new file mode 100644
index 0000000..0c589ec
--- /dev/null
+++ b/pt--frontend/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>Vite + React</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.jsx"></script>
+  </body>
+</html>
diff --git a/pt--frontend/package-lock.json b/pt--frontend/package-lock.json
new file mode 100644
index 0000000..e129379
--- /dev/null
+++ b/pt--frontend/package-lock.json
@@ -0,0 +1,4205 @@
+{
+  "name": "pt-fronted",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "pt-fronted",
+      "version": "0.0.0",
+      "dependencies": {
+        "@mantine/core": "^4.0.0",
+        "@mantine/hooks": "^4.0.0",
+        "axios": "^1.9.0",
+        "react": "^18.3.1",
+        "react-dom": "^18.3.1",
+        "react-router-dom": "^6.4.0"
+      },
+      "devDependencies": {
+        "@eslint/js": "^9.25.0",
+        "@types/react": "^19.1.2",
+        "@types/react-dom": "^19.1.2",
+        "@vitejs/plugin-react": "^4.4.1",
+        "eslint": "^9.25.0",
+        "eslint-plugin-react-hooks": "^5.2.0",
+        "eslint-plugin-react-refresh": "^0.4.19",
+        "globals": "^16.0.0",
+        "vite": "^6.3.5"
+      }
+    },
+    "node_modules/@ampproject/remapping": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
+      "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/code-frame": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz",
+      "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.27.1",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/compat-data": {
+      "version": "7.27.2",
+      "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.27.2.tgz",
+      "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.27.1.tgz",
+      "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
+      "dev": true,
+      "dependencies": {
+        "@ampproject/remapping": "^2.2.0",
+        "@babel/code-frame": "^7.27.1",
+        "@babel/generator": "^7.27.1",
+        "@babel/helper-compilation-targets": "^7.27.1",
+        "@babel/helper-module-transforms": "^7.27.1",
+        "@babel/helpers": "^7.27.1",
+        "@babel/parser": "^7.27.1",
+        "@babel/template": "^7.27.1",
+        "@babel/traverse": "^7.27.1",
+        "@babel/types": "^7.27.1",
+        "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.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.27.1.tgz",
+      "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==",
+      "dependencies": {
+        "@babel/parser": "^7.27.1",
+        "@babel/types": "^7.27.1",
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25",
+        "jsesc": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-compilation-targets": {
+      "version": "7.27.2",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+      "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+      "dev": true,
+      "dependencies": {
+        "@babel/compat-data": "^7.27.2",
+        "@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-module-imports": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+      "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+      "dependencies": {
+        "@babel/traverse": "^7.27.1",
+        "@babel/types": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-transforms": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz",
+      "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.27.1",
+        "@babel/traverse": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-plugin-utils": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+      "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+      "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-option": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+      "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helpers": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.1.tgz",
+      "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
+      "dev": true,
+      "dependencies": {
+        "@babel/template": "^7.27.1",
+        "@babel/types": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.27.2",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.2.tgz",
+      "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+      "dependencies": {
+        "@babel/types": "^7.27.1"
+      },
+      "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.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+      "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+      "dev": true,
+      "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.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+      "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.1.tgz",
+      "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/template": {
+      "version": "7.27.2",
+      "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz",
+      "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+      "dependencies": {
+        "@babel/code-frame": "^7.27.1",
+        "@babel/parser": "^7.27.2",
+        "@babel/types": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.27.1.tgz",
+      "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==",
+      "dependencies": {
+        "@babel/code-frame": "^7.27.1",
+        "@babel/generator": "^7.27.1",
+        "@babel/parser": "^7.27.1",
+        "@babel/template": "^7.27.1",
+        "@babel/types": "^7.27.1",
+        "debug": "^4.3.1",
+        "globals": "^11.1.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse/node_modules/globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.1.tgz",
+      "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@emotion/babel-plugin": {
+      "version": "11.13.5",
+      "resolved": "https://registry.npmmirror.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
+      "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.16.7",
+        "@babel/runtime": "^7.18.3",
+        "@emotion/hash": "^0.9.2",
+        "@emotion/memoize": "^0.9.0",
+        "@emotion/serialize": "^1.3.3",
+        "babel-plugin-macros": "^3.1.0",
+        "convert-source-map": "^1.5.0",
+        "escape-string-regexp": "^4.0.0",
+        "find-root": "^1.1.0",
+        "source-map": "^0.5.7",
+        "stylis": "4.2.0"
+      }
+    },
+    "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz",
+      "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
+    },
+    "node_modules/@emotion/cache": {
+      "version": "11.14.0",
+      "resolved": "https://registry.npmmirror.com/@emotion/cache/-/cache-11.14.0.tgz",
+      "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+      "dependencies": {
+        "@emotion/memoize": "^0.9.0",
+        "@emotion/sheet": "^1.4.0",
+        "@emotion/utils": "^1.4.2",
+        "@emotion/weak-memoize": "^0.4.0",
+        "stylis": "4.2.0"
+      }
+    },
+    "node_modules/@emotion/hash": {
+      "version": "0.9.2",
+      "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz",
+      "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
+    },
+    "node_modules/@emotion/memoize": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.9.0.tgz",
+      "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+    },
+    "node_modules/@emotion/react": {
+      "version": "11.14.0",
+      "resolved": "https://registry.npmmirror.com/@emotion/react/-/react-11.14.0.tgz",
+      "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
+      "dependencies": {
+        "@babel/runtime": "^7.18.3",
+        "@emotion/babel-plugin": "^11.13.5",
+        "@emotion/cache": "^11.14.0",
+        "@emotion/serialize": "^1.3.3",
+        "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+        "@emotion/utils": "^1.4.2",
+        "@emotion/weak-memoize": "^0.4.0",
+        "hoist-non-react-statics": "^3.3.1"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@emotion/serialize": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmmirror.com/@emotion/serialize/-/serialize-1.3.3.tgz",
+      "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
+      "dependencies": {
+        "@emotion/hash": "^0.9.2",
+        "@emotion/memoize": "^0.9.0",
+        "@emotion/unitless": "^0.10.0",
+        "@emotion/utils": "^1.4.2",
+        "csstype": "^3.0.2"
+      }
+    },
+    "node_modules/@emotion/sheet": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/@emotion/sheet/-/sheet-1.4.0.tgz",
+      "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="
+    },
+    "node_modules/@emotion/unitless": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.10.0.tgz",
+      "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="
+    },
+    "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
+      "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      }
+    },
+    "node_modules/@emotion/utils": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmmirror.com/@emotion/utils/-/utils-1.4.2.tgz",
+      "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="
+    },
+    "node_modules/@emotion/weak-memoize": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+      "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
+      "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
+      "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
+      "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
+      "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
+      "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
+      "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
+      "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
+      "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
+      "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
+      "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
+      "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
+      "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
+      "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
+      "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
+      "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
+      "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
+      "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
+      "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
+      "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
+      "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
+      "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
+      "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
+      "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
+      "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.25.4",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
+      "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+      "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^3.4.3"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint-community/regexpp": {
+      "version": "4.12.1",
+      "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+      "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+      "dev": true,
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@eslint/config-array": {
+      "version": "0.20.0",
+      "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.20.0.tgz",
+      "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/object-schema": "^2.1.6",
+        "debug": "^4.3.1",
+        "minimatch": "^3.1.2"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/config-helpers": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.2.2.tgz",
+      "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/core": {
+      "version": "0.13.0",
+      "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.13.0.tgz",
+      "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
+      "dev": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.15"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/eslintrc": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+      "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^10.0.1",
+        "globals": "^14.0.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/globals": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz",
+      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@eslint/js": {
+      "version": "9.26.0",
+      "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.26.0.tgz",
+      "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/object-schema": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+      "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/plugin-kit": {
+      "version": "0.2.8",
+      "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
+      "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/core": "^0.13.0",
+        "levn": "^0.4.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@humanfs/core": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz",
+      "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanfs/node": {
+      "version": "0.16.6",
+      "resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.6.tgz",
+      "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+      "dev": true,
+      "dependencies": {
+        "@humanfs/core": "^0.19.1",
+        "@humanwhocodes/retry": "^0.3.0"
+      },
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+      "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.22"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/retry": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+      "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.8",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+      "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+      "dependencies": {
+        "@jridgewell/set-array": "^1.2.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/set-array": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.25",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@mantine/core": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/@mantine/core/-/core-4.0.0.tgz",
+      "integrity": "sha512-aWwfv8d1Eqx0nqjoZZwbK17gWfQeukCRXfysusC15WHzq8R7Q5QkZzhxU6wBlzObIvLZvWy64uApcY8QxiXr5A==",
+      "dependencies": {
+        "@mantine/styles": "4.0.0",
+        "@popperjs/core": "^2.9.3",
+        "@radix-ui/react-scroll-area": "^0.1.1",
+        "clsx": "^1.1.1",
+        "react-popper": "^2.2.5",
+        "react-textarea-autosize": "^8.3.2"
+      },
+      "peerDependencies": {
+        "@mantine/hooks": "4.0.0",
+        "react": ">=16.8.0",
+        "react-dom": ">=16.8.0"
+      }
+    },
+    "node_modules/@mantine/core/node_modules/@radix-ui/react-scroll-area": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-0.1.4.tgz",
+      "integrity": "sha512-QHxRsjy+hsHwQYJ9cCNgSJ5+6ioZu1KhwD1UOXoHNciuFGMX08v+uJPKXIz+ySv03Rx6cOz6f/Fk5aPHRMFi/A==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/number": "0.1.0",
+        "@radix-ui/primitive": "0.1.0",
+        "@radix-ui/react-compose-refs": "0.1.0",
+        "@radix-ui/react-context": "0.1.1",
+        "@radix-ui/react-presence": "0.1.2",
+        "@radix-ui/react-primitive": "0.1.4",
+        "@radix-ui/react-use-callback-ref": "0.1.0",
+        "@radix-ui/react-use-direction": "0.1.0",
+        "@radix-ui/react-use-layout-effect": "0.1.0"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@mantine/core/node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-compose-refs": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz",
+      "integrity": "sha512-eyclbh+b77k+69Dk72q3694OHrn9B3QsoIRx7ywX341U9RK1ThgQjMFZoPtmZNQTksXHLNEiefR8hGVeFyInGg==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@mantine/core/node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-0.1.1.tgz",
+      "integrity": "sha512-PkyVX1JsLBioeu0jB9WvRpDBBLtLZohVDT3BB5CTSJqActma8S8030P57mWZb4baZifMvN7KKWPAA40UmWKkQg==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@mantine/core/node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz",
+      "integrity": "sha512-6gSl2IidySupIMJFjYnDIkIWRyQdbu/AHK7rbICPani+LW4b0XdxBXc46og/iZvuwW8pjCS8I2SadIerv84xYA==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/react-slot": "0.1.2"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@mantine/core/node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-0.1.2.tgz",
+      "integrity": "sha512-ADkqfL+agEzEguU3yS26jfB50hRrwf7U4VTwAOZEmi/g+ITcBWe12yM46ueS/UCIMI9Py+gFUaAdxgxafFvY2Q==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/react-compose-refs": "0.1.0"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@mantine/core/node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-use-callback-ref": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.1.0.tgz",
+      "integrity": "sha512-Va041McOFFl+aV+sejvl0BS2aeHx86ND9X/rVFmEFQKTXCp6xgUK0NGUAGcgBlIjnJSbMYPGEk1xKSSlVcN2Aw==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@mantine/core/node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-use-direction": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-direction/-/react-use-direction-0.1.0.tgz",
+      "integrity": "sha512-NajpY/An9TCPSfOVkgWIdXJV+VuWl67PxB6kOKYmtNAFHvObzIoh8o0n9sAuwSAyFCZVq211FEf9gvVDRhOyiA==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@mantine/core/node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-use-layout-effect": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz",
+      "integrity": "sha512-+wdeS51Y+E1q1Wmd+1xSSbesZkpVj4jsg0BojCbopWvgq5iBvixw5vgemscdh58ep98BwUbsFYnrywFhV9yrVg==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@mantine/hooks": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/@mantine/hooks/-/hooks-4.0.0.tgz",
+      "integrity": "sha512-wYOv28mJdzLNK4Q112o18XglkNN0WkMioSh5lEE+V2nBrxhTu/vlfFkiC21HZXKBJ1lyy22nIvSUA2jz82W3lg==",
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      }
+    },
+    "node_modules/@mantine/styles": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/@mantine/styles/-/styles-4.0.0.tgz",
+      "integrity": "sha512-wsOCu9heyc2NMLYZfWMa+3K4QmlyTGJB52hiHHbu84KPlvNoAXAeh6dpB37eM9MySLPJnWEEXfiA3x26rmgesQ==",
+      "dependencies": {
+        "@emotion/cache": "^11.7.1",
+        "@emotion/react": "^11.7.1",
+        "@emotion/serialize": "^1.0.2",
+        "@emotion/utils": "^1.0.0",
+        "clsx": "^1.1.1",
+        "csstype": "^3.0.9"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0",
+        "react-dom": ">=16.8.0"
+      }
+    },
+    "node_modules/@modelcontextprotocol/sdk": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz",
+      "integrity": "sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==",
+      "dev": true,
+      "dependencies": {
+        "content-type": "^1.0.5",
+        "cors": "^2.8.5",
+        "cross-spawn": "^7.0.3",
+        "eventsource": "^3.0.2",
+        "express": "^5.0.1",
+        "express-rate-limit": "^7.5.0",
+        "pkce-challenge": "^5.0.0",
+        "raw-body": "^3.0.0",
+        "zod": "^3.23.8",
+        "zod-to-json-schema": "^3.24.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@popperjs/core": {
+      "version": "2.11.8",
+      "resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.8.tgz",
+      "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
+    "node_modules/@radix-ui/number": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/number/-/number-0.1.0.tgz",
+      "integrity": "sha512-rpf6QiOWLHAkM4FEMYu9i+5Jr8cKT893+R4mPpcdsy4LD7omr9JfdOqj/h/xPA5+EcVrpMMlU6rrRYpUB5UI8g==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10"
+      }
+    },
+    "node_modules/@radix-ui/primitive": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-0.1.0.tgz",
+      "integrity": "sha512-tqxZKybwN5Fa3VzZry4G6mXAAb9aAqKmPtnVbZpL0vsBwvOHTBwsjHVPXylocYLwEtBY9SCe665bYnNB515uoA==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10"
+      }
+    },
+    "node_modules/@radix-ui/react-presence": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-0.1.2.tgz",
+      "integrity": "sha512-3BRlFZraooIUfRlyN+b/Xs5hq1lanOOo/+3h6Pwu2GMFjkGKKa4Rd51fcqGqnVlbr3jYg+WLuGyAV4KlgqwrQw==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10",
+        "@radix-ui/react-compose-refs": "0.1.0",
+        "@radix-ui/react-use-layout-effect": "0.1.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8"
+      }
+    },
+    "node_modules/@radix-ui/react-presence/node_modules/@radix-ui/react-compose-refs": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz",
+      "integrity": "sha512-eyclbh+b77k+69Dk72q3694OHrn9B3QsoIRx7ywX341U9RK1ThgQjMFZoPtmZNQTksXHLNEiefR8hGVeFyInGg==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@radix-ui/react-presence/node_modules/@radix-ui/react-use-layout-effect": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz",
+      "integrity": "sha512-+wdeS51Y+E1q1Wmd+1xSSbesZkpVj4jsg0BojCbopWvgq5iBvixw5vgemscdh58ep98BwUbsFYnrywFhV9yrVg==",
+      "dependencies": {
+        "@babel/runtime": "^7.13.10"
+      },
+      "peerDependencies": {
+        "react": "^16.8 || ^17.0"
+      }
+    },
+    "node_modules/@remix-run/router": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.0.0.tgz",
+      "integrity": "sha512-SCR1cxRSMNKjaVYptCzBApPDqGwa3FGdjVHc+rOToocNPHQdIYLZBfv/3f+KvYuXDkUGVIW9IAzmPNZDRL1I4A==",
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz",
+      "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz",
+      "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz",
+      "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz",
+      "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz",
+      "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz",
+      "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz",
+      "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz",
+      "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz",
+      "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz",
+      "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz",
+      "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz",
+      "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz",
+      "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz",
+      "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz",
+      "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz",
+      "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz",
+      "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz",
+      "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz",
+      "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz",
+      "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/babel__core": {
+      "version": "7.20.5",
+      "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
+      "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+      "dev": true,
+      "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.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+      "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__template": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz",
+      "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+      "dev": true,
+      "dependencies": {
+        "@babel/parser": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__traverse": {
+      "version": "7.20.7",
+      "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+      "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.20.7"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz",
+      "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+      "dev": true
+    },
+    "node_modules/@types/json-schema": {
+      "version": "7.0.15",
+      "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz",
+      "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+      "dev": true
+    },
+    "node_modules/@types/parse-json": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz",
+      "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="
+    },
+    "node_modules/@types/react": {
+      "version": "19.1.3",
+      "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.1.3.tgz",
+      "integrity": "sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==",
+      "dev": true,
+      "dependencies": {
+        "csstype": "^3.0.2"
+      }
+    },
+    "node_modules/@types/react-dom": {
+      "version": "19.1.3",
+      "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.1.3.tgz",
+      "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==",
+      "dev": true,
+      "peerDependencies": {
+        "@types/react": "^19.0.0"
+      }
+    },
+    "node_modules/@vitejs/plugin-react": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz",
+      "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==",
+      "dev": true,
+      "dependencies": {
+        "@babel/core": "^7.26.10",
+        "@babel/plugin-transform-react-jsx-self": "^7.25.9",
+        "@babel/plugin-transform-react-jsx-source": "^7.25.9",
+        "@types/babel__core": "^7.20.5",
+        "react-refresh": "^0.17.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz",
+      "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+      "dev": true,
+      "dependencies": {
+        "mime-types": "^3.0.0",
+        "negotiator": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.14.1",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
+      "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "peerDependencies": {
+        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.9.0.tgz",
+      "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/babel-plugin-macros": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+      "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "cosmiconfig": "^7.0.0",
+        "resolve": "^1.19.0"
+      },
+      "engines": {
+        "node": ">=10",
+        "npm": ">=6"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "node_modules/body-parser": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.0.tgz",
+      "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+      "dev": true,
+      "dependencies": {
+        "bytes": "^3.1.2",
+        "content-type": "^1.0.5",
+        "debug": "^4.4.0",
+        "http-errors": "^2.0.0",
+        "iconv-lite": "^0.6.3",
+        "on-finished": "^2.4.1",
+        "qs": "^6.14.0",
+        "raw-body": "^3.0.0",
+        "type-is": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.24.5",
+      "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.5.tgz",
+      "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
+      "dev": 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"
+        }
+      ],
+      "dependencies": {
+        "caniuse-lite": "^1.0.30001716",
+        "electron-to-chromium": "^1.5.149",
+        "node-releases": "^2.0.19",
+        "update-browserslist-db": "^1.1.3"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "dev": true,
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001717",
+      "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz",
+      "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==",
+      "dev": 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"
+        }
+      ]
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/clsx": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz",
+      "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "node_modules/content-disposition": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.0.tgz",
+      "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+      "dev": true,
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true
+    },
+    "node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz",
+      "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.6.0"
+      }
+    },
+    "node_modules/cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "dev": true,
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/cosmiconfig": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+      "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+      "dependencies": {
+        "@types/parse-json": "^4.0.0",
+        "import-fresh": "^3.2.1",
+        "parse-json": "^5.0.0",
+        "path-type": "^4.0.0",
+        "yaml": "^1.10.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/cosmiconfig/node_modules/yaml": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz",
+      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+    },
+    "node_modules/debug": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz",
+      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "dev": true
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.151",
+      "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz",
+      "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==",
+      "dev": true
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dependencies": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "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.25.4",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.4.tgz",
+      "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.25.4",
+        "@esbuild/android-arm": "0.25.4",
+        "@esbuild/android-arm64": "0.25.4",
+        "@esbuild/android-x64": "0.25.4",
+        "@esbuild/darwin-arm64": "0.25.4",
+        "@esbuild/darwin-x64": "0.25.4",
+        "@esbuild/freebsd-arm64": "0.25.4",
+        "@esbuild/freebsd-x64": "0.25.4",
+        "@esbuild/linux-arm": "0.25.4",
+        "@esbuild/linux-arm64": "0.25.4",
+        "@esbuild/linux-ia32": "0.25.4",
+        "@esbuild/linux-loong64": "0.25.4",
+        "@esbuild/linux-mips64el": "0.25.4",
+        "@esbuild/linux-ppc64": "0.25.4",
+        "@esbuild/linux-riscv64": "0.25.4",
+        "@esbuild/linux-s390x": "0.25.4",
+        "@esbuild/linux-x64": "0.25.4",
+        "@esbuild/netbsd-arm64": "0.25.4",
+        "@esbuild/netbsd-x64": "0.25.4",
+        "@esbuild/openbsd-arm64": "0.25.4",
+        "@esbuild/openbsd-x64": "0.25.4",
+        "@esbuild/sunos-x64": "0.25.4",
+        "@esbuild/win32-arm64": "0.25.4",
+        "@esbuild/win32-ia32": "0.25.4",
+        "@esbuild/win32-x64": "0.25.4"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "dev": true
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint": {
+      "version": "9.26.0",
+      "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.26.0.tgz",
+      "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "@eslint-community/regexpp": "^4.12.1",
+        "@eslint/config-array": "^0.20.0",
+        "@eslint/config-helpers": "^0.2.1",
+        "@eslint/core": "^0.13.0",
+        "@eslint/eslintrc": "^3.3.1",
+        "@eslint/js": "9.26.0",
+        "@eslint/plugin-kit": "^0.2.8",
+        "@humanfs/node": "^0.16.6",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@humanwhocodes/retry": "^0.4.2",
+        "@modelcontextprotocol/sdk": "^1.8.0",
+        "@types/estree": "^1.0.6",
+        "@types/json-schema": "^7.0.15",
+        "ajv": "^6.12.4",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.6",
+        "debug": "^4.3.2",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^8.3.0",
+        "eslint-visitor-keys": "^4.2.0",
+        "espree": "^10.3.0",
+        "esquery": "^1.5.0",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^8.0.0",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
+        "ignore": "^5.2.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.3",
+        "zod": "^3.24.2"
+      },
+      "bin": {
+        "eslint": "bin/eslint.js"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://eslint.org/donate"
+      },
+      "peerDependencies": {
+        "jiti": "*"
+      },
+      "peerDependenciesMeta": {
+        "jiti": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eslint-plugin-react-hooks": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+      "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-react-refresh": {
+      "version": "0.4.20",
+      "resolved": "https://registry.npmmirror.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
+      "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
+      "dev": true,
+      "peerDependencies": {
+        "eslint": ">=8.40"
+      }
+    },
+    "node_modules/eslint-scope": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.3.0.tgz",
+      "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-visitor-keys": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+      "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/espree": {
+      "version": "10.3.0",
+      "resolved": "https://registry.npmmirror.com/espree/-/espree-10.3.0.tgz",
+      "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^4.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/esquery": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz",
+      "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/eventsource": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.7.tgz",
+      "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+      "dev": true,
+      "dependencies": {
+        "eventsource-parser": "^3.0.1"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/eventsource-parser": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
+      "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/express": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmmirror.com/express/-/express-5.1.0.tgz",
+      "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+      "dev": true,
+      "dependencies": {
+        "accepts": "^2.0.0",
+        "body-parser": "^2.2.0",
+        "content-disposition": "^1.0.0",
+        "content-type": "^1.0.5",
+        "cookie": "^0.7.1",
+        "cookie-signature": "^1.2.1",
+        "debug": "^4.4.0",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "etag": "^1.8.1",
+        "finalhandler": "^2.1.0",
+        "fresh": "^2.0.0",
+        "http-errors": "^2.0.0",
+        "merge-descriptors": "^2.0.0",
+        "mime-types": "^3.0.0",
+        "on-finished": "^2.4.1",
+        "once": "^1.4.0",
+        "parseurl": "^1.3.3",
+        "proxy-addr": "^2.0.7",
+        "qs": "^6.14.0",
+        "range-parser": "^1.2.1",
+        "router": "^2.2.0",
+        "send": "^1.1.0",
+        "serve-static": "^2.2.0",
+        "statuses": "^2.0.1",
+        "type-is": "^2.0.1",
+        "vary": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/express-rate-limit": {
+      "version": "7.5.0",
+      "resolved": "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
+      "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/express-rate-limit"
+      },
+      "peerDependencies": {
+        "express": "^4.11 || 5 || ^5.0.0-beta.1"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "node_modules/fdir": {
+      "version": "6.4.4",
+      "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.4.tgz",
+      "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+      "dev": true,
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/file-entry-cache": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+      "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.0.tgz",
+      "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^4.4.0",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "on-finished": "^2.4.1",
+        "parseurl": "^1.3.3",
+        "statuses": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/find-root": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/find-root/-/find-root-1.1.0.tgz",
+      "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
+    },
+    "node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/flat-cache": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz",
+      "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.4"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/flatted": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz",
+      "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+      "dev": true
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz",
+      "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/form-data/node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/form-data/node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz",
+      "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "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.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "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.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/globals": {
+      "version": "16.1.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-16.1.0.tgz",
+      "integrity": "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "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.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "dependencies": {
+        "react-is": "^16.7.0"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "dev": true,
+      "dependencies": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
+      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+      "dev": true,
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/ignore": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
+      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/import-fresh": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz",
+      "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+      "dependencies": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.19"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
+    },
+    "node_modules/is-core-module": {
+      "version": "2.16.1",
+      "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
+      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+      "dependencies": {
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-promise": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz",
+      "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+      "dev": true
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+    },
+    "node_modules/js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "dependencies": {
+        "argparse": "^2.0.1"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
+    "node_modules/jsesc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/json-buffer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz",
+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "dev": true
+    },
+    "node_modules/json-parse-even-better-errors": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "dev": true,
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/keyv": {
+      "version": "4.5.4",
+      "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "dev": true,
+      "dependencies": {
+        "json-buffer": "3.0.1"
+      }
+    },
+    "node_modules/levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/lines-and-columns": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+      "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
+    },
+    "node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "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.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz",
+      "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+      "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.54.0",
+      "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz",
+      "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.1.tgz",
+      "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+      "dev": true,
+      "dependencies": {
+        "mime-db": "^1.54.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "node_modules/negotiator": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz",
+      "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.19",
+      "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz",
+      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+      "dev": true
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "dev": true,
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/optionator": {
+      "version": "0.9.4",
+      "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
+      "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+      "dev": true,
+      "dependencies": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.5"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dependencies": {
+        "callsites": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/parse-json": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz",
+      "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+      "dependencies": {
+        "@babel/code-frame": "^7.0.0",
+        "error-ex": "^1.3.1",
+        "json-parse-even-better-errors": "^2.3.0",
+        "lines-and-columns": "^1.1.6"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
+    },
+    "node_modules/path-to-regexp": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
+      "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/path-type": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz",
+      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
+      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/pkce-challenge": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
+      "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=16.20.0"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.3",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz",
+      "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+      "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"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.8",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "dev": true,
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.14.0",
+      "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz",
+      "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+      "dev": true,
+      "dependencies": {
+        "side-channel": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.0.tgz",
+      "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+      "dev": true,
+      "dependencies": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.6.3",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/react": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz",
+      "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz",
+      "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.2"
+      },
+      "peerDependencies": {
+        "react": "^18.3.1"
+      }
+    },
+    "node_modules/react-fast-compare": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+      "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
+    },
+    "node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
+    "node_modules/react-popper": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/react-popper/-/react-popper-2.3.0.tgz",
+      "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
+      "dependencies": {
+        "react-fast-compare": "^3.0.1",
+        "warning": "^4.0.2"
+      },
+      "peerDependencies": {
+        "@popperjs/core": "^2.0.0",
+        "react": "^16.8.0 || ^17 || ^18",
+        "react-dom": "^16.8.0 || ^17 || ^18"
+      }
+    },
+    "node_modules/react-refresh": {
+      "version": "0.17.0",
+      "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz",
+      "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-router": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.4.0.tgz",
+      "integrity": "sha512-B+5bEXFlgR1XUdHYR6P94g299SjrfCBMmEDJNcFbpAyRH1j1748yt9NdDhW3++nw1lk3zQJ6aOO66zUx3KlTZg==",
+      "dependencies": {
+        "@remix-run/router": "1.0.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "react": ">=16.8"
+      }
+    },
+    "node_modules/react-router-dom": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.4.0.tgz",
+      "integrity": "sha512-4Aw1xmXKeleYYQ3x0Lcl2undHR6yMjXZjd9DKZd53SGOYqirrUThyUb0wwAX5VZAyvSuzjNJmZlJ3rR9+/vzqg==",
+      "dependencies": {
+        "react-router": "6.4.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "react": ">=16.8",
+        "react-dom": ">=16.8"
+      }
+    },
+    "node_modules/react-textarea-autosize": {
+      "version": "8.5.9",
+      "resolved": "https://registry.npmmirror.com/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz",
+      "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==",
+      "dependencies": {
+        "@babel/runtime": "^7.20.13",
+        "use-composed-ref": "^1.3.0",
+        "use-latest": "^1.2.1"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
+    "node_modules/resolve": {
+      "version": "1.22.10",
+      "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
+      "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+      "dependencies": {
+        "is-core-module": "^2.16.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.40.2",
+      "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.40.2.tgz",
+      "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "1.0.7"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.40.2",
+        "@rollup/rollup-android-arm64": "4.40.2",
+        "@rollup/rollup-darwin-arm64": "4.40.2",
+        "@rollup/rollup-darwin-x64": "4.40.2",
+        "@rollup/rollup-freebsd-arm64": "4.40.2",
+        "@rollup/rollup-freebsd-x64": "4.40.2",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.40.2",
+        "@rollup/rollup-linux-arm-musleabihf": "4.40.2",
+        "@rollup/rollup-linux-arm64-gnu": "4.40.2",
+        "@rollup/rollup-linux-arm64-musl": "4.40.2",
+        "@rollup/rollup-linux-loongarch64-gnu": "4.40.2",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2",
+        "@rollup/rollup-linux-riscv64-gnu": "4.40.2",
+        "@rollup/rollup-linux-riscv64-musl": "4.40.2",
+        "@rollup/rollup-linux-s390x-gnu": "4.40.2",
+        "@rollup/rollup-linux-x64-gnu": "4.40.2",
+        "@rollup/rollup-linux-x64-musl": "4.40.2",
+        "@rollup/rollup-win32-arm64-msvc": "4.40.2",
+        "@rollup/rollup-win32-ia32-msvc": "4.40.2",
+        "@rollup/rollup-win32-x64-msvc": "4.40.2",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/router": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/router/-/router-2.2.0.tgz",
+      "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^4.4.0",
+        "depd": "^2.0.0",
+        "is-promise": "^4.0.0",
+        "parseurl": "^1.3.3",
+        "path-to-regexp": "^8.0.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true
+    },
+    "node_modules/scheduler": {
+      "version": "0.23.2",
+      "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz",
+      "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/send": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/send/-/send-1.2.0.tgz",
+      "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^4.3.5",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "etag": "^1.8.1",
+        "fresh": "^2.0.0",
+        "http-errors": "^2.0.0",
+        "mime-types": "^3.0.1",
+        "ms": "^2.1.3",
+        "on-finished": "^2.4.1",
+        "range-parser": "^1.2.1",
+        "statuses": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
+    "node_modules/serve-static": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.0.tgz",
+      "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+      "dev": true,
+      "dependencies": {
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "parseurl": "^1.3.3",
+        "send": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "dev": true
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz",
+      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+      "dev": true,
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3",
+        "side-channel-list": "^1.0.0",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz",
+      "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+      "dev": true,
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "dev": true,
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "dev": true,
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/stylis": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz",
+      "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
+    },
+    "node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.13",
+      "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.13.tgz",
+      "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+      "dev": true,
+      "dependencies": {
+        "fdir": "^6.4.4",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz",
+      "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+      "dev": true,
+      "dependencies": {
+        "content-type": "^1.0.5",
+        "media-typer": "^1.1.0",
+        "mime-types": "^3.0.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+      "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+      "dev": 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"
+        }
+      ],
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/use-composed-ref": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/use-composed-ref/-/use-composed-ref-1.4.0.tgz",
+      "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/use-isomorphic-layout-effect": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz",
+      "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/use-latest": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/use-latest/-/use-latest-1.3.0.tgz",
+      "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==",
+      "dependencies": {
+        "use-isomorphic-layout-effect": "^1.1.1"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/vite": {
+      "version": "6.3.5",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.5.tgz",
+      "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+      "dev": true,
+      "dependencies": {
+        "esbuild": "^0.25.0",
+        "fdir": "^6.4.4",
+        "picomatch": "^4.0.2",
+        "postcss": "^8.5.3",
+        "rollup": "^4.34.9",
+        "tinyglobby": "^0.2.13"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+        "jiti": ">=1.21.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "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/warning": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
+      "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+      "dependencies": {
+        "loose-envify": "^1.0.0"
+      }
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/word-wrap": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz",
+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true
+    },
+    "node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true
+    },
+    "node_modules/yaml": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.7.1.tgz",
+      "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "bin": {
+        "yaml": "bin.mjs"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/zod": {
+      "version": "3.24.4",
+      "resolved": "https://registry.npmmirror.com/zod/-/zod-3.24.4.tgz",
+      "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/colinhacks"
+      }
+    },
+    "node_modules/zod-to-json-schema": {
+      "version": "3.24.5",
+      "resolved": "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz",
+      "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==",
+      "dev": true,
+      "peerDependencies": {
+        "zod": "^3.24.1"
+      }
+    }
+  }
+}
diff --git a/pt--frontend/package.json b/pt--frontend/package.json
new file mode 100644
index 0000000..f7617b2
--- /dev/null
+++ b/pt--frontend/package.json
@@ -0,0 +1,31 @@
+{
+  "name": "pt-fronted",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "lint": "eslint .",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@mantine/core": "^4.0.0",
+    "@mantine/hooks": "^4.0.0",
+    "axios": "^1.9.0",
+    "react": "^18.3.1",
+    "react-dom": "^18.3.1",
+    "react-router-dom": "^6.4.0"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.25.0",
+    "@types/react": "^19.1.2",
+    "@types/react-dom": "^19.1.2",
+    "@vitejs/plugin-react": "^4.4.1",
+    "eslint": "^9.25.0",
+    "eslint-plugin-react-hooks": "^5.2.0",
+    "eslint-plugin-react-refresh": "^0.4.19",
+    "globals": "^16.0.0",
+    "vite": "^6.3.5"
+  }
+}
diff --git a/pt--frontend/public/vite.svg b/pt--frontend/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/pt--frontend/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>
\ No newline at end of file
diff --git a/pt--frontend/src/App.css b/pt--frontend/src/App.css
new file mode 100644
index 0000000..1766c19
--- /dev/null
+++ b/pt--frontend/src/App.css
@@ -0,0 +1,2891 @@
+#root {
+  max-width: 100%;
+  margin: 0;
+  padding: 0;
+}
+
+/* 覆盖默认样式,适配Mantine组件 */
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+  border-radius: 8px;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
+
+.mantine-Card-root {
+  margin-bottom: 1rem;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
diff --git a/pt--frontend/src/App.jsx b/pt--frontend/src/App.jsx
new file mode 100644
index 0000000..81d3cb6
--- /dev/null
+++ b/pt--frontend/src/App.jsx
@@ -0,0 +1,17 @@
+// App.jsx
+import { Routes, Route } from 'react-router-dom';
+import Home from './pages/Home';
+import UploadPage from './pages/UploadPage';
+import TorrentDetail from './pages/Torrentdetail';
+
+function App() {
+  return (
+    <Routes>
+      <Route path="/" element={<Home />} />
+      <Route path="/upload" element={<UploadPage />} />
+      <Route path="/torrent/:id" element={<TorrentDetail />} />
+    </Routes>
+  );
+}
+
+export default App;
diff --git a/pt--frontend/src/api/activity.js b/pt--frontend/src/api/activity.js
new file mode 100644
index 0000000..0b2f802
--- /dev/null
+++ b/pt--frontend/src/api/activity.js
@@ -0,0 +1,13 @@
+import axios from 'axios';
+
+const BASE_URL = 'http://localhost:8080/activity';
+
+// 获取所有 is_show == 0 的活动预览(只含标题和图片)
+export const getActivityPreviews = () => {
+    return axios.get(`${BASE_URL}/preview`);
+};
+
+// 获取所有 is_show == 0 的完整活动信息
+export const getFullActivities = () => {
+    return axios.get(`${BASE_URL}/full`);
+};
diff --git a/pt--frontend/src/api/chat.js b/pt--frontend/src/api/chat.js
new file mode 100644
index 0000000..69f8353
--- /dev/null
+++ b/pt--frontend/src/api/chat.js
@@ -0,0 +1,37 @@
+import axios from 'axios';
+
+const BASE_URL = 'http://localhost:8080/chat';  // 根据你的后端接口地址调整
+
+// 发送消息
+export const sendMessage = async ({ senderId, receiverId, content }) => {
+    const now = new Date().toISOString(); // 发送时间
+
+    // 构造后端需要的格式,chatimformation1 或 chatimformation2 中只有一个有值
+    const payload = {
+        friend1: senderId,
+        friend2: receiverId,
+        talkTime: now,
+        chatimformation1: senderId < receiverId ? content : null, // 约定较小 ID 为 friend1 发送的消息
+        chatimformation2: senderId > receiverId ? content : null,
+    };
+
+    const response = await axios.post(`${BASE_URL}/create`, payload);
+    return response.data;
+};
+
+// 获取两个用户之间的聊天记录
+export const getMessagesByUserIds = async (senderId, receiverId) => {
+    const response = await axios.get(`${BASE_URL}/between`, {
+        params: {
+            user1: senderId,
+            user2: receiverId,
+        }
+    });
+    return response.data;
+};
+
+// 可选:获取某用户与所有人的聊天记录(如果你后续需要聊天列表用)
+export const getChatsByUser = async (userId) => {
+    const response = await axios.get(`${BASE_URL}/user/${userId}`);
+    return response.data;
+};
diff --git a/pt--frontend/src/api/comment.js b/pt--frontend/src/api/comment.js
new file mode 100644
index 0000000..28b2b4e
--- /dev/null
+++ b/pt--frontend/src/api/comment.js
@@ -0,0 +1,40 @@
+import axios from 'axios';
+
+const BASE_URL = 'http://localhost:8080/comment';
+
+// 创建评论
+export const createComment = (commentData) => {
+    return axios.post(`${BASE_URL}/create`, commentData);
+};
+
+// 删除评论
+export const deleteComment = (commentId) => {
+    return axios.delete(`${BASE_URL}/delete/${commentId}`);
+};
+
+// 更新评论
+export const updateComment = (commentData) => {
+    return axios.put(`${BASE_URL}/update`, commentData);
+};
+
+// 获取某个帖子的所有评论
+// comment.js
+export async function getCommentsByPostId(postid) {
+    try {
+        const response = await axios.get(`${BASE_URL}/post/${postid}`);
+        return Array.isArray(response.data) ? response.data : []; // 确保返回数据是数组
+    } catch (error) {
+        console.error('获取评论失败', error);
+        return [];
+    }
+}
+
+// 点赞评论
+export const likeComment = (commentId) => {
+    return axios.post(`${BASE_URL}/like/${commentId}`);
+};
+
+// 取消点赞评论
+export const unlikeComment = (commentId) => {
+    return axios.post(`${BASE_URL}/unlike/${commentId}`);
+};
diff --git a/pt--frontend/src/api/friends.js b/pt--frontend/src/api/friends.js
new file mode 100644
index 0000000..f139cc2
--- /dev/null
+++ b/pt--frontend/src/api/friends.js
@@ -0,0 +1,20 @@
+import axios from 'axios';
+
+const BASE_URL = 'http://localhost:8080/friends';
+
+// 添加好友
+export const addFriend = (friendData) => {
+    return axios.post(`${BASE_URL}/add`, friendData);
+};
+
+// ✅ 删除好友(通过 friend1 和 friend2 双向删除)
+export const deleteFriend = (friend1, friend2) => {
+    return axios.delete(`${BASE_URL}/delete`, {
+        params: { friend1, friend2 }
+    });
+};
+
+// 查询某个用户的所有好友(friend1 或 friend2)
+export const getFriendsByUserId = (userid) => {
+    return axios.get(`${BASE_URL}/list/${userid}`);
+};
diff --git a/pt--frontend/src/api/index.js b/pt--frontend/src/api/index.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pt--frontend/src/api/index.js
diff --git a/pt--frontend/src/api/post.js b/pt--frontend/src/api/post.js
new file mode 100644
index 0000000..9aa47a1
--- /dev/null
+++ b/pt--frontend/src/api/post.js
@@ -0,0 +1,110 @@
+const BASE_URL = 'http://localhost:8080/post';
+
+/**
+ * 创建帖子(带图片)
+ * @param {FormData} formData 包含 userid、post_title、post_content、tags、rannge、is_pinned、photo
+ */
+export const createPost = (formData) => {
+    return fetch(`${BASE_URL}/create`, {
+        method: 'POST',
+        body: formData,
+    }).then(res => res.json());
+};
+
+/**
+ * 删除帖子
+ * @param {number} postid 帖子 ID
+ */
+export const deletePost = (postid) => {
+    return fetch(`${BASE_URL}/delete/${postid}`, {
+        method: 'DELETE',
+    }).then(res => res.json());
+};
+
+/**
+ * 更新帖子(JSON 格式)
+ * @param {Object} post 帖子对象
+ */
+export const updatePost = (post) => {
+    return fetch(`${BASE_URL}/update`, {
+        method: 'PUT',
+        headers: {
+            'Content-Type': 'application/json',
+        },
+        body: JSON.stringify(post),
+    }).then(res => res.json());
+};
+
+/**
+ * 关键词搜索帖子
+ * @param {string} keyword 搜索关键词
+ */
+export const searchPosts = (keyword) => {
+    return fetch(`${BASE_URL}/search?keyword=${encodeURIComponent(keyword)}`)
+        .then(res => res.json());
+};
+
+/**
+ * 点赞帖子
+ * @param {number} postid 帖子 ID
+ */
+export const likePost = (postid) => {
+    return fetch(`${BASE_URL}/like/${postid}`, {
+        method: 'PUT',
+    }).then(res => res.json());
+};
+
+/**
+ * 取消点赞帖子
+ * @param {number} postid 帖子 ID
+ */
+export const unlikePost = (postid) => {
+    return fetch(`${BASE_URL}/unlike/${postid}`, {
+        method: 'PUT',
+    }).then(res => res.json());
+};
+
+/**
+ * 置顶帖子
+ * @param {number} postid 帖子 ID
+ */
+export const pinPost = (postid) => {
+    return fetch(`${BASE_URL}/pin/${postid}`, {
+        method: 'PUT',
+    }).then(res => res.json());
+};
+
+/**
+ * 取消置顶帖子
+ * @param {number} postid 帖子 ID
+ */
+export const unpinPost = (postid) => {
+    return fetch(`${BASE_URL}/unpin/${postid}`, {
+        method: 'PUT',
+    }).then(res => res.json());
+};
+
+/**
+ * 获取某用户所有帖子
+ * @param {number} userid 用户 ID
+ */
+export const findPostsByUserId = (userid) => {
+    return fetch(`${BASE_URL}/findByUserid?userid=${userid}`)
+        .then(res => res.json());
+};
+
+/**
+ * 获取所有置顶帖子
+ */
+export const findPinnedPosts = () => {
+    return fetch(`${BASE_URL}/findPinned`)
+        .then(res => res.json());
+};
+
+/**
+ * 获取所有帖子(排序后)
+ */
+export const getAllPostsSorted = () => {
+    return fetch(`${BASE_URL}/all`)
+        .then(res => res.json());
+};
diff --git a/pt--frontend/src/api/request.js b/pt--frontend/src/api/request.js
new file mode 100644
index 0000000..0a41208
--- /dev/null
+++ b/pt--frontend/src/api/request.js
@@ -0,0 +1,93 @@
+import axios from 'axios';
+
+const BASE_URL = 'http://localhost:8080/request';
+
+// 创建求助帖(支持上传图片)
+export const createRequest = (formData) => {
+    return axios.post(`${BASE_URL}/create`, formData, {
+        headers: {
+            'Content-Type': 'multipart/form-data',
+        },
+    });
+};
+
+// 修改求助帖金额
+export const updateMoney = (requestid, money) => {
+    return axios.put(`${BASE_URL}/updateMoney/${requestid}`, null, {
+        params: { money },
+    });
+};
+
+// ✅ 新增:根据名称批量更新被协助用户 ID
+export const updateLoaduserByName = (name, loaduser) => {
+    return axios.post(`${BASE_URL}/updateLoaduserByName`, null, {
+        params: { name, loaduser },
+    });
+};
+
+// 删除求助帖
+export const deleteRequest = (requestid) => {
+    return axios.delete(`${BASE_URL}/delete/${requestid}`);
+};
+
+// 根据名称查找求助帖
+export const findByName = async (name) => {
+    try {
+        const response = await axios.get(`${BASE_URL}/findByName`, {
+            params: { name },
+        });
+        return Array.isArray(response.data) ? response.data : [];
+    } catch (error) {
+        console.error('按名称查找求助帖失败', error);
+        return [];
+    }
+};
+
+// 根据发帖用户 ID 查找求助帖
+export const findByUserid = async (userid) => {
+    try {
+        const response = await axios.get(`${BASE_URL}/findByUserid`, {
+            params: { userid },
+        });
+        return Array.isArray(response.data) ? response.data : [];
+    } catch (error) {
+        console.error('按用户ID查找求助帖失败', error);
+        return [];
+    }
+};
+
+// 根据被协助用户 ID 查找求助帖
+export const findByLoaduser = async (loaduser) => {
+    try {
+        const response = await axios.get(`${BASE_URL}/findByLoaduser`, {
+            params: { loaduser },
+        });
+        return Array.isArray(response.data) ? response.data : [];
+    } catch (error) {
+        console.error('按被协助用户ID查找求助帖失败', error);
+        return [];
+    }
+};
+
+// 获取某名称的总金额
+export const getTotalMoneyByName = async (name) => {
+    try {
+        const response = await axios.get(`${BASE_URL}/totalMoneyByName`, {
+            params: { name },
+        });
+        return typeof response.data === 'number' ? response.data : 0;
+    } catch (error) {
+        console.error('获取总金额失败', error);
+        return 0;
+    }
+};
+
+export const getAllRequests = async () => {
+    try {
+        const response = await axios.get(`${BASE_URL}/all`);
+        return Array.isArray(response.data) ? response.data : [];
+    } catch (error) {
+        console.error('获取全部求助帖失败', error);
+        return [];
+    }
+};
diff --git a/pt--frontend/src/assets/react.svg b/pt--frontend/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/pt--frontend/src/assets/react.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="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
\ No newline at end of file
diff --git a/pt--frontend/src/components/ActivityFullList.jsx b/pt--frontend/src/components/ActivityFullList.jsx
new file mode 100644
index 0000000..02b3ebd
--- /dev/null
+++ b/pt--frontend/src/components/ActivityFullList.jsx
@@ -0,0 +1,36 @@
+// src/components/ActivityFullList.jsx
+import React, { useEffect, useState } from 'react';
+import { getFullActivities } from '../api/activity';
+
+const ActivityFullList = () => {
+    const [activities, setActivities] = useState([]);
+
+    useEffect(() => {
+        getFullActivities()
+            .then(res => setActivities(res.data))
+            .catch(err => console.error('获取完整活动失败:', err));
+    }, []);
+
+    return (
+        <div>
+            <h2>完整活动信息</h2>
+            <div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
+                {activities.map(activity => (
+                    <div key={activity.activityid} style={{ borderBottom: '1px solid #ddd', paddingBottom: '16px' }}>
+                        <h3>{activity.title}</h3>
+                        <img
+                            src={activity.photo}
+                            alt={activity.title}
+                            style={{ width: '300px', height: 'auto', borderRadius: '4px' }}
+                        />
+                        <p><strong>内容:</strong>{activity.content}</p>
+                        <p><strong>时间:</strong>{activity.time}</p>
+                        <p><strong>奖励:</strong>{activity.award}</p>
+                    </div>
+                ))}
+            </div>
+        </div>
+    );
+};
+
+export default ActivityFullList;
diff --git a/pt--frontend/src/components/ActivityPreview.jsx b/pt--frontend/src/components/ActivityPreview.jsx
new file mode 100644
index 0000000..05050df
--- /dev/null
+++ b/pt--frontend/src/components/ActivityPreview.jsx
@@ -0,0 +1,33 @@
+// src/components/ActivityPreview.jsx
+import React, { useEffect, useState } from 'react';
+import { getActivityPreviews } from '../api/activity';
+
+const ActivityPreview = () => {
+    const [activities, setActivities] = useState([]);
+
+    useEffect(() => {
+        getActivityPreviews()
+            .then(res => setActivities(res.data))
+            .catch(err => console.error('获取活动预览失败:', err));
+    }, []);
+
+    return (
+        <div>
+            <h2>活动预览</h2>
+            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '16px' }}>
+                {activities.map(activity => (
+                    <div key={activity.activityid} style={{ border: '1px solid #ccc', padding: '12px', borderRadius: '8px' }}>
+                        <h3>{activity.title}</h3>
+                        <img
+                            src={activity.photo}
+                            alt={activity.title}
+                            style={{ width: '200px', height: 'auto', borderRadius: '4px' }}
+                        />
+                    </div>
+                ))}
+            </div>
+        </div>
+    );
+};
+
+export default ActivityPreview;
diff --git a/pt--frontend/src/components/ChatBox.jsx b/pt--frontend/src/components/ChatBox.jsx
new file mode 100644
index 0000000..aec406a
--- /dev/null
+++ b/pt--frontend/src/components/ChatBox.jsx
@@ -0,0 +1,155 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { getMessagesByUserIds, sendMessage } from '../api/chat'; // 替换为新API
+
+const ChatBox = ({ senderId, receiverId }) => { // 不再接收relationId
+  const [messages, setMessages] = useState([]);
+  const [inputContent, setInputContent] = useState('');
+  const messagesEndRef = useRef(null);
+
+  // 加载历史消息(改为通过用户ID获取)
+  const loadMessages = async () => {
+    try {
+      if (!senderId || !receiverId) return;
+      const data = await getMessagesByUserIds(senderId, receiverId);
+
+      // 格式化消息:同时处理friend1(chatimformation1)和friend2(chatimformation2)的消息
+      const formattedMessages = data.flatMap(msg => {
+        const messages = [];
+        // 处理friend1的消息(chatimformation1)
+        if (msg.chatimformation1) {
+          messages.push({
+            content: msg.chatimformation1,
+            isSelf: msg.friend1 === senderId,  // 发送方是friend1,判断是否是当前用户
+            talkTime: msg.talkTime  // 保留时间用于排序和唯一key
+          });
+        }
+        // 处理friend2的消息(chatimformation2)
+        if (msg.chatimformation2) {
+          messages.push({
+            content: msg.chatimformation2,
+            isSelf: msg.friend2 === senderId,  // 发送方是friend2,判断是否是当前用户
+            talkTime: msg.talkTime  // 保留时间用于排序和唯一key
+          });
+        }
+        return messages;
+      }).sort((a, b) => new Date(a.talkTime) - new Date(b.talkTime)); // 按时间升序排列
+
+      setMessages(formattedMessages);
+    } catch (error) {
+      console.error('加载消息失败:', error.message);
+    }
+  };
+
+  // 发送消息(不再需要relationId)
+  const handleSend = async () => {
+    if (!inputContent.trim()) return;
+    if (!senderId || !receiverId) { // 新增:校验用户ID有效性
+      console.error('未获取到有效的用户ID');
+      return;
+    }
+
+    try {
+      const newMessage = await sendMessage({
+        senderId,
+        receiverId,
+        content: inputContent
+      });
+      setMessages(prev => [...prev, { content: inputContent, isSelf: true }]);
+      setInputContent('');
+      scrollToBottom();
+    } catch (error) {
+      console.error('发送消息失败:', error.message);
+    }
+  };
+
+  // 依赖改为用户ID变化时重新加载
+  useEffect(() => {
+    if (senderId && receiverId) loadMessages();
+  }, [senderId, receiverId]);
+
+  const scrollToBottom = () => {
+    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+  };
+
+  return (
+    <div className="chat-box" style={{
+      width: '400px',
+      height: '600px',
+      border: '1px solid #e5e7eb',
+      borderRadius: '8px',
+      display: 'flex',
+      flexDirection: 'column'
+    }}>
+      {/* 消息列表 */}
+      <div style={{
+        flex: 1,
+        padding: '16px',
+        overflowY: 'auto',
+        backgroundColor: '#f3f4f6'
+      }}>
+        {/* 消息列表渲染(使用数据库唯一ID作为key) */}
+        {messages.map((msg) => (
+          <div
+            key={msg.informationid}
+            style={{
+              marginBottom: '12px',
+              display: 'flex',
+              justifyContent: msg.isSelf ? 'flex-end' : 'flex-start'
+            }}
+          >
+            <div
+              style={{
+                maxWidth: '70%',
+                padding: '8px 12px',
+                borderRadius: '12px',
+                backgroundColor: msg.isSelf ? '#2563eb' : '#ffffff',
+                color: msg.isSelf ? 'white' : 'black'
+              }}
+            >
+              {msg.content}
+            </div>
+          </div>
+        ))}
+        <div ref={messagesEndRef} />
+      </div>
+
+      {/* 输入区域 */}
+      <div style={{
+        padding: '16px',
+        display: 'flex',
+        gap: '8px',
+        borderTop: '1px solid #e5e7eb'
+      }}>
+        <input
+          type="text"
+          value={inputContent}
+          onChange={(e) => setInputContent(e.target.value)}
+          onKeyPress={(e) => e.key === 'Enter' && handleSend()}
+          placeholder="输入消息..."
+          style={{
+            flex: 1,
+            padding: '8px 12px',
+            borderRadius: '6px',
+            border: '1px solid #d1d5db',
+            fontSize: '14px'
+          }}
+        />
+        <button
+          onClick={handleSend}
+          style={{
+            padding: '8px 16px',
+            backgroundColor: '#2563eb',
+            color: 'white',
+            border: 'none',
+            borderRadius: '6px',
+            cursor: 'pointer'
+          }}
+        >
+          发送
+        </button>
+      </div>
+    </div>
+  );
+};
+
+export default ChatBox;
\ No newline at end of file
diff --git a/pt--frontend/src/components/Comment.jsx b/pt--frontend/src/components/Comment.jsx
new file mode 100644
index 0000000..9708566
--- /dev/null
+++ b/pt--frontend/src/components/Comment.jsx
@@ -0,0 +1,94 @@
+// src/components/Comment.jsx
+import React, { useState, useEffect } from 'react';
+import {
+    getCommentsByPostId,
+    createComment,
+    deleteComment,
+    likeComment,
+    unlikeComment,
+} from '../api/comment';
+
+const Comment = ({ postId, currentUser }) => {
+    const [comments, setComments] = useState([]);
+    const [newContent, setNewContent] = useState('');
+
+    useEffect(() => {
+        loadComments();
+    }, [postId]);
+
+    const loadComments = async () => {
+        const data = await getCommentsByPostId(postId);
+        setComments(data);
+    };
+
+    const handleCreate = async () => {
+        if (!newContent.trim()) return;
+
+        const commentData = {
+            postid: postId,
+            userid: currentUser.id,
+            postCommentcontent: newContent,
+            commenttime: new Date().toISOString()
+        };
+
+        await createComment(commentData);
+        setNewContent('');
+        loadComments();
+    };
+
+    const handleDelete = async (commentid) => {
+        await deleteComment(commentid);
+        loadComments();
+    };
+
+    const handleLike = async (commentid) => {
+        await likeComment(commentid);
+        loadComments();
+    };
+
+    const handleUnlike = async (commentid) => {
+        await unlikeComment(commentid);
+        loadComments();
+    };
+
+    return (
+        <div>
+            <h4 className="font-semibold text-gray-700 mb-2">评论</h4>
+            <div className="mb-2">
+                <textarea
+                    value={newContent}
+                    onChange={(e) => setNewContent(e.target.value)}
+                    placeholder="写下你的评论..."
+                    className="w-full border rounded p-2"
+                />
+                <button
+                    onClick={handleCreate}
+                    className="mt-2 bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600"
+                >
+                    发布评论
+                </button>
+            </div>
+
+            <div className="space-y-2 mt-4">
+                {comments.map((comment) => (
+                    <div key={comment.commentid} className="border rounded p-2">
+                        <div className="text-sm text-gray-800 font-medium">用户ID:{comment.userid}</div>
+                        <div className="text-gray-700">{comment.postCommentcontent}</div>
+                        <div className="text-xs text-gray-500 mt-1">
+                            {comment.commenttime || '暂无时间'} | 👍 {comment.likes}
+                        </div>
+                        <div className="flex gap-2 mt-1 text-sm">
+                            <button onClick={() => handleLike(comment.commentid)} className="text-blue-500 hover:underline">点赞</button>
+                            <button onClick={() => handleUnlike(comment.commentid)} className="text-yellow-500 hover:underline">取消点赞</button>
+                            {comment.userid === currentUser.id && (
+                                <button onClick={() => handleDelete(comment.commentid)} className="text-red-500 hover:underline">删除</button>
+                            )}
+                        </div>
+                    </div>
+                ))}
+            </div>
+        </div>
+    );
+};
+
+export default Comment;
diff --git a/pt--frontend/src/components/FriendManager.jsx b/pt--frontend/src/components/FriendManager.jsx
new file mode 100644
index 0000000..d28f93d
--- /dev/null
+++ b/pt--frontend/src/components/FriendManager.jsx
@@ -0,0 +1,156 @@
+import React, { useState, useEffect } from 'react';
+import { addFriend, deleteFriend, getFriendsByUserId } from '../api/friends';
+
+const FriendManager = ({ currentUser, onSelectRelation }) => {
+    const [friendId, setFriendId] = useState('');
+    const [friends, setFriends] = useState([]);
+    const [isLoading, setIsLoading] = useState(false);
+    const [isRefreshing, setIsRefreshing] = useState(false);
+
+    useEffect(() => {
+        if (currentUser?.id) loadFriends(currentUser.id);
+    }, [currentUser]);
+
+    const loadFriends = async (userid) => {
+        setIsRefreshing(true);
+        try {
+            const res = await getFriendsByUserId(userid);
+            setFriends(res.data);
+        } catch (err) {
+            console.error('加载好友失败', err);
+            alert('加载好友失败,请稍后重试');
+        }
+        setIsRefreshing(false);
+    };
+
+    const handleFriendIdChange = (e) => {
+        const value = e.target.value;
+        if (/^\d*$/.test(value)) setFriendId(value);
+    };
+
+    const handleAddFriend = async () => {
+        if (!friendId) return alert('请输入好友ID');
+
+        const newFriendId = parseInt(friendId, 10);
+        if (newFriendId === currentUser.id) return alert('不能添加自己为好友');
+
+        if (friends.some(f => f.friend1 === newFriendId || f.friend2 === newFriendId)) {
+            return alert('该用户已是您的好友');
+        }
+
+        setIsLoading(true);
+        try {
+            const res = await addFriend({ friend1: currentUser.id, friend2: newFriendId });
+            if (res.data) {
+                alert('添加成功');
+                setFriendId('');
+                loadFriends(currentUser.id);
+            } else {
+                alert('添加失败');
+            }
+        } catch (err) {
+            alert('添加好友失败');
+            console.error(err);
+        }
+        setIsLoading(false);
+    };
+
+    /* ---------- 这里开始:删除好友逻辑改为 friend1 + friend2 ---------- */
+    const handleDelete = async (friend1, friend2) => {
+        if (!window.confirm('确认删除该好友吗?')) return;
+        setIsLoading(true);
+        try {
+            const res = await deleteFriend(friend1, friend2);
+            if (res.data) {
+                alert('删除成功');
+                loadFriends(currentUser.id);
+            } else {
+                alert('删除失败');
+            }
+        } catch (err) {
+            alert('删除好友失败');
+            console.error(err);
+        }
+        setIsLoading(false);
+    };
+    /* ------------------------------------------------------------------- */
+
+    return (
+        <div className="max-w-xl mx-auto p-4">
+            <h2 className="text-2xl font-bold mb-4">好友管理</h2>
+
+            {/* 添加好友区域 */}
+            <div className="mb-6 space-y-2">
+                <input
+                    type="text"
+                    placeholder="输入好友的用户ID"
+                    value={friendId}
+                    onChange={handleFriendIdChange}
+                    className="border p-2 rounded w-full"
+                />
+                <button
+                    onClick={handleAddFriend}
+                    disabled={isLoading}
+                    className={`bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 ${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`}
+                >
+                    {isLoading ? '添加中…' : '添加好友'}
+                </button>
+            </div>
+
+            {/* 好友列表 */}
+            <div className="flex justify-between items-center mb-3">
+                <h3 className="text-xl font-semibold">我的好友列表</h3>
+                <button
+                    onClick={() => loadFriends(currentUser.id)}
+                    disabled={isRefreshing}
+                    className="text-sm text-blue-500 hover:underline disabled:text-gray-400"
+                >
+                    {isRefreshing ? '刷新中…' : '刷新'}
+                </button>
+            </div>
+
+            {friends.length === 0 ? (
+                <p className="text-gray-500">暂无好友</p>
+            ) : (
+                <ul className="space-y-3">
+                    {friends.map((f) => {
+                        const friendUserId =
+                            f.friend1 === currentUser.id ? f.friend2 : f.friend1;
+                        return (
+                            <li
+                                key={f.relationid}
+                                className="border p-3 rounded flex justify-between items-center hover:bg-gray-100 cursor-pointer"
+                                onClick={() =>
+                                    onSelectRelation({
+                                        relationid: f.relationid,
+                                        friendId: friendUserId,
+                                    })
+                                }
+                            >
+                                <div>
+                                    <p>好友用户ID:{friendUserId}</p>
+                                    <p className="text-sm text-gray-500">
+                                        添加时间:{new Date(f.requestTime).toLocaleString()}
+                                    </p>
+                                </div>
+                                <button
+                                    onClick={(e) => {
+                                        e.stopPropagation();
+                                        /* ------- 传入正确的 friend1 & friend2 -------- */
+                                        handleDelete(f.friend1, f.friend2);
+                                    }}
+                                    className="text-red-500 hover:underline"
+                                    disabled={isLoading}
+                                >
+                                    删除
+                                </button>
+                            </li>
+                        );
+                    })}
+                </ul>
+            )}
+        </div>
+    );
+};
+
+export default FriendManager;
diff --git a/pt--frontend/src/components/Post.jsx b/pt--frontend/src/components/Post.jsx
new file mode 100644
index 0000000..10a4840
--- /dev/null
+++ b/pt--frontend/src/components/Post.jsx
@@ -0,0 +1,200 @@
+import React, { useState, useEffect } from 'react';
+import {
+    createPost,
+    findPinnedPosts,
+    likePost,
+    unlikePost,
+    searchPosts,
+    getAllPostsSorted,
+    findPostsByUserId,
+} from '../api/post';
+import Comment from './Comment';
+
+const Post = () => {
+    const [title, setTitle] = useState('');
+    const [content, setContent] = useState('');
+    const [tags, setTags] = useState('');
+    const [photo, setPhoto] = useState(null);
+    const [posts, setPosts] = useState([]);
+    const [searchKeyword, setSearchKeyword] = useState('');
+
+    const currentUser = { id: 1, username: '测试用户' };
+
+    useEffect(() => {
+        loadPinnedPosts();  // 初始加载置顶帖子
+    }, []);
+
+    const loadPinnedPosts = async () => {
+        const data = await findPinnedPosts();
+        setPosts(data);
+    };
+
+    const loadAllPosts = async () => {
+        const data = await getAllPostsSorted();
+        setPosts(data);
+    };
+
+    const loadMyPosts = async () => {
+        const data = await findPostsByUserId(currentUser.id);
+        setPosts(data);
+    };
+
+    const handleCreate = async () => {
+        const formData = new FormData();
+        formData.append('userid', currentUser.id);
+        formData.append('post_title', title);
+        formData.append('post_content', content);
+        formData.append('is_pinned', true);
+        formData.append('tags', tags);
+        formData.append('rannge', 'public');
+        if (photo) {
+            formData.append('photo', photo);
+        }
+
+        const success = await createPost(formData);
+        if (success) {
+            alert('帖子创建成功');
+            loadPinnedPosts();
+            setTitle('');
+            setContent('');
+            setTags('');
+            setPhoto(null);
+        } else {
+            alert('创建失败');
+        }
+    };
+
+    const handleLike = async (postid) => {
+        await likePost(postid);
+        loadPinnedPosts();
+    };
+
+    const handleUnlike = async (postid) => {
+        await unlikePost(postid);
+        loadPinnedPosts();
+    };
+
+    const handleSearch = async () => {
+        const result = await searchPosts(searchKeyword);
+        setPosts(result);
+    };
+
+    return (
+        <div className="p-4 max-w-3xl mx-auto">
+            <h1 className="text-2xl font-bold mb-4">创建帖子</h1>
+            <div className="space-y-3 mb-6">
+                <input
+                    type="text"
+                    placeholder="标题"
+                    value={title}
+                    onChange={(e) => setTitle(e.target.value)}
+                    className="w-full border p-2 rounded"
+                />
+                <textarea
+                    placeholder="内容"
+                    value={content}
+                    onChange={(e) => setContent(e.target.value)}
+                    className="w-full border p-2 rounded"
+                />
+                <input
+                    type="text"
+                    placeholder="标签(用逗号分隔,如 学习,编程)"
+                    value={tags}
+                    onChange={(e) => setTags(e.target.value)}
+                    className="w-full border p-2 rounded"
+                />
+                <input
+                    type="file"
+                    onChange={(e) => setPhoto(e.target.files[0])}
+                />
+                <button
+                    onClick={handleCreate}
+                    className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
+                >
+                    发布
+                </button>
+            </div>
+
+            <div className="mb-6 flex gap-3">
+                <input
+                    type="text"
+                    placeholder="搜索关键词"
+                    value={searchKeyword}
+                    onChange={(e) => setSearchKeyword(e.target.value)}
+                    className="border p-2 rounded flex-grow"
+                />
+                <button
+                    onClick={handleSearch}
+                    className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
+                >
+                    搜索
+                </button>
+            </div>
+
+            {/* 新增三个展示按钮 */}
+            <div className="mb-6 flex gap-3">
+                <button
+                    onClick={loadPinnedPosts}
+                    className="bg-yellow-500 text-white px-4 py-2 rounded hover:bg-yellow-600"
+                >
+                    置顶帖子
+                </button>
+                <button
+                    onClick={loadAllPosts}
+                    className="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600"
+                >
+                    所有帖子
+                </button>
+                <button
+                    onClick={loadMyPosts}
+                    className="bg-indigo-500 text-white px-4 py-2 rounded hover:bg-indigo-600"
+                >
+                    我的帖子
+                </button>
+            </div>
+
+            <h2 className="text-xl font-semibold mb-3">帖子列表</h2>
+            <div className="space-y-6">
+                {posts.map((post) => (
+                    <div key={post.postid} className="border rounded p-4 shadow bg-white">
+                        <h3 className="text-lg font-bold">{post.postTitle}</h3>
+                        <p className="text-gray-700">{post.postContent}</p>
+                        {post.photo && (
+                            <img
+                                src={`http://localhost:8080${post.photo}`}
+                                alt="post"
+                                className="w-64 h-auto mt-2"
+                            />
+                        )}
+                        <div className="mt-2 text-sm text-gray-500">
+                            发布时间:{post.postCreatedTime}
+                        </div>
+                        <div className="mt-2 text-sm text-gray-500">
+                            标签:{post.tags || '无'}
+                        </div>
+                        <div className="mt-2 flex items-center gap-4">
+                            <span>👍 {post.likes}</span>
+                            <button
+                                onClick={() => handleLike(post.postid)}
+                                className="text-blue-500 hover:underline"
+                            >
+                                点赞
+                            </button>
+                            <button
+                                onClick={() => handleUnlike(post.postid)}
+                                className="text-red-500 hover:underline"
+                            >
+                                取消点赞
+                            </button>
+                        </div>
+                        <div className="mt-4 border-t pt-4">
+                            <Comment postId={post.postid} currentUser={currentUser} />
+                        </div>
+                    </div>
+                ))}
+            </div>
+        </div>
+    );
+};
+
+export default Post;
diff --git a/pt--frontend/src/components/RequestBoard.jsx b/pt--frontend/src/components/RequestBoard.jsx
new file mode 100644
index 0000000..f90f2fb
--- /dev/null
+++ b/pt--frontend/src/components/RequestBoard.jsx
@@ -0,0 +1,300 @@
+import React, { useState, useEffect } from 'react';
+import {
+    createRequest,
+    deleteRequest,
+    updateMoney,
+    findByUserid,
+    findByName,
+    getTotalMoneyByName,
+    updateLoaduserByName,
+} from '../api/request';
+
+const RequestBoard = ({ currentUserId }) => {
+    const [requests, setRequests] = useState([]); // 始终存储当前用户的求助帖
+    const [searchedRequests, setSearchedRequests] = useState([]); // 存储搜索结果
+    const [formData, setFormData] = useState({
+        userid: currentUserId,
+        name: '',
+        plot: '',
+        money: '',
+        year: '',
+        country: '',
+        photo: null,
+    });
+    const [searchName, setSearchName] = useState('');
+    const [totalMoney, setTotalMoney] = useState(null);
+
+    // 获取当前用户求助帖
+    const loadUserRequests = async () => {
+        const data = await findByUserid(currentUserId);
+        setRequests(data);
+    };
+
+    useEffect(() => {
+        loadUserRequests();
+    }, []);
+
+    // 表单变更处理
+    const handleChange = (e) => {
+        const { name, value, files } = e.target;
+        setFormData((prev) => ({
+            ...prev,
+            [name]: files ? files[0] : value,
+        }));
+    };
+
+    // 提交创建
+    const handleCreate = async (e) => {
+        e.preventDefault();
+        const fd = new FormData();
+        Object.entries(formData).forEach(([key, value]) => {
+            if (value !== '' && value !== null) fd.append(key, value);
+        });
+
+        const res = await createRequest(fd);
+        if (res.data === true) {
+            alert('创建成功');
+            setFormData(prev => ({
+                ...prev,
+                name: '',
+                plot: '',
+                money: '',
+                year: '',
+                country: '',
+                photo: null,
+            }));
+            loadUserRequests(); // 创建成功后刷新当前用户帖子
+        } else {
+            alert('创建失败');
+        }
+    };
+
+    // 删除
+    const handleDelete = async (id) => {
+        await deleteRequest(id);
+        loadUserRequests(); // 删除后刷新当前用户帖子
+    };
+
+    // 更新金额
+    const handleUpdateMoney = async (id, newMoney) => {
+        if (!newMoney) return;
+        await updateMoney(id, newMoney);
+        loadUserRequests(); // 更新金额后刷新当前用户帖子
+    };
+
+    // 按名称搜索并计算总金额
+    const handleSearch = async () => {
+        const data = await findByName(searchName);
+        const total = await getTotalMoneyByName(searchName);
+        setSearchedRequests(data); // 搜索结果存储到独立状态
+        setTotalMoney(total);
+    };
+
+    // 上传更新被协助用户 ID(批量更新同名求助帖)
+    const handleUploadLoaduser = async (name) => {
+        try {
+            await updateLoaduserByName(name, currentUserId);
+            alert('更新成功');
+            loadUserRequests(); // 更新后刷新当前用户帖子
+            setSearchedRequests([]); // 同步清空搜索结果(可选)
+        } catch (error) {
+            alert('更新失败,请稍后重试');
+            console.error(error);
+        }
+    };
+
+    return (
+        <div className="p-4 max-w-4xl mx-auto">
+            <h2 className="text-2xl font-bold mb-4">发布求助帖</h2>
+            <form className="grid grid-cols-2 gap-4 mb-6" onSubmit={handleCreate}>
+                <input
+                    name="name"
+                    placeholder="标题/名称"
+                    className="border p-2"
+                    value={formData.name}
+                    onChange={handleChange}
+                />
+                <textarea
+                    name="plot"
+                    placeholder="内容/情节"
+                    className="border p-2 col-span-2"
+                    value={formData.plot}
+                    onChange={handleChange}
+                />
+                <input
+                    name="money"
+                    type="number"
+                    placeholder="金额"
+                    className="border p-2"
+                    value={formData.money}
+                    onChange={handleChange}
+                />
+                <input
+                    name="year"
+                    type="number"
+                    placeholder="年份"
+                    className="border p-2"
+                    value={formData.year}
+                    onChange={handleChange}
+                />
+                <input
+                    name="country"
+                    placeholder="国家"
+                    className="border p-2"
+                    value={formData.country}
+                    onChange={handleChange}
+                />
+                <input
+                    name="photo"
+                    type="file"
+                    className="border p-2 col-span-2"
+                    onChange={handleChange}
+                />
+                <button
+                    type="submit"
+                    className="bg-blue-500 text-white p-2 col-span-2 rounded"
+                >
+                    发布
+                </button>
+            </form>
+
+            <div className="mb-6">
+                <h3 className="text-xl font-semibold mb-2">查找求助帖</h3>
+                <div className="flex gap-2 mb-2">
+                    <input
+                        type="text"
+                        placeholder="输入标题"
+                        value={searchName}
+                        onChange={(e) => setSearchName(e.target.value)}
+                        className="border p-2 flex-1"
+                    />
+                    <button
+                        onClick={handleSearch}
+                        className="bg-green-500 text-white p-2 rounded"
+                    >
+                        查找
+                    </button>
+                </div>
+                {totalMoney !== null && (
+                    <p className="text-gray-700">
+                        该名称对应的总金额:<strong>{totalMoney}</strong>
+                    </p>
+                )}
+            </div>
+
+            {/* 搜索结果展示(有搜索结果时显示) */}
+            {searchedRequests.length > 0 && (
+                <div className="mb-6">
+                    <h3 className="text-xl font-semibold mb-2">搜索结果</h3>
+                    <button
+                        onClick={() => {
+                            setSearchedRequests([]);
+                            setSearchName('');
+                            setTotalMoney(null);
+                        }}
+                        className="bg-gray-500 text-white p-1 px-2 rounded mb-2"
+                    >
+                        ← 返回我的求助帖
+                    </button>
+                    {searchedRequests.length === 0 ? (
+                        <p className="text-gray-500">无匹配的求助帖</p>
+                    ) : (
+                        <div className="grid grid-cols-1 gap-4">
+                            {searchedRequests.map((request) => (
+                                <div key={request.requestid} className="border p-3 rounded shadow">
+                                    <h4 className="text-lg font-semibold">{request.name}</h4>
+                                    <p className="text-gray-600 mb-2">{request.plot}</p>
+                                    <div className="flex gap-2 mb-2">
+                                        <span>金额:{request.money}</span>
+                                        <span>年份:{request.year || '未填写'}</span>
+                                        <span>国家:{request.country || '未填写'}</span>
+                                    </div>
+                                    {request.photo && (
+                                        <img
+                                            src={`http://localhost:8080${request.photo}`}
+                                            alt="求助帖"
+                                            className="w-32 h-auto mb-2"
+                                        />
+                                    )}
+                                    <div className="flex gap-2">
+                                        <input
+                                            type="number"
+                                            placeholder="新金额"
+                                            onChange={(e) => handleUpdateMoney(request.requestid, e.target.value)}
+                                            className="border p-1 flex-1"
+                                        />
+                                        <button
+                                            onClick={() => handleDelete(request.requestid)}
+                                            className="bg-red-500 text-white p-1 px-2 rounded"
+                                        >
+                                            删除
+                                        </button>
+                                        <button
+                                            onClick={() => handleUploadLoaduser(request.name)}
+                                            className="bg-blue-500 text-white p-1 px-2 rounded"
+                                        >
+                                            上传更新loaduser
+                                        </button>
+                                    </div>
+                                </div>
+                            ))}
+                        </div>
+                    )}
+                </div>
+            )}
+
+            {/* 我的求助帖展示(无搜索结果时显示) */}
+            {searchedRequests.length === 0 && (
+                <div className="mb-6">
+                    <h3 className="text-xl font-semibold mb-2">我的求助帖</h3>
+                    {requests.length === 0 ? (
+                        <p className="text-gray-500">暂无求助帖</p>
+                    ) : (
+                        <div className="grid grid-cols-1 gap-4">
+                            {requests.map((request) => (
+                                <div key={request.requestid} className="border p-3 rounded shadow">
+                                    <h4 className="text-lg font-semibold">{request.name}</h4>
+                                    <p className="text-gray-600 mb-2">{request.plot}</p>
+                                    <div className="flex gap-2 mb-2">
+                                        <span>金额:{request.money}</span>
+                                        <span>年份:{request.year || '未填写'}</span>
+                                        <span>国家:{request.country || '未填写'}</span>
+                                    </div>
+                                    {request.photo && (
+                                        <img
+                                            src={`http://localhost:8080${request.photo}`}
+                                            alt="求助帖"
+                                            className="w-32 h-auto mb-2"
+                                        />
+                                    )}
+                                    <div className="flex gap-2">
+                                        <input
+                                            type="number"
+                                            placeholder="新金额"
+                                            onChange={(e) => handleUpdateMoney(request.requestid, e.target.value)}
+                                            className="border p-1 flex-1"
+                                        />
+                                        <button
+                                            onClick={() => handleDelete(request.requestid)}
+                                            className="bg-red-500 text-white p-1 px-2 rounded"
+                                        >
+                                            删除
+                                        </button>
+                                        <button
+                                            onClick={() => handleUploadLoaduser(request.name)}
+                                            className="bg-blue-500 text-white p-1 px-2 rounded"
+                                        >
+                                            上传更新loaduser
+                                        </button>
+                                    </div>
+                                </div>
+                            ))}
+                        </div>
+                    )}
+                </div>
+            )}
+        </div>
+    );
+};
+
+export default RequestBoard;
diff --git a/pt--frontend/src/components/torrentlist.jsx b/pt--frontend/src/components/torrentlist.jsx
new file mode 100644
index 0000000..be4b76b
--- /dev/null
+++ b/pt--frontend/src/components/torrentlist.jsx
@@ -0,0 +1,1319 @@
+// import { useState, useEffect } from 'react';
+// import { Link } from 'react-router-dom';
+// import axios from 'axios';
+
+// function TorrentList() {
+//   const [torrents, setTorrents] = useState([]);
+//   const [categories, setCategories] = useState([]);
+//   const [selectedCategory, setSelectedCategory] = useState('');
+
+//   // 获取所有分类
+//   useEffect(() => {
+//     axios.get('http://localhost:8080/categories') // 假设这个接口返回所有分类
+//       .then(res => setCategories(res.data))
+//       .catch(err => console.error('获取分类失败', err));
+//   }, []);
+
+//   // 获取种子(根据分类筛选)
+//   useEffect(() => {
+//     const url = selectedCategory
+//       ? `http://localhost:8080/torrent/listByCategory?categoryid=${selectedCategory}`
+//       : 'http://localhost:8080/torrent/list';
+
+//     axios.get(url)
+//       .then(res => setTorrents(res.data))
+//       .catch(err => console.error('获取种子失败', err));
+//   }, [selectedCategory]);
+//   console.log(torrents);
+  
+
+//   return (
+//     <div className="p-4">
+//       <div className="mb-4">
+//         <label className="mr-2 font-medium">选择分类:</label>
+//         <select
+//           value={selectedCategory}
+//           onChange={e => setSelectedCategory(e.target.value)}
+//           className="border rounded px-2 py-1"
+//         >
+//           <option value="">全部</option>
+//           {categories.map(cat => (
+//             <option key={cat.categoryid} value={cat.categoryid}>
+//               {cat.category_name}
+//             </option>
+//           ))}
+//         </select>
+//       </div>
+//       <table className="w-full border-collapse">
+//         <thead>
+//           <tr className="bg-gray-200">
+//             <th className="p-2 border">名称</th>
+//             <th className="p-2 border">上传者</th>
+//             <th className="p-2 border">描述</th>
+//             <th className="p-2 border">上传时间</th>
+//             <th className="p-2 border">下载次数</th>
+//             <th className="p-2 border">促销方式</th>
+//             <th className="p-2 border">操作</th>
+//           </tr>
+//         </thead>
+//         <tbody>
+//           {torrents.map(t => (
+//             <tr key={t.torrentid} className="border-t hover:bg-gray-100">
+//               <td className="p-2 border">{t.filename}</td>
+//               <td className="p-2 border">{t.uploader_id}</td>
+//               <td className="p-2 border">{t.description}</td>
+//               <td className="p-2 border">{new Date(t.uploadTime).toLocaleString()}</td>
+//               <td className="p-2 border">{t.downloadCount}</td>
+//               <td className="p-2 border">
+//           {(() => {
+//             switch(t.promotionid) {
+//               case 1: return '上传加倍';
+//               case 2: return '下载免费';
+//               case 3: return '下载减半';
+//               case 0: return '没有促销';
+//               default: return '没有促销';
+//             }
+//           })()}
+//         </td>
+//               <td className="p-2 border">
+//                 <a
+//                   href={`http://localhost:8080/torrent/download/${t.torrentid}`}
+//                   className="text-blue-500 hover:underline"
+//                   target="_blank"
+//                   rel="noreferrer"
+//                 >
+//                   下载
+//                 </a>
+//               </td>
+//               <Link
+//          to={`/torrent/${t.torrentid}`}
+//           className="text-green-600 hover:underline"
+//        >
+//         查看详情
+//        </Link>
+//             </tr>
+//           ))}
+//         </tbody>
+//       </table>
+//     </div>
+//   );
+// }
+
+// export default TorrentList;
+// import { useState, useEffect } from 'react';
+// import axios from 'axios';
+
+// function TorrentList() {
+//   const [torrents, setTorrents] = useState([]);
+//   const [categories, setCategories] = useState([]);
+//   const [selectedCategory, setSelectedCategory] = useState('');
+//   const [filters, setFilters] = useState({});
+//   const [showSuccess, setShowSuccess] = useState(false);
+//   const softwaregenres = [
+//     { value: '系统软件', label: '系统软件' },
+//     { value: '应用软件', label: '应用软件' },
+//     { value: '游戏软件', label: '游戏软件' },
+//     { value: '驱动程序', label: '驱动程序' },
+//     { value: '办公软件', label: '办公软件' },
+//     { value: '其他', label: '其他' },
+//   ]
+//   const softwareplatforms = [
+//     { value: 'Windows', label: 'Windows' },
+//     { value: 'Mac', label: 'Mac' },
+//     { value: 'Linux', label: 'Linux' },
+//     { value: 'Android', label: 'Android' },
+//     { value: 'iOS', label: 'iOS' },
+//     { value: '其他', label: '其他' },
+//   ]
+//   const softwareformats = [
+//     { value: 'EXE', label: 'EXE' }, 
+//     { value: 'DMG', label: 'DMG' },
+//     { value: '光盘镜像', label: '光盘镜像' },
+//     { value: 'APK', label: 'APK' },
+//     { value: 'IPA', label: 'IPA' },
+//     { value: '其他', label: '其他' },
+//   ]
+//    const sourceTypes = [
+//     { value: 'CCTV', label: 'CCTV' },
+//     { value: '卫视', label: '卫视' },
+//     { value: '国家地理', label: '国家地理' },
+//     { value: 'BBC', label: 'BBC' },
+//     { value: 'Discovery', label: 'Discovery' },
+//     { value: '其他', label: '其他' },
+//   ]
+//   const othergenres = [
+//     { value: '电子书', label: '电子书' },
+//     { value: '视频', label: '视频' },
+//     { value: 'MP3', label: 'MP3' },
+//     { value: '图片', label: '图片' },
+//     { value: '其他', label: '其他' },
+//   ]
+// const resolutions = [
+//     { value: '720p', label: '720p' },
+//     { value: '1080p', label: '1080p' },
+//     { value: '2K', label: '2K' },
+//     { value: '4K', label: '4K' },
+//     { value: '8K', label: '8K' },
+//     { value: '其他', label: '其他' },
+//   ];
+
+//   // 每个分类的筛选字段配置
+//   const categoryFiltersConfig = {
+//     1: [ // Movie 电影
+//       { id: 'resolution', label: '分辨率', type: 'select', options: ['1080p', '4K', '720p', '其他'] },
+//       { id: 'codecFormat', label: '编码格式', type: 'select', options: ['H.264', 'H.265', 'AV1', 'VC1', 'X264', '其他'] },
+//       { id: 'region', label: '地区', type: 'select', options: ['大陆', '港台', '欧美', '日韩', '其他'] },
+//       { id: 'genre', label: '类型', type: 'select', options: ['动作', '喜剧', '爱情', '科幻', '恐怖','动作', '冒险', '历史', '悬疑', '其他'] },
+//     ],
+//     2: [ // TV 剧集
+//       { id: 'region', label: '地区', type: 'select', options: ['大陆', '港台', '欧美', '日韩', '其他'] },
+//       { id: 'format', label: '格式', type: 'select', options: resolutions },
+//       { id: 'genre', label: '类型', type: 'select', options: ['动作', '喜剧', '爱情', '科幻', '恐怖','动作', '冒险', '历史', '悬疑', '其他'] },
+//     ],
+//     3: [ // Music 音乐
+//       { id: 'genre', label: '类型', type: 'select', options: ['专辑', '单曲', 'EP', '现场', '其他'] },
+//       { id: 'style', label: '风格', type: 'select', options: ['流行', '摇滚', '电子', '古典', '爵士', '民谣', '说唱', '其他'] },
+//     ],
+//     4: [ // Anime 动漫
+//       { id: 'genre', label: '类型', type: 'select', options: ['新番连载', '剧场版', 'OVA', '完结动漫', '其他'] },
+//       { id: 'format', label: '格式', type: 'select', options: ['ZIP', 'RAR', '7Z', 'MKV', 'MP4', '其他'] },
+//       { id: 'resolution', label: '分辨率', type: 'select', options: ['720P', '1080P', '4K', '其他'] },
+//     ],
+//     5: [ // Game 游戏
+//       { id: 'platform', label: '平台', type: 'select', options: ['PC', 'PS5', 'Xbox', 'Switch', '手机', '其他'] },
+//       { id: 'genre', label: '类型', type: 'select', options: ['角色扮演', '射击', '冒险', '策略', '体育', '桌面游戏', '其他'] },
+//       { id: 'language', label: '语言', type: 'select', options: ['中文', '英文', '日文', '其他'] },
+//       { id: 'dataType', label: '数据类型', type: 'select', options: ['压缩包', '补丁', '安装包', 'nds', '其他'] },
+//     ],
+//     6: [ // 综艺
+//       { id: 'isMainland', label: '是否为大陆综艺', type: 'select', options: ['是','不是'] },
+//       { id: 'format', label: '格式', type: 'select', options: ['1080P', '4K', 'HD', '其他'] },
+//       { id: 'genre', label: '类型', type: 'select', options: ['真人秀', '选秀','访谈', '音乐', '游戏', '其他'] },
+//     ],
+//     7: [ // 学习
+//       { id: 'genre', label: '类型', type: 'select', options: ['计算机','软件','人文','外语','理工科','其他'] },
+//       { id: 'learningformat', label: '格式', type: 'select', options: ['PDF','EPUB','视频','音频','PPT','其他'] },
+//     ],
+//     8: [ // 体育
+//       { id: 'eventType', label: '赛事类型', type: 'select', options: ['足球', '篮球', '网球', '乒乓球', '羽毛球', '其他'] },
+//       { id: 'region', label: '地区', type: 'select', options: ['亚洲', '欧洲', '美洲', '其他'] },
+//     ],
+//     9: [ // 其他
+//       { id: 'otherGenre', label: '类型', type: 'select', options: othergenres },
+//     ],
+//     10: [ // 纪录片
+//       { id: 'source', label: '来源', type: 'select', options: sourceTypes },
+//       { id: 'resolution', label: '分辨率', type: 'select', options: resolutions },
+//     ],
+//     11: [ // 软件
+//       { id: 'softwsreplatform', label: '平台', type: 'select', options: softwareplatforms },
+//       { id: 'softwareGenre', label: '软件类型', type: 'select', options: softwaregenres },
+//       { id: 'softwareFormat', label: '软件格式', type: 'select', options: softwareformats },
+//     ],
+//     // 其他分类...
+//   };
+
+//   // 获取所有分类
+//   useEffect(() => {
+//     axios.get('http://localhost:8080/categories')
+//       .then(res => setCategories(res.data))
+//       .catch(err => console.error('加载分类失败', err));
+//   }, []);
+
+//   // 根据选择的分类显示不同的表单字段
+//   useEffect(() => {
+//     setFilters({}); // 清空筛选条件
+//   }, [selectedCategory]);
+
+//   const handleFilterChange = (e) => {
+//     const { name, value } = e.target;
+//     setFilters(prev => ({ ...prev, [name]: value }));
+//   };
+
+//   const filteredTorrents = (torrents) => {
+//     return torrents.filter(torrent => {
+//       if (!selectedCategory) return true; // 如果没有选择分类,显示所有
+//       if (torrent.categoryid !== parseInt(selectedCategory)) return false;
+
+//       // 根据筛选条件过滤
+//       for (const [key, value] of Object.entries(filters)) {
+//         if (value && torrent[key] !== value) {
+//           return false;
+//         }
+//       }
+//       return true;
+//     });
+//   };
+
+//   // 获取种子(根据分类筛选)
+//   useEffect(() => {
+//     let url = selectedCategory
+//       ? `http://localhost:8080/torrent/listByCategory?categoryid=${selectedCategory}`
+//       : 'http://localhost:8080/torrent/list';
+
+//     // 添加筛选条件到 URL
+//     Object.entries(filters).forEach(([key, value]) => {
+//       if (value) {
+//         url += `&${key}=${encodeURIComponent(value)}`;
+//       }
+//     });
+
+//     axios.get(url)
+//       .then(res => {
+//         setTorrents(res.data);
+//       })
+//       .catch(err => console.error('获取种子失败', err));
+//   }, [selectedCategory, filters]);
+
+//   const handleDownload = (torrentId) => {
+//     window.open(`http://localhost:8080/torrent/download/${torrentId}`, '_blank');
+//   };
+
+//   return (
+//     <div className="p-4">
+//       {/* 分类选择 */}
+//       <div className="mb-4">
+//         <label className="mr-2 font-medium">选择分类:</label>
+//         <select
+//           value={selectedCategory}
+//           onChange={(e) => setSelectedCategory(e.target.value)}
+//           className="border rounded px-2 py-1 mr-4"
+//         >
+//           <option value="">全部</option>
+//           {categories.map(cat => (
+//             <option key={cat.categoryid} value={cat.categoryid}>
+//               {cat.category_name}
+//             </option>
+//           ))}
+//         </select>
+
+//         {/* 动态渲染筛选表单 */}
+//         {selectedCategory && categoryFiltersConfig[selectedCategory] && (
+//           <div className="flex flex-wrap gap-2">
+//             {categoryFiltersConfig[selectedCategory].map(filter => {
+//               if (filter.type === 'select') {
+//                 // 根据筛选字段选择对应的选项列表
+//                 let options;
+//                 switch (filter.id) {
+//                   case 'softwareplatform':
+//                     options = softwareplatforms;
+//                     break;
+//                   case 'softwareGenre':
+//                     options = softwaregenres;
+//                     break;
+//                   case 'softwareFormat':
+//                     options = softwareformats;
+//                     break;
+//                   case 'resolution':
+//                     options = [
+//                       { value: '720p', label: '720p' },
+//                       { value: '1080p', label: '1080p' },
+//                       { value: '2K', label: '2K' },
+//                       { value: '4K', label: '4K' },
+//                       { value: '8K', label: '8K' },
+//                       { value: '其他', label: '其他' },
+//                     ];
+//                     break;
+//                   case 'region':
+//                     if (selectedCategory === '1' || selectedCategory === '2') { // Movie or TV
+//                     options = [
+//                       { value: '大陆', label: '大陆' },
+//                       { value: '港台', label: '港台' },
+//                       { value: '欧美', label: '欧美' },
+//                       { value: '日韩', label: '日韩' },
+//                       { value: '其他', label: '其他' },
+//                     ];
+//                     }
+//                     else if (selectedCategory === '8') { // Sports
+//                       options = [
+//                         { value: '亚洲', label: '亚洲' },
+//                         { value: '欧洲', label: '欧洲' },
+//                         { value: '美洲', label: '美洲' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     }
+//                     break;
+//                   case 'isMainland':
+//                     options = [ 
+//                       { value: 'true', label: '是' },
+//                       { value: 'false', label: '不是' },
+//                     ];
+//                     break;
+//                   case 'codecFormat':
+//                     options = [
+//                       { value: 'H.264', label: 'H.264' },
+//                       { value: 'H.265', label: 'H.265' },
+//                       { value: 'AV1', label: 'AV1' },
+//                       { value: 'VC1', label: 'VC1' },
+//                       { value: 'X264', label: 'X264' },
+//                       { value: '其他', label: '其他' },
+//                     ];
+//                     break;
+//                   case 'platform':
+//                     options = [
+//                       { value: 'PC', label: 'PC' },
+//                       { value: 'PS5', label: 'PS5' },
+//                       { value: 'Xbox', label: 'Xbox' },
+//                       { value: 'Switch', label: 'Switch' },
+//                       { value: '手机', label: '手机' },
+//                       { value: '其他', label: '其他' },
+//                     ];
+//                     break;
+//                   case 'language':
+//                       options = [
+//                         { value: '中文', label: '中文' },
+//                         { value: '英文', label: '英文' },
+//                         { value: '日文', label: '日文' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                       break;
+//                   case'EventType':
+//                     options = [
+//                       { value: '足球', label: '足球' },
+//                       { value: '篮球', label: '篮球' },
+//                       { value: '网球', label: '网球' },
+//                       { value: '乒乓球', label: '乒乓球' },
+//                       { value: '羽毛球', label: '羽毛球' },
+//                       { value: '其他', label: '其他' },
+//                     ];
+//                     break;
+//                   case 'genre':
+//                     if (selectedCategory === '3') { // Music
+//                       options = [
+//                         { value: '专辑', label: '专辑' },
+//                         { value: '单曲', label: '单曲' },
+//                         { value: 'EP', label: 'EP' },
+//                         { value: '现场', label: '现场' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     } else if (selectedCategory === '4') { // Anime
+//                       options = [
+//                         { value: '新番连载', label: '新番连载' },
+//                         { value: '剧场版', label: '剧场版' },
+//                         { value: 'OVA', label: 'OVA' },
+//                         { value: '完结动漫', label: '完结动漫' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     } else if (selectedCategory === '5') { // Game
+//                       options = [
+//                         { value: '角色扮演', label: '角色扮演' },
+//                         { value: '射击', label: '射击' },
+//                         { value: '冒险', label: '冒险' },
+//                         { value: '策略', label: '策略' },
+//                         { value: '体育', label: '体育' },
+//                         { value: '桌面游戏', label: '桌面游戏' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     } else if (selectedCategory === '1') { // TV
+//                       options = [
+//                         { value: '动作', label: '动作' },
+//                         { value: '喜剧', label: '喜剧' },
+//                         { value: '爱情', label: '爱情' },
+//                         { value: '科幻', label: '科幻' },
+//                         { value: '恐怖', label: '恐怖' },
+//                         { value: '动作', label: '动作' },
+//                         { value: '冒险', label: '冒险' },
+//                         { value: '历史', label: '历史' },
+//                         { value: '悬疑', label: '悬疑' },
+//                         { value: '其他', label: '其他' },
+//                       ]; 
+//                     }
+//                     else if(selectedCategory === '2') {
+//                        options = [
+//                         { value: '动作', label: '动作' },
+//                         { value: '喜剧', label: '喜剧' },
+//                         { value: '爱情', label: '爱情' },
+//                         { value: '科幻', label: '科幻' },
+//                         { value: '恐怖', label: '恐怖' },
+//                         { value: '动作', label: '动作' },
+//                         { value: '冒险', label: '冒险' },
+//                         { value: '历史', label: '历史' },
+//                         { value: '悬疑', label: '悬疑' },
+//                         { value: '其他', label: '其他' },
+//                       ]; // Movie
+//                     }
+//                     else if(selectedCategory === '6') {
+//                       options = [
+//                         { value: '真人秀', label: '真人秀' },
+//                         { value: '选秀', label: '选秀' },
+//                         { value: '访谈', label: '访谈' },
+//                         { value: '音乐', label: '音乐' },
+//                         { value: '游戏', label: '游戏' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     }
+//                     break;
+//                   case 'style':
+//                     if (selectedCategory === '3') { // Music
+//                       options = [
+//                         { value: '流行', label: '流行' },
+//                         { value: '摇滚', label: '摇滚' },
+//                         { value: '电子', label: '电子' },
+//                         { value: '古典', label: '古典' },
+//                         { value: '爵士', label: '爵士' },
+//                         { value: '民谣', label: '民谣' },
+//                         { value: '说唱', label: '说唱' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     } else {
+//                       options = []; // 根据需要定义
+//                     }
+//                     break;
+//                   case 'dataType':
+//                     if (selectedCategory === '5') { // Game
+//                       options = [
+//                         { value: '压缩包', label: '压缩包' },
+//                         { value: '补丁', label: '补丁' },
+//                         { value: '安装包', label: '安装包' },
+//                         { value: 'nds', label: 'nds' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     } else {
+//                       options = []; // 根据需要定义
+//                     }
+//                     break;
+//                   case 'format': if(selectedCategory === '4') {
+//                       options = [
+//                         { value: 'ZIP', label: 'ZIP' }, 
+//                         { value: 'RAR', label: 'RAR' },
+//                         { value: '7Z', label: '7Z' },
+//                         { value: 'MKV', label: 'MKV' },
+//                         { value: 'MP4', label: 'MP4' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     } else if(selectedCategory === '6') {
+//                       options = [
+//                         { value: '1080P', label: '1080P' },
+//                         { value: '4K', label: '4K' },
+//                         { value: 'HD', label: 'HD' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     } else if(selectedCategory === '2') {
+//                       options = [ { value: '720p', label: '720p' },
+//                                    { value: '1080p', label: '1080p' },
+//                                  { value: '2K', label: '2K' },
+//                                   { value: '4K', label: '4K' },
+//                                    { value: '8K', label: '8K' },
+//                                     { value: '其他', label: '其他' },
+//                       ];
+//                     } else {
+//                       options = []; // 根据需要定义
+//                     }
+//                     break;
+//                   case 'eventType':
+//                     if (selectedCategory === '7') { // Sports
+//                       options = [
+//                         { value: '足球', label: '足球' },
+//                         { value: '篮球', label: '篮球' },
+//                         { value: '网球', label: '网球' },
+//                         { value: '乒乓球', label: '乒乓球' },
+//                         { value: '羽毛球', label: '羽毛球' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     } else {
+//                       options = []; // 根据需要定义
+//                     }
+//                     break;
+//                   case 'source':
+//                     if (selectedCategory === '10') { // Documentary
+//                       options = [
+//                         { value: 'CCTV', label: 'CCTV' },
+//                         { value: '卫视', label: '卫视' },
+//                         { value: '国家地理', label: '国家地理' },
+//                         { value: 'BBC', label: 'BBC' },
+//                         { value: 'Discovery', label: 'Discovery' },
+//                         { value: '其他', label: '其他' },
+//                       ];
+//                     } else {
+//                       options = []; // 根据需要定义
+//                     }
+//                     break;
+//                   default:
+//                     options = []; // 默认无选项
+//                 }
+
+//                 return (
+//                   <select
+//                     key={filter.id}
+//                     name={filter.id}
+//                     value={filters[filter.id] || ''}
+//                     onChange={handleFilterChange}
+//                     className="border rounded px-2 py-1"
+//                   >
+//                     <option value="">全部</option>
+//                     {options.map(option => (
+//                       <option key={option.value} value={option.value}>
+//                         {option.label}
+//                       </option>
+//                     ))}
+//                   </select>
+//                 );
+//               }
+//               // 其他类型(如输入框)可以类似实现
+//               return null;
+//             })}
+//           </div>
+//         )}
+//       </div>
+
+//       {/* 筛选按钮 */}
+//       <button
+//         onClick={() => {
+//           // 这里可以根据需要添加额外的筛选逻辑
+//           // 例如,如果某些筛选需要联动,可以在这里处理
+//         }}
+//         className="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600 ml-4"
+//       >
+//         应用筛选
+//       </button>
+
+//       {/* 种子列表 */}
+//       <table className="w-full border-collapse mt-4">
+//         <thead>
+//           <tr className="bg-gray-200">
+//             <th className="p-2 border">名称</th>
+//             <th className="p-2 border">上传者</th>
+//             <th className="p-2 border">描述</th>
+//             <th className="p-2 border">上传时间</th>
+//             <th className="p-2 border">下载次数</th>
+//             <th className="p-2 border">促销方式</th>
+//             <th className="p-2 border">操作</th>
+//           </tr>
+//         </thead>
+//         <tbody>
+//           {torrents.map(t => (
+//             <tr key={t.torrentid} className="border-t hover:bg-gray-100">
+//               <td className="p-2 border">{t.filename}</td>
+//               <td className="p-2 border">{t.uploader_id}</td>
+//               <td className="p-2 border">{t.description}</td>
+//               <td className="p-2 border">{new Date(t.uploadTime).toLocaleString()}</td>
+//               <td className="p-2 border">{t.downloadCount}</td>
+//               <td className="p-2 border">
+//                 {(() => {
+//                   switch(t.promotionid) {
+//                     case 1: return '上传加倍';
+//                     case 2: return '下载免费';
+//                     case 3: return '下载减半';
+//                     case 0: return '没有促销';
+//                     default: return '没有促销';
+//                   }
+//                 })()}
+//               </td>
+//               <td className="p-2 border">
+//                 <button
+//                   onClick={() => handleDownload(t.torrentid)}
+//                   className="text-blue-500 hover:underline mr-2"
+//                 >
+//                   下载
+//                 </button>
+//                 <a
+//                   href={`/torrent/${t.torrentid}`}
+//                   className="text-green-600 hover:underline"
+//                 >
+//                   查看详情
+//                 </a>
+//               </td>
+//             </tr>
+//           ))}
+//         </tbody>
+//       </table>
+
+//       {/* 成功提示 */}
+//       {showSuccess && (
+//         <div className="mt-4 p-3 bg-green-100 text-green-800 border border-green-300 rounded">
+//           上传成功!
+//         </div>
+//       )}
+//     </div>
+//   );
+// }
+
+// export default TorrentList;
+import { useState, useEffect } from 'react';
+import axios from 'axios';
+
+// 常量配置集中管理
+const FILTER_OPTIONS = {
+  // 通用选项
+  common: {
+    resolution: [
+      { value: '720p', label: '720p' },
+      { value: '1080p', label: '1080p' },
+      { value: '2K', label: '2K' },
+      { value: '4K', label: '4K' },
+      { value: '8K', label: '8K' },
+      { value: '其他', label: '其他' },
+    ],
+    region: {
+      movie: [
+        { value: '大陆', label: '大陆' },
+        { value: '港台', label: '港台' },
+        { value: '欧美', label: '欧美' },
+        { value: '日韩', label: '日韩' },
+        { value: '其他', label: '其他' },
+      ],
+      variety: [
+        { value: '大陆', label: '大陆' },
+        { value: '港台', label: '港台' },
+        { value: '欧美', label: '欧美' },
+        { value: '日韩', label: '日韩' },
+        { value: '其他', label: '其他' },
+      ],
+      sports: [
+        { value: '亚洲', label: '亚洲' },
+        { value: '欧洲', label: '欧洲' },
+        { value: '美洲', label: '美洲' },
+        { value: '其他', label: '其他' },
+      ]
+    },
+    genre: {
+      movie: [
+        { value: '动作', label: '动作' },
+        { value: '喜剧', label: '喜剧' },
+        { value: '爱情', label: '爱情' },
+        { value: '科幻', label: '科幻' },
+        { value: '恐怖', label: '恐怖' },
+        { value: '冒险', label: '冒险' },
+        { value: '历史', label: '历史' },
+        { value: '悬疑', label: '悬疑' },
+        { value: '其他', label: '其他' },
+      ],
+      music: [
+        { value: '流行', label: '流行' },
+        { value: '摇滚', label: '摇滚' },
+        { value: '电子', label: '电子' },
+        { value: '古典', label: '古典' },
+        { value: '爵士', label: '爵士' },
+        { value: '民谣', label: '民谣' },
+        { value: '说唱', label: '说唱' },
+        { value: '其他', label: '其他' },
+      ],
+      anime: [
+        { value: '新番连载', label: '新番连载' },
+        { value: '剧场版', label: '剧场版' },
+        { value: 'OVA', label: 'OVA' },
+        { value: '完结动漫', label: '完结动漫' },
+        { value: '其他', label: '其他' },
+      ],
+      game: [
+        { value: '角色扮演', label: '角色扮演' },
+        { value: '射击', label: '射击' },
+        { value: '冒险', label: '冒险' },
+        { value: '策略', label: '策略' },
+        { value: '体育', label: '体育' },
+        { value: '桌面游戏', label: '桌面游戏' },
+        { value: '其他', label: '其他' },
+      ],
+      variety: [
+        { value: '真人秀', label: '真人秀' },
+        { value: '选秀', label: '选秀' },
+        { value: '访谈', label: '访谈' },
+        { value: '音乐', label: '音乐' },
+        { value: '游戏', label: '游戏' },
+        { value: '其他', label: '其他' },
+      ],
+      learning: [
+        { value: '计算机', label: '计算机' },
+        { value: '软件', label: '软件' },
+        { value: '人文', label: '人文' },
+        { value: '外语', label: '外语' },
+        { value: '理工科', label: '理工科' },
+        { value: '其他', label: '其他' },
+      ],
+      sports: [
+        { value: '足球', label: '足球' },
+        { value: '篮球', label: '篮球' },
+        { value: '网球', label: '网球' },
+        { value: '乒乓球', label: '乒乓球' },
+        { value: '羽毛球', label: '羽毛球' },
+        { value: '其他', label: '其他' },
+      ],
+      // 其他类型...
+    }
+  },
+  
+  // 分类特定选项
+  categories: {
+    1: { // 电影
+      name: '电影',
+      filters: [
+        { id: 'resolution', label: '分辨率', type: 'select' },
+        { id: 'codec_format', label: '编码格式', type: 'select', 
+          options: [
+            { value: 'H.264', label: 'H.264' },
+            { value: 'H.265', label: 'H.265' },
+            { value: 'AV1', label: 'AV1' },
+            { value: 'VC1', label: 'VC1' },
+            { value: 'X264', label: 'X264' },
+            { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'region', label: '地区', type: 'select' },
+        { id: 'genre', label: '类型', type: 'select' }
+      ]
+    },
+    2: { // 电视剧
+      name: '剧集',
+      filters: [
+        { id: 'region', label: '地区', type: 'select',
+          options: [
+            { value: '大陆', label: '大陆' },
+            { value: '港台', label: '港台' },
+            { value: '欧美', label: '欧美' },
+            { value: '日韩', label: '日韩' },
+            { value: '其他', label: '其他' },
+          ]
+         },
+        { id: 'format', label: '分辨率', type: 'select',
+          options: [
+            { value: '720p', label: '720p' },
+            { value: '1080p', label: '1080p' },
+            { value: '2K', label: '2K' },
+            { value: '4K', label: '4K' },
+            { value: '8K', label: '8K' },
+            { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'genre', label: '类型', type: 'select' ,
+          options:[
+            { value: '真人秀', label: '真人秀' },
+            { value: '选秀', label: '选秀' },
+            { value: '访谈', label: '访谈' },
+            { value: '游戏', label: '游戏' },
+            { value: '音乐', label: '音乐' },
+            { value: '其他', label: '其他' },
+          ]
+        }
+      ]
+    },
+    3: { // 音乐
+      name: '音乐',
+      filters: [
+        { id: 'genre', label: '类型', type: 'select', 
+          options: [
+            { value: '专辑', label: '专辑' },
+            { value: '单曲', label: '单曲' },
+            { value: 'EP', label: 'EP' },
+            { value: '现场', label: '现场' },
+            { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'style', label: '风格', type: 'select',
+          options: [
+            { value: '流行', label: '流行' },
+            { value: '摇滚', label: '摇滚' },
+            { value: '电子', label: '电子' },
+            { value: '古典', label: '古典' },
+            { value: '爵士', label: '爵士' },
+            { value: '民谣', label: '民谣' },
+            { value: '说唱', label: '说唱' },
+            { value: '其他', label: '其他' },
+          ]
+         },
+         {id:'format', label: '格式', type: 'select',
+          options:[
+            { value: 'MP3', label: 'MP3' },
+            { value: 'FLAC', label: 'FLAC' },
+            { value: 'WAV', label: 'WAV' },
+            { value: 'AAC', label: 'AAC' },
+            { value: 'OGG', label: 'OGG' },
+            { value: '其他', label: '其他' },
+          ]
+         }
+      ]
+    },
+    4: { // 动漫
+      name: '动漫',
+      filters: [
+        { id: 'genre', label: '类型', type: 'select' ,
+          options: [
+            { value: '新番连载', label: '新番连载' },
+            { value: '剧场版', label: '剧场版' },
+            { value: 'OVA', label: 'OVA' },
+            { value: '完结动漫', label: '完结动漫' },
+            { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'format', label: '格式', type: 'select' ,
+          options:[
+            { value: 'ZIP', label: 'ZIP' },
+    { value: 'RAR', label: 'RAR' },
+    { value: '7Z', label: '7Z' },
+    { value: 'MKV', label: 'MKV' },
+    { value: 'MP4', label: 'MP4' },
+    { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'resolution', label: '分辨率', type: 'select',
+          options:[
+             { value: '720p', label: '720p' },
+      { value: '1080p', label: '1080p' },
+      { value: '2K', label: '2K' },
+      { value: '4K', label: '4K' },
+      { value: '8K', label: '8K' },
+      { value: '其他', label: '其他' },
+          ]
+        }
+      ]
+    },
+    5: { // 游戏
+      name: '游戏',
+      filters: [
+        { id: 'platform', label: '平台', type: 'select', 
+          options: [
+            { value: 'PC', label: 'PC' },
+            { value: 'PS5', label: 'PS5' },
+            { value: 'Xbox', label: 'Xbox' },
+            { value: 'Switch', label: 'Switch' },
+            { value: '手机', label: '手机' },
+            { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'genre', label: '类型', type: 'select', 
+          options: [
+            { value: '角色扮演', label: '角色扮演' },
+            { value: '射击', label: '射击' },
+            { value: '冒险', label: '冒险' },
+            { value: '策略', label: '策略' },
+            { value: '体育', label: '体育' },
+            { value: '桌面游戏', label: '桌面游戏' },
+            { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'data_format', label: '数据类型', type: 'select' ,
+          options: [
+            { value: '压缩包', label: '压缩包' },
+            { value: '补丁', label: '补丁' },
+            { value: '安装包', label: '安装包' },
+            { value: 'nds', label: 'nds' },
+            { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'language', label: '语言', type: 'select',
+          options: [
+            { value: '中文', label: '中文' },
+            { value: '英文', label: '英文' },
+            { value: '日文', label: '日文' },
+            { value: '其他', label: '其他' },
+          ]
+         }
+      ]
+    },
+    6: { // 综艺
+      name: '综艺',
+      filters: [
+        { id: 'is_mainland', label: '是否大陆综艺', type: 'select' ,
+          options:[
+            { value: 'true', label: '是' },
+            { value: 'false', label: ' 不是' },
+          ]
+        },
+        { id: 'format', label: '分辨率', type: 'select',
+          options:[
+              { value: '720p', label: '720p' },
+      { value: '1080p', label: '1080p' },
+      { value: '2K', label: '2K' },
+      { value: '4K', label: '4K' },
+      { value: '8K', label: '8K' },
+      { value: '其他', label: '其他' },
+          ]
+        },
+        {id: 'genre', label: '类型', type: 'select',
+          options:[
+            { value: '真人秀', label: '真人秀' },
+    { value: '选秀', label: '选秀' },
+    { value: '访谈', label: '访谈' },
+    { value: '游戏', label: '游戏' },
+    { value: '音乐', label: '音乐' },
+    { value: '其他', label: '其他' },
+          ]
+        }
+      ]
+    },
+    7: { // 体育
+      name: '体育',
+      filters: [
+        { id: 'genre', label: '体育类型', type: 'select' ,
+          options:[
+             { value: '足球', label: '足球' },
+    { value: '篮球', label: '篮球' },
+    { value: '网球', label: '网球' },
+    { value: '乒乓球', label: '乒乓球' },
+    { value: '羽毛球', label: '羽毛球' },
+    { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'event_type', label: '赛事类型', type: 'select',
+          options:[
+            { value: '足球', label: '足球' },
+    { value: '篮球', label: '篮球' },
+    { value: '网球', label: '网球' },
+    { value: '乒乓球', label: '乒乓球' },
+    { value: '羽毛球', label: '羽毛球' },
+    { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'format', label: '分辨率', type: 'select',
+          options:[
+              { value: '720p', label: '720p' },
+      { value: '1080p', label: '1080p' },
+      { value: '2K', label: '2K' },
+      { value: '4K', label: '4K' },
+      { value: '8K', label: '8K' },
+      { value: '其他', label: '其他' },
+          ]
+        }
+      ]
+    },
+    8: { // 软件
+      name: '软件',
+      filters: [
+        { id: 'platform', label: '平台', type: 'select' ,
+          options:[
+            { value: 'Windows', label: 'Windows' },
+    { value: 'Mac', label: 'Mac' },
+    { value: 'Linux', label: 'Linux' },
+    { value: 'Android', label: 'Android' },
+    { value: 'iOS', label: 'iOS' },
+    { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'format', label: '格式', type: 'select',
+          options:[
+          { value: 'EXE', label: 'EXE' }, 
+    { value: 'DMG', label: 'DMG' },
+    { value: '光盘镜像', label: '光盘镜像' },
+    { value: 'APK', label: 'APK' },
+    { value: 'IPA', label: 'IPA' },
+    { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'genre', label: '类型', type: 'select',
+          options:[
+           { value: '系统软件', label: '系统软件' },
+    { value: '应用软件', label: '应用软件' },
+    { value: '游戏软件', label: '游戏软件' },
+    { value: '驱动程序', label: '驱动程序' },
+    { value: '办公软件', label: '办公软件' },
+    { value: '其他', label: '其他' },
+          ]
+        },
+      ]
+    },
+    9: { // 学习
+      name: '学习',
+      filters: [
+        { id: 'genre', label: '类型', type: 'select' ,
+          options:[
+            { value: '计算机', label: '计算机' },
+            { value: '软件', label: '软件' },
+            { value: '人文', label: '人文' },
+            { value: '外语', label: '外语' },
+            { value: '理工科', label: '理工科' },
+            { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'format', label: '格式', type: 'select',
+          options:[
+             { value: 'PDF', label: 'PDF' },
+    { value: 'EPUB', label: 'EPUB' },
+    { value: '视频', label: '视频' },
+    { value: '音频', label: '音频' },
+    { value: 'PPT', label: 'PPT' },
+    { value: '其他', label: '其他' },
+          ]
+        }
+      ]
+    },
+    10: { // 纪录片
+      name: '纪录片',
+      filters: [
+        { id: 'source', label: '视频源', type: 'select',
+          options:[
+             { value: 'CCTV', label: 'CCTV' },
+    { value: '卫视', label: '卫视' },
+    { value: '国家地理', label: '国家地理' },
+    { value: 'BBC', label: 'BBC' },
+    { value: 'Discovery', label: 'Discovery' },
+    { value: '其他', label: '其他' },
+          ]
+        },
+        { id: 'format', label: '格式', type: 'select',
+          options:[
+             { value: '720p', label: '720p' },
+    { value: '1080p', label: '1080p' },
+    { value: '2K', label: '2K' },
+    { value: '4K', label: '4K' },
+    { value: '8K', label: '8K' },
+    { value: '其他', label: '其他' },
+          ]
+        }
+      ]
+    },
+    11: { // 其他
+      name: '其他',
+      filters: [
+        { id: 'gener', label: '类型', type: 'select',
+          options:[
+           { value: '电子书', label: '电子书' },
+    { value: '视频', label: '视频' },
+    { value: 'MP3', label: 'MP3' },
+    { value: '图片', label: '图片' },
+    { value: '其他', label: '其他' },
+          ]
+        }
+      ]
+    }
+    // 其他分类配置...
+  }
+};
+
+// 获取分类筛选配置
+const getCategoryFilters = (categoryId) => {
+  const category = FILTER_OPTIONS.categories[categoryId];
+  if (!category) return [];
+  
+  return category.filters.map(filter => {
+    // 自动填充通用选项
+    if (filter.id === 'resolution' && !filter.options) {
+      return { ...filter, options: FILTER_OPTIONS.common.resolution };
+    }
+    if (filter.id === 'region' && !filter.options) {
+      const regionType = categoryId === 8 ? 'sports' : 'movie';
+      return { ...filter, options: FILTER_OPTIONS.common.region[regionType] };
+    }
+    if (filter.id === 'genre' && !filter.options) {
+      const genreType = categoryId === 3 ? 'music' : 'movie';
+      return { ...filter, options: FILTER_OPTIONS.common.genre[genreType] };
+    }
+    return filter;
+  });
+};
+
+// 格式化日期显示
+const formatDate = (dateString) => {
+  const options = { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' };
+  return new Date(dateString).toLocaleString('zh-CN', options);
+};
+
+// 获取促销方式名称
+const getPromotionName = (promotionId) => {
+  switch(promotionId) {
+    case 1: return '上传加倍';
+    case 2: return '下载免费';
+    case 3: return '下载减半';
+    default: return '没有促销';
+  }
+};
+
+function TorrentList() {
+  const [torrents, setTorrents] = useState([]);
+  const [categories, setCategories] = useState([]);
+  const [selectedCategory, setSelectedCategory] = useState('');
+  const [filters, setFilters] = useState({});
+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState(null);
+  
+  // 获取所有分类
+  useEffect(() => {
+    const fetchCategories = async () => {
+      try {
+        const res = await axios.get('http://localhost:8080/categories');
+        setCategories(res.data);
+      } catch (err) {
+        console.error('加载分类失败', err);
+        setError('加载分类失败,请稍后重试');
+      }
+    };
+    fetchCategories();
+  }, []);
+
+  // 获取种子数据
+  useEffect(() => {
+    const fetchTorrents = async () => {
+      setIsLoading(true);
+      setError(null);
+      try {
+        let url = selectedCategory
+          // ? `http://localhost:8080/torrent/listByCategory?categoryid=${selectedCategory}`
+          ? `http://localhost:8080/torrent/listByCategorywithfilter?categoryid=${selectedCategory}`
+          : 'http://localhost:8080/torrent/list';
+        
+        // 添加筛选参数
+        const params = new URLSearchParams();
+        Object.entries(filters).forEach(([key, value]) => {
+          if (value) params.append(key, value);
+        });
+        
+        const res = await axios.get(`${url}&${params.toString()}`);
+        setTorrents(res.data);
+        console.log('torrents:', torrents);
+      } catch (err) {
+        console.error('获取种子失败', err);
+        setError('获取种子列表失败,请稍后重试');
+      } finally {
+        setIsLoading(false);
+      }
+    };
+    console.log('Fetching torrents with filters:', filters);
+    console.log('Selected category:', selectedCategory);
+    console.log(torrents);
+    const timer = setTimeout(fetchTorrents, 300); // 防抖
+    return () => clearTimeout(timer);
+  }, [selectedCategory, filters]);
+
+  // 切换分类时重置筛选条件
+  const handleCategoryChange = (categoryId) => {
+    setSelectedCategory(categoryId);
+    setFilters({});
+  };
+
+  // 处理筛选条件变化
+  const handleFilterChange = (e) => {
+    const { name, value } = e.target;
+    setFilters(prev => ({ ...prev, [name]: value }));
+  };
+
+  // 下载种子
+  const handleDownload = (torrentId) => {
+    window.open(`http://localhost:8080/torrent/download/${torrentId}`, '_blank');
+  };
+
+  // 获取当前分类的筛选配置
+  const currentFilters = getCategoryFilters(selectedCategory);
+
+  return (
+    <div className="p-4 max-w-7xl mx-auto">
+      <h1 className="text-2xl font-bold mb-6">种子列表</h1>
+      
+      {/* 分类选择 */}
+      <div className="mb-6 bg-white p-4 rounded-lg shadow">
+        <div className="flex flex-wrap items-center gap-4">
+          <div className="flex items-center">
+            <label className="mr-2 font-medium whitespace-nowrap">选择分类:</label>
+            <select
+              value={selectedCategory}
+              onChange={(e) => handleCategoryChange(e.target.value)}
+              className="border rounded px-3 py-2 min-w-[150px]"
+            >
+              <option value="">全部分类</option>
+              {categories.map(cat => (
+                <option key={cat.categoryid} value={cat.categoryid}>
+                  {cat.category_name}
+                </option>
+              ))}
+            </select>
+          </div>
+
+          {/* 动态筛选表单 */}
+          {currentFilters.length > 0 && (
+            <div className="flex flex-wrap gap-4">
+              {currentFilters.map(filter => (
+                <div key={filter.id} className="flex items-center">
+                  <label className="mr-2 text-sm whitespace-nowrap">{filter.label}:</label>
+                  <select
+                    name={filter.id}
+                    value={filters[filter.id] || ''}
+                    onChange={handleFilterChange}
+                    className="border rounded px-3 py-2 min-w-[120px]"
+                  >
+                    <option value="">全部</option>
+                    {filter.options.map(option => (
+                      <option key={option.value} value={option.value}>
+                        {option.label}
+                      </option>
+                    ))}
+                  </select>
+                </div>
+              ))}
+            </div>
+          )}
+        </div>
+      </div>
+      
+
+      {/* 错误提示 */}
+      {error && (
+        <div className="mb-4 p-3 bg-red-100 text-red-700 rounded border border-red-200">
+          {error}
+        </div>
+      )}
+      
+     
+      {/* 加载状态 */}
+      {isLoading ? (
+        <div className="flex justify-center items-center h-64">
+          <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
+        </div>
+      ) : (
+        /* 种子列表 */
+        <div className="bg-white rounded-lg shadow overflow-hidden">
+          <div className="overflow-x-auto">
+            <table className="w-full">
+              <thead className="bg-gray-50">
+                <tr>
+                  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">名称</th>
+                  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">大小</th>
+                  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">上传者</th>
+                  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">上传时间</th>
+                  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">下载次数</th>
+                  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">促销</th>
+                  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
+                </tr>
+              </thead>
+              <tbody className="bg-white divide-y divide-gray-200">
+                {torrents.length > 0 ? (
+                  torrents.map(torrent => (
+                    <tr key={torrent.torrentid} className="hover:bg-gray-50">
+                      <td className="px-6 py-4 whitespace-nowrap">
+                        <div className="text-sm font-medium text-gray-900">{torrent.filename}</div>
+                        <div className="text-sm text-gray-500">{torrent.description}</div>
+                      </td>
+                      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
+                        {torrent.torrentSize} B
+                      </td>
+                      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
+                        {torrent.uploader_id}
+                      </td>
+                      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
+                        {new Date(torrent.uploadTime).toLocaleString()}
+                      </td>
+                      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
+                        {torrent.downloadCount}
+                      </td>
+                      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
+                        {getPromotionName(torrent.promotionid)}
+                      </td>
+                      <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
+                        <button
+                          onClick={() => handleDownload(torrent.torrentid)}
+                          className="text-blue-600 hover:text-blue-900 mr-4"
+                        >
+                          下载
+                        </button>
+                        <a
+                          href={`/torrent/${torrent.torrentid}`}
+                          className="text-green-600 hover:text-green-900"
+                        >
+                          详情
+                        </a>
+                      </td>
+                    </tr>
+                  ))
+                ) : (
+                  <tr>
+                    <td colSpan="7" className="px-6 py-4 text-center text-sm text-gray-500">
+                      没有找到符合条件的种子
+                    </td>
+                  </tr>
+                )}
+              </tbody>
+            </table>
+          </div>
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default TorrentList;
\ No newline at end of file
diff --git a/pt--frontend/src/components/upload.jsx b/pt--frontend/src/components/upload.jsx
new file mode 100644
index 0000000..c2cdc76
--- /dev/null
+++ b/pt--frontend/src/components/upload.jsx
@@ -0,0 +1,1461 @@
+// // import React, { useState, useEffect } from 'react';
+// // import axios from 'axios';
+
+// // function UploadTorrent() {
+// //   const [title, setTitle] = useState('');
+// //   const [description, setDescription] = useState('');
+// //   const [categoryId, setCategoryId] = useState('');
+// //   const [dpi, setDpi] = useState('');  // 可选字段
+// //   const [caption, setCaption] = useState('');  // 可选字段
+// //   const [file, setFile] = useState(null);
+// //   const [categories, setCategories] = useState([]);
+// //   const [showSuccess, setShowSuccess] = useState(false);
+
+// //   useEffect(() => {
+// //     axios.get('http://localhost:8080/categories')
+// //       .then(res => setCategories(res.data))
+// //       .catch(err => console.error('加载分类失败', err));
+// //   }, []);
+
+// //   const handleSubmit = async (e) => {
+// //     e.preventDefault();
+// //     if (!file) {
+// //       alert('请选择一个 .torrent 文件');
+// //       return;
+// //     }
+
+// //     if (!categoryId) {
+// //       alert('请选择分类');
+// //       return;
+// //     }
+
+// //     const formData = new FormData();
+// //     formData.append('file', file);
+// //     formData.append('title', title);
+// //     formData.append('description', description);
+// //     formData.append('categoryId', categoryId);
+// //     if (dpi) formData.append('dpi', dpi);  // 只有当 dpi 有值时才添加
+// //     if (caption) formData.append('caption', caption);  // 只有当 caption 有值时才添加
+
+// //     try {
+// //       await axios.post('http://localhost:8080/torrent/upload', formData, {
+// //         headers: { 'Content-Type': 'multipart/form-data' },
+// //         responseType: 'blob', // 关键:指定响应类型为 blob
+// //       });
+     
+// //       // 创建下载链接
+// //       const url = window.URL.createObjectURL(new Blob([response.data]));
+// //       const link = document.createElement('a');
+// //       link.href = url;
+// //       link.setAttribute('download', file.name); // 使用原文件名
+// //       document.body.appendChild(link);
+// //       link.click();
+// //       link.remove();
+// //       // 清空表单
+// //       setShowSuccess(true);
+// //       setTitle('');
+// //       setDescription('');
+// //       setCategoryId('');
+// //       setDpi('');
+// //       setCaption('');
+// //       setFile(null);
+// //     } catch (err) {
+// //       console.error('上传失败', err.response?.data || err.message);
+// //       alert(err.response?.data || '上传失败,请检查后端是否启动');
+// //     }
+// //   };
+
+// //   return (
+// //     <div className="max-w-xl mx-auto mt-10 p-6 bg-white shadow rounded">
+// //       <h2 className="text-2xl font-bold mb-4">上传种子</h2>
+// //       <form onSubmit={handleSubmit} className="space-y-4">
+// //         <input type="file" accept=".torrent" onChange={(e) => setFile(e.target.files[0])} />
+// //         <input
+// //           type="text"
+// //           placeholder="标题"
+// //           value={title}
+// //           onChange={(e) => setTitle(e.target.value)}
+// //           className="w-full p-2 border rounded"
+// //           required
+// //         />
+// //         <textarea
+// //           placeholder="描述"
+// //           value={description}
+// //           onChange={(e) => setDescription(e.target.value)}
+// //           className="w-full p-2 border rounded"
+// //         />
+// //         <select
+// //           value={categoryId}
+// //           onChange={(e) => setCategoryId(e.target.value)}
+// //           className="w-full p-2 border rounded"
+// //           required
+// //         >
+// //           <option value="">请选择分类</option>
+// //           {categories.map(cat => (
+// //             <option key={cat.categoryid} value={cat.categoryid}>{cat.category_name}</option>
+// //           ))}
+// //         </select>
+// //         <input
+// //           type="text"
+// //           placeholder="DPI(可选)"
+// //           value={dpi}
+// //           onChange={(e) => setDpi(e.target.value)}
+// //           className="w-full p-2 border rounded"
+// //         />
+// //         <input
+// //           type="text"
+// //           placeholder="字幕(可选)"
+// //           value={caption}
+// //           onChange={(e) => setCaption(e.target.value)}
+// //           className="w-full p-2 border rounded"
+// //         />
+// //         <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
+// //           上传
+// //         </button>
+// //       </form>
+
+// //       {showSuccess && (
+// //         <div className="mt-4 p-3 bg-green-100 text-green-800 border border-green-300 rounded">
+// //           上传成功!
+// //         </div>
+// //       )}
+// //     </div>
+// //   );
+// // }
+
+// // export default UploadTorrent;
+// import React, { useState, useEffect } from 'react';;
+// import axios from 'axios';
+
+// function UploadTorrent() {
+//   const [title, setTitle] = useState('');
+//   const [description, setDescription] = useState('');
+//   const [categoryId, setCategoryId] = useState('');
+//   const [dpi, setDpi] = useState('');
+//   const [caption, setCaption] = useState('');
+//   const [file, setFile] = useState(null);
+//   const [categories, setCategories] = useState([]);
+//   const [showSuccess, setShowSuccess] = useState(false);
+
+//   useEffect(() => {
+//     axios.get('http://localhost:8080/categories')
+//       .then(res => setCategories(res.data))
+//       .catch(err => console.error('加载分类失败', err));
+//   }, []);
+
+//   const handleSubmit = async (e) => {
+//     e.preventDefault();
+//     if (!file) {
+//       alert('请选择一个 .torrent 文件');
+//       return;
+//     }
+
+//     if (!categoryId) {
+//       alert('请选择分类');
+//       return;
+//     }
+
+//     const formData = new FormData();
+//     formData.append('file', file);
+//     formData.append('title', title);
+//     formData.append('description', description);
+//     formData.append('categoryId', categoryId);
+//     if (dpi) formData.append('dpi', dpi);
+//     if (caption) formData.append('caption', caption);
+
+//     try {
+//       // 发送上传请求
+//       const response = await axios.post('http://localhost:8080/torrent/upload', formData, {
+//         headers: { 'Content-Type': 'multipart/form-data' },
+//         responseType: 'blob', // 关键:指定响应类型为 blob
+//       });
+
+//       // 创建下载链接
+//       const url = window.URL.createObjectURL(new Blob([response.data]));
+//       const link = document.createElement('a');
+//       link.href = url;
+//       link.setAttribute('download', file.name); // 使用原文件名
+//       document.body.appendChild(link);
+//       link.click();
+//       link.remove();
+
+//       // 显示成功提示
+//       setShowSuccess(true);
+//       setTitle('');
+//       setDescription('');
+//       setCategoryId('');
+//       setDpi('');
+//       setCaption('');
+//       setFile(null);
+//     } catch (err) {
+//       console.error('上传失败', err.response?.data || err.message);
+//       alert(err.response?.data || '上传失败,请检查后端是否启动');
+//     }
+//   };
+
+//   return (
+//     <div className="max-w-xl mx-auto mt-10 p-6 bg-white shadow rounded">
+//       <h2 className="text-2xl font-bold mb-4">上传种子</h2>
+//       <form onSubmit={handleSubmit} className="space-y-4">
+//         <input type="file" accept=".torrent" onChange={(e) => setFile(e.target.files[0])} />
+//         <input
+//           type="text"
+//           placeholder="标题"
+//           value={title}
+//           onChange={(e) => setTitle(e.target.value)}
+//           className="w-full p-2 border rounded"
+//           required
+//         />
+//         <textarea
+//           placeholder="描述"
+//           value={description}
+//           onChange={(e) => setDescription(e.target.value)}
+//           className="w-full p-2 border rounded"
+//         />
+//         <select
+//           value={categoryId}
+//           onChange={(e) => setCategoryId(e.target.value)}
+//           className="w-full p-2 border rounded"
+//           required
+//         >
+//           <option value="">请选择分类</option>
+//           {categories.map(cat => (
+//             <option key={cat.categoryid} value={cat.categoryid}>{cat.category_name}</option>
+//           ))}
+//         </select>
+//         <input
+//           type="text"
+//           placeholder="DPI(可选)"
+//           value={dpi}
+//           onChange={(e) => setDpi(e.target.value)}
+//           className="w-full p-2 border rounded"
+//         />
+//         <input
+//           type="text"
+//           placeholder="字幕/说明(可选)"
+//           value={caption}
+//           onChange={(e) => setCaption(e.target.value)}
+//           className="w-full p-2 border rounded"
+//         />
+//         <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
+//           上传
+//         </button>
+//       </form>
+
+//       {showSuccess && (
+//         <div className="mt-4 p-3 bg-green-100 text-green-800 border border-green-300 rounded">
+//           上传成功!
+//         </div>
+//       )}
+//     </div>
+//   );
+// }
+
+// export default UploadTorrent;
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+
+function UploadTorrent() {
+  const [title, setTitle] = useState('');
+  const [description, setDescription] = useState('');
+  const [categoryId, setCategoryId] = useState('');
+  const [file, setFile] = useState(null);
+  const [categories, setCategories] = useState([]);
+  const [showSuccess, setShowSuccess] = useState(false);
+  
+  // 通用参数
+  const [dpi, setDpi] = useState('');
+  const [caption, setCaption] = useState('');
+  const [region, setRegion] = useState('');
+  const [year, setYear] = useState('');
+  const [genre, setGenre] = useState('');
+  const [format, setFormat] = useState('');
+  const [resolution, setResolution] = useState('');
+  
+  // 特殊参数
+  const [codecFormat, setCodecFormat] = useState('');
+  const [platform, setPlatform] = useState('');
+  const [language, setLanguage] = useState('');
+  const [eventType, setEventType] = useState('');
+  const [source, setSource] = useState('');
+  const [style, setStyle] = useState('');
+  const [dataType, setdataType] = useState('');
+  const [isMainland, setIsMainland] = useState(false);
+
+  // 根据分类显示不同的表单字段
+  const [showMovieFields, setShowMovieFields] = useState(false);
+  const [showMusicFields, setShowMusicFields] = useState(false);
+  const [showGameFields, setShowGameFields] = useState(false);
+  const [showTvFields, setShowTvFields] = useState(false);
+  const [showAnimeFields, setShowAnimeFields] = useState(false);
+  const [showlearningFields, setShowlearningFields] = useState(false);
+  const [showsoftwareFields, setShowsoftwareFields] = useState(false);
+  const [showvarietyFields, setShowvarietyFields] = useState(false);
+  const [showsportsFields, setShowsportsFields] = useState(false);
+  const [showdocFields, setShowdocFields] = useState(false);
+  const [showotherFields, setShowotherFields] = useState(false);
+  // 其他分类字段...
+  // 编码格式选项
+  const codecFormats = [
+    { value: 'H.264', label: 'H.264' },
+    { value: 'H.265', label: 'H.265' },
+    { value: 'AV1', label: 'AV1' },
+    { value: 'VP9', label: 'VP9' },
+    { value: 'VC1', label: 'VC1' },
+    { value: 'X264', label: 'X264' },
+  ];
+  const regions = [
+    { value: '大陆', label: '大陆' },
+    { value: '港台', label: '港台' },
+    { value: '欧美', label: '欧美' },
+    { value: '日韩', label: '日韩' },
+    { value: '其他', label: '其他' },
+  ];
+  const genres = [
+    { value: '动作', label: '动作' },
+    { value: '喜剧', label: '喜剧' },
+    { value: '爱情', label: '爱情' },
+    { value: '科幻', label: '科幻' },
+    { value: '恐怖', label: '恐怖' },
+    { value: '动作', label: '动作' },
+    { value: '冒险', label: '冒险' },
+    { value: '历史', label: '历史' },
+    { value: '悬疑', label: '悬疑' },
+    { value: '其他', label: '其他' },
+  ];
+  const resolutions = [
+    { value: '720p', label: '720p' },
+    { value: '1080p', label: '1080p' },
+    { value: '2K', label: '2K' },
+    { value: '4K', label: '4K' },
+    { value: '8K', label: '8K' },
+    { value: '其他', label: '其他' },
+  ];
+
+
+  const eventTypes = [
+    { value: '足球', label: '足球' },
+    { value: '篮球', label: '篮球' },
+    { value: '网球', label: '网球' },
+    { value: '乒乓球', label: '乒乓球' },
+    { value: '羽毛球', label: '羽毛球' },
+  ]
+  const styles = [
+    { value: '大陆综艺', label: '大陆综艺' },
+    { value: '日韩综艺', label: '日韩综艺' },
+    { value: '欧美综艺', label: '欧美综艺' },
+    { value: '其他', label: '其他' },
+  ]
+  const platforms = [
+    { value: 'PC', label: 'PC' },
+    { value: 'PS5', label: 'PS5' },
+    { value: 'Xbox', label: 'Xbox' },
+    { value: 'Switch', label: 'Switch' },
+    { value: '手机', label: '手机' },
+    { value: '其他', label: '其他' },
+  ]
+  const gamegenres = [
+    { value: '角色扮演', label: '角色扮演' },
+    { value: '射击', label: '射击' },
+    { value: '冒险', label: '冒险' },
+    { value: '策略', label: '策略' },
+    { value: '体育', label: '体育' },
+    { value: '桌面游戏', label: '桌面游戏'},
+    { value: '其他', label: '其他' },
+  ]
+  const dataTypes = [
+    { value: '压缩包', label: '压缩包' },
+    { value: '补丁', label: '补丁' },
+    { value: '安装包', label: '安装包' },
+    { value: 'nds', label: 'nds' },
+    { value: '其他', label: '其他' },
+  ]
+  const languages = [
+    { value: '中文', label: '中文' },
+    { value: '英文', label: '英文' },
+    { value: '日文', label: '日文' },
+    { value: '其他', label: '其他' },
+  ]
+  const musicgenres = [
+    { value: '专辑', label: '专辑' },
+    { value: '单曲', label: '单曲' },
+    { value: 'EP', label: 'EP' },
+    { value: '现场', label: '现场' },
+    { value: '其他', label: '其他' },
+  ]
+  const musicstyles = [
+    { value: '流行', label: '流行' },
+    { value: '摇滚', label: '摇滚' },
+    { value: '电子', label: '电子' },
+    { value: '古典', label: '古典' },
+    { value: '爵士', label: '爵士' },
+    { value: '民谣', label: '民谣' },
+    { value: '说唱', label: '说唱' },
+    { value: '其他', label: '其他' },
+  ]
+  const musicformats = [
+    { value: 'MP3', label: 'MP3' },
+    { value: 'FLAC', label: 'FLAC' },
+    { value: 'WAV', label: 'WAV' },
+    { value: 'AAC', label: 'AAC' },
+    { value: 'OGG', label: 'OGG' },
+    { value: '其他', label: '其他' },
+  ]
+  const anigenres = [
+    { value: '新番连载', label: '新番连载' },
+    { value: '剧场版', label: '剧场版' },
+    { value: 'OVA', label: 'OVA' },
+    { value: '完结动漫', label: '完结动漫' },
+    { value: '其他', label: '其他' },
+  ]
+  const animeformats = [
+    { value: 'ZIP', label: 'ZIP' },
+    { value: 'RAR', label: 'RAR' },
+    { value: '7Z', label: '7Z' },
+    { value: 'MKV', label: 'MKV' },
+    { value: 'MP4', label: 'MP4' },
+    { value: '其他', label: '其他' },
+  ]
+  const varietygenres = [
+    { value: '真人秀', label: '真人秀' },
+    { value: '选秀', label: '选秀' },
+    { value: '访谈', label: '访谈' },
+    { value: '游戏', label: '游戏' },
+    { value: '音乐', label: '音乐' },
+    { value: '其他', label: '其他' },
+  ]
+  const sportsgenres = [
+    { value: '足球', label: '足球' },
+    { value: '篮球', label: '篮球' },
+    { value: '网球', label: '网球' },
+    { value: '乒乓球', label: '乒乓球' },
+    { value: '羽毛球', label: '羽毛球' },
+    { value: '其他', label: '其他' },
+  ]
+  const softwaregenres = [
+    { value: '系统软件', label: '系统软件' },
+    { value: '应用软件', label: '应用软件' },
+    { value: '游戏软件', label: '游戏软件' },
+    { value: '驱动程序', label: '驱动程序' },
+    { value: '办公软件', label: '办公软件' },
+    { value: '其他', label: '其他' },
+  ]
+  const softwareplatforms = [
+    { value: 'Windows', label: 'Windows' },
+    { value: 'Mac', label: 'Mac' },
+    { value: 'Linux', label: 'Linux' },
+    { value: 'Android', label: 'Android' },
+    { value: 'iOS', label: 'iOS' },
+    { value: '其他', label: '其他' },
+  ]
+  const softwareformats = [
+    { value: 'EXE', label: 'EXE' }, 
+    { value: 'DMG', label: 'DMG' },
+    { value: '光盘镜像', label: '光盘镜像' },
+    { value: 'APK', label: 'APK' },
+    { value: 'IPA', label: 'IPA' },
+    { value: '其他', label: '其他' },
+  ]
+  const learninggenres = [
+    { value: '计算机', label: '计算机' },
+    { value: '软件', label: '软件' },
+    { value: '人文', label: '人文' },
+    { value: '外语', label: '外语' },
+    { value: '理工类', label: '理工类' },
+    { value: '其他', label: '其他' },
+  ]
+  const learningformats = [
+    { value: 'PDF', label: 'PDF' },
+    { value: 'EPUB', label: 'EPUB' },
+    { value: '视频', label: '视频' },
+    { value: '音频', label: '音频' },
+    { value: 'PPT', label: 'PPT' },
+    { value: '其他', label: '其他' },
+  ]
+  const sourceTypes = [
+    { value: 'CCTV', label: 'CCTV' },
+    { value: '卫视', label: '卫视' },
+    { value: '国家地理', label: '国家地理' },
+    { value: 'BBC', label: 'BBC' },
+    { value: 'Discovery', label: 'Discovery' },
+    { value: '其他', label: '其他' },
+  ]
+  const othergenres = [
+    { value: '电子书', label: '电子书' },
+    { value: '视频', label: '视频' },
+    { value: 'MP3', label: 'MP3' },
+    { value: '图片', label: '图片' },
+    { value: '其他', label: '其他' },
+  ]
+  useEffect(() => {
+    axios.get('http://localhost:8080/categories')
+      .then(res => setCategories(res.data))
+      .catch(err => console.error('加载分类失败', err));
+  }, []);
+
+  // 根据选择的分类显示不同的表单字段
+  useEffect(() => {
+    setShowMovieFields(categoryId === '1');
+    setShowMusicFields(categoryId === '3');
+    setShowGameFields(categoryId === '5');
+    setShowTvFields(categoryId === '2');
+    setShowAnimeFields(categoryId === '4');
+    setShowlearningFields(categoryId === '9');
+    setShowsoftwareFields(categoryId === '8');
+    setShowvarietyFields(categoryId === '6');
+    setShowsportsFields(categoryId === '7');
+    setShowdocFields(categoryId === '10');
+    setShowotherFields(categoryId === '11');
+    // 其他分类...
+  }, [categoryId]);
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+    if (!file) {
+      alert('请选择一个 .torrent 文件');
+      return;
+    }
+
+    if (!categoryId) {
+      alert('请选择分类');
+      return;
+    }
+
+    const formData = new FormData();
+    formData.append('file', file);
+    formData.append('title', title);
+    formData.append('description', description);
+    formData.append('categoryId', categoryId);
+    
+    // 通用参数
+    if (dpi) formData.append('dpi', dpi);
+    if (caption) formData.append('caption', caption);
+    if (region) formData.append('region', region);
+    if (year) formData.append('year', year);
+    if (genre) formData.append('genre', genre);
+    if (format) formData.append('format', format);
+    if (resolution) formData.append('resolution', resolution);
+    
+    // 特殊参数
+    if (codecFormat) formData.append('codecFormat', codecFormat);
+    if (platform) formData.append('platform', platform);
+    if (language) formData.append('language', language);
+    if (eventType) formData.append('eventType', eventType);
+    if (source) formData.append('source', source);
+    if (style) formData.append('style', style);
+    if (dataType) formData.append('dataType', dataType);
+    formData.append('isMainland', isMainland.toString());
+
+    try {
+      const response = await axios.post('http://localhost:8080/torrent/upload', formData, {
+        headers: { 'Content-Type': 'multipart/form-data' },
+        responseType: 'blob',
+      });
+
+      // 创建下载链接
+      const url = window.URL.createObjectURL(new Blob([response.data]));
+      const link = document.createElement('a');
+      link.href = url;
+      link.setAttribute('download', file.name);
+      document.body.appendChild(link);
+      link.click();
+      link.remove();
+
+      // 显示成功提示
+      setShowSuccess(true);
+      // 清空表单
+      setTitle('');
+      setDescription('');
+      setCategoryId('');
+      setFile(null);
+      // 清空其他字段...
+    } catch (err) {
+      console.error('上传失败', err.response?.data || err.message);
+      alert(err.response?.data || '上传失败,请检查后端是否启动');
+    }
+  };
+
+  return (
+    <div className="max-w-xl mx-auto mt-10 p-6 bg-white shadow rounded">
+      <h2 className="text-2xl font-bold mb-4">上传种子</h2>
+      <form onSubmit={handleSubmit} className="space-y-4">
+        <div>
+          <label className="block mb-1">种子文件 (.torrent)</label>
+          <input 
+            type="file" 
+            accept=".torrent" 
+            onChange={(e) => setFile(e.target.files[0])} 
+            className="w-full p-2 border rounded"
+            required
+          />
+        </div>
+
+        <div>
+          <label className="block mb-1">标题</label>
+          <input
+            type="text"
+            placeholder="标题"
+            value={title}
+            onChange={(e) => setTitle(e.target.value)}
+            className="w-full p-2 border rounded"
+            required
+          />
+        </div>
+
+        <div>
+          <label className="block mb-1">描述</label>
+          <textarea
+            placeholder="描述"
+            value={description}
+            onChange={(e) => setDescription(e.target.value)}
+            className="w-full p-2 border rounded"
+          />
+        </div>
+
+        <div>
+          <label className="block mb-1">分类</label>
+          <select
+            value={categoryId}
+            onChange={(e) => setCategoryId(e.target.value)}
+            className="w-full p-2 border rounded"
+            required
+          >
+            <option value="">请选择分类</option>
+            {categories.map(cat => (
+              <option key={cat.categoryid} value={cat.categoryid}>{cat.category_name}</option>
+            ))}
+          </select>
+        </div>
+
+        {/* <div>
+          <label className="block mb-1">DPI(可选)</label>
+          <input
+            type="text"
+            placeholder="DPI"
+            value={dpi}
+            onChange={(e) => setDpi(e.target.value)}
+            className="w-full p-2 border rounded"
+          />
+        </div>
+
+        <div>
+          <label className="block mb-1">字幕/说明(可选)</label>
+          <input
+            type="text"
+            placeholder="字幕/说明"
+            value={caption}
+            onChange={(e) => setCaption(e.target.value)}
+            className="w-full p-2 border rounded"
+          />
+        </div> */}
+
+        {/* 电影相关字段 */}
+        {showMovieFields && (
+        <>
+        <div>
+          <label className="block mb-1">字幕/说明</label>
+          <input
+            type="text"
+            placeholder="字幕/说明"
+            value={caption}
+            onChange={(e) => setCaption(e.target.value)}
+            className="w-full p-2 border rounded"
+          />
+        </div>
+        <div>
+          <label className="block mb-1">地区</label>
+          <select
+            value={region}
+            onChange={(e) => setRegion(e.target.value)}
+            className="w-full p-2 border rounded"
+          >
+            <option value="">请选择地区</option>
+            {regions.map((regions) => (
+              <option key={regions.value} value={regions.value}>
+                {regions.label}
+              </option>
+            ))}
+          </select>
+        </div>
+          {/* <input
+            type="text"
+            placeholder="region"
+            value={region}
+            onChange={(e) => setRegion(e.target.value)}
+            className="w-full p-2 border rounded"
+          />
+        </div> */}
+
+        <div>
+          <label className="block mb-1">年份</label>
+          <input
+            type="text"
+            placeholder="年份"
+            value={year}
+            onChange={(e) => setYear(e.target.value)}
+            className="w-full p-2 border rounded"
+          />
+        </div>
+        <div>
+          <label className="block mb-1">类型</label>
+          {/* <input
+            type="text"
+            placeholder="类型"
+            value={genre}
+            onChange={(e) => setGenre(e.target.value)}
+            className="w-full p-2 border rounded"
+          /> */}
+          <select
+              value={genre}
+              onChange={(e) => setGenre(e.target.value)}
+              className="w-full p-2 border rounded"
+             >
+              <option value="">请选择类型</option>
+                {genres.map((format) => (
+               <option key={format.value} value={format.value}>
+                {format.label}
+               </option>
+                ))}
+               </select>
+        </div>
+            {/* <div>
+              <label className="block mb-1">编码格式</label>
+              <input
+                type="text"
+                placeholder="如 H.264, H.265"
+                value={codecFormat}
+                onChange={(e) => setCodecFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              />
+            </div> */}
+            <div>
+           <label className="block mb-1">编码格式</label>
+            <select
+              value={codecFormat}
+              onChange={(e) => setCodecFormat(e.target.value)}
+              className="w-full p-2 border rounded"
+             >
+              <option value="">请选择编码格式</option>
+                {codecFormats.map((format) => (
+               <option key={format.value} value={format.value}>
+                {format.label}
+               </option>
+                ))}
+               </select>
+            </div>
+            <div>
+              <label className="block mb-1">分辨率</label>
+              <select
+                value={resolution}
+                onChange={(e) => setResolution(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择分辨率</option>
+                {resolutions.map((resolution) => (
+                  <option key={resolution.value} value={resolution.value}>
+                    {resolution.label}
+                  </option>
+                ))}
+              </select>
+{/*               
+              <input
+                type="text"
+                placeholder="如 1080p, 4K"
+                value={resolution}
+                onChange={(e) => setResolution(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+          </>
+        )}
+        {/*剧集相关字段 */}
+        {showTvFields && (
+          <>
+            <div>
+              <label className="block mb-1">地区</label>
+              <select
+                value={region}
+                onChange={(e) => setRegion(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择地区</option>
+                {regions.map((regions) => (
+                  <option key={regions.value} value={regions.value}>
+                    {regions.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如 中国, 美国"
+                value={region}
+                onChange={(e) => setRegion(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+            <label className="block mb-1">格式</label>
+            <select
+              value={format}
+              onChange={(e) => setFormat(e.target.value)}
+              className="w-full p-2 border rounded"       
+            >
+              <option value="">请选择格式</option>
+              {resolutions.map((format) => (
+                <option key={format.value} value={format.value}>
+                  {format.label}
+                </option>
+              ))}
+            </select>
+{/*         <input
+              type="text"
+              placeholder="如1080P, 4K"
+              value={format}
+              onChange={(e) => setFormat(e.target.value)}
+              className="w-full p-2 border rounded"
+            /> */}
+          </div>
+          <div>
+          <label className="block mb-1">类型</label>
+          <select
+            value={genre}
+            onChange={(e) => setGenre(e.target.value)}
+            className="w-full p-2 border rounded"
+          >
+            <option value="">请选择类型</option>
+            {genres.map((genre) => (
+              <option key={genre.value} value={genre.value}>
+                {genre.label}
+              </option>
+            ))}
+          </select>
+{/*       <input
+            type="text"
+            placeholder="类型"
+            value={genre}
+            onChange={(e) => setGenre(e.target.value)}
+            className="w-full p-2 border rounded"
+          /> */}
+        </div>
+       </>
+        )}
+
+        {/* 游戏相关字段 */}
+        {showGameFields && (
+          <>
+            <div>
+              <label className="block mb-1">平台</label>
+              <select
+                value={platform}
+                onChange={(e) => setPlatform(e.target.value)} 
+                className="w-full p-2 border rounded" 
+              >
+                <option value="">请选择平台</option>
+                {platforms.map((platform) => (
+                  <option key={platform.value} value={platform.value}>
+                    {platform.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+              <input
+                type="text"
+                placeholder="如 PC, PS5"
+                value={platform}
+                onChange={(e) => setPlatform(e.target.value)}
+                className="w-full p-2 border rounded"
+              />*/}
+            </div>
+             <div>
+          <label className="block mb-1">类型</label>
+          <select
+            value={genre}
+            onChange={(e) => setGenre(e.target.value)}
+            className="w-full p-2 border rounded"
+          >
+            <option value="">请选择类型</option>  
+            {gamegenres.map((genre) => (
+              <option key={genre.value} value={genre.value}>
+                {genre.label}
+              </option>
+            ))}
+          </select>
+          {/* <input
+            type="text"
+            placeholder="类型"
+            value={genre}
+            onChange={(e) => setGenre(e.target.value)}
+            className="w-full p-2 border rounded"
+          /> */}
+        </div>
+            <div>
+              <label className="block mb-1">语言</label>
+              <select
+                value={language}
+                onChange={(e) => setLanguage(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择语言</option>
+                {languages.map((language) => (
+                  <option key={language.value} value={language.value}>
+                    {language.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如 中文, 英文"
+                value={language}
+                onChange={(e) => setLanguage(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+             <div>
+              <label className="block mb-1">数据类型</label>
+              <select
+                value={dataType}
+                onChange={(e) => setdataType(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择数据类型</option>
+                {dataTypes.map((dataType) => (
+                  <option key={dataType.value} value={dataType.value}>
+                    {dataType.label}
+                  </option>
+                ))}
+              </select>
+{/*             <input
+                type="text"
+                placeholder="如压缩包,补丁"
+                value={dataType}
+                onChange={(e) => setdataType(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+          </>
+        )}
+
+        {/* 综艺相关字段 */}
+        {showvarietyFields && (
+          <>
+          <div>
+            <label className="flex items-center">
+              <input
+                type="checkbox"
+                checked={isMainland}
+                onChange={(e) => setIsMainland(e.target.checked)}
+                className="mr-2"
+              />
+              是否大陆综艺
+            </label>
+          </div>
+          <div>
+          <label className="block mb-1">类型</label>
+          <select
+            value={style}
+            onChange={(e) => setStyle(e.target.value)}
+            className="w-full p-2 border rounded"
+          >
+            <option value="">请选择类型</option>
+            {varietygenres.map((style) => (
+              <option key={style.value} value={style.value}>
+                {style.label}
+              </option>
+            ))}
+          </select>
+          {/* <input
+            type="text"
+            placeholder="类型"
+            value={genre}
+            onChange={(e) => setGenre(e.target.value)}
+            className="w-full p-2 border rounded"
+          /> */}
+        </div>
+          <div>
+            <label className="block mb-1">格式</label>
+            <select
+              value={format}
+              onChange={(e) => setFormat(e.target.value)}
+              className="w-full p-2 border rounded"
+            >
+              <option value="">请选择格式</option>
+              {resolutions.map((format) => (
+                <option key={format.value} value={format.value}>
+                  {format.label}
+                </option>
+              ))}
+            </select>
+            {/* <input
+              type="text"
+              placeholder="如1080P, 4K"
+              value={format}
+              onChange={(e) => setFormat(e.target.value)}
+              className="w-full p-2 border rounded"
+            /> */}
+          </div>
+          </>
+        )}
+        {/* 动漫相关字段 */}
+        {showAnimeFields && (
+          <>
+            <div>
+              <label className="block mb-1">类型</label>
+              <select
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择类型</option>
+                {anigenres.map((genre) => (
+                  <option key={genre.value} value={genre.value}>
+                    {genre.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如治愈, 热血"
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+              <label className="block mb-1">格式</label>
+              <select
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择格式</option>
+                {animeformats.map((format) => (
+                  <option key={format.value} value={format.value}>
+                    {format.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如1080P, 4K"
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+              <label className="block mb-1">分辨率</label>
+              <select
+                value={resolution}
+                onChange={(e) => setResolution(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择分辨率</option>
+                {resolutions.map((resolution) => (
+                  <option key={resolution.value} value={resolution.value}>
+                    {resolution.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如 1080p, 4K"
+                value={resolution}
+                onChange={(e) => setResolution(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+          </>
+        )}
+        {/* 学习相关字段 */}
+        {showlearningFields && (
+          <>
+            <div>
+              <label className="block mb-1">类型</label>
+              <select
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择类型</option>
+                {learninggenres.map((genre) => (
+                  <option key={genre.value} value={genre.value}>
+                    {genre.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如 课程, 讲座"
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+           <div>
+              <label className="block mb-1">格式</label>
+              <select
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择格式</option>
+                {learningformats.map((format) => (
+                  <option key={format.value} value={format.value}>
+                    {format.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如1080P, 4K"
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+          </>
+        )}
+        {/* 软件相关字段 */}
+        {showsoftwareFields && (
+          <>
+            <div>
+              <label className="block mb-1">平台</label>
+              <select
+                value={platform}
+                onChange={(e) => setPlatform(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择平台</option>
+                {softwareplatforms.map((platform) => (
+                  <option key={platform.value} value={platform.value}>
+                    {platform.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如 Windows, Mac"
+                value={platform}
+                onChange={(e) => setPlatform(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+             <div>
+              <label className="block mb-1">类型</label>
+              <select
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择类型</option>
+                {softwaregenres.map((genre) => (
+                  <option key={genre.value} value={genre.value}>
+                    {genre.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如 学习,办公"
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+              <label className="block mb-1">格式</label>
+              <select
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择格式</option>
+                {softwareformats.map((format) => (
+                  <option key={format.value} value={format.value}>
+                    {format.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如 ZIP, EXE"
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+          </>
+        )}
+        {/* 体育相关字段 */}
+        {showsportsFields && (
+          <>
+            <div>
+              <label className="block mb-1">类型</label>
+              <select
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择类型</option>
+                {sportsgenres.map((genre) => (
+                  <option key={genre.value} value={genre.value}>
+                    {genre.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如 比赛, 训练"
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+              <label className="block mb-1">格式</label>
+              <select
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择格式</option>
+                {resolutions.map((format) => (
+                  <option key={format.value} value={format.value}>
+                    {format.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如1080P, 4K"
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+              <label className="block mb-1">赛事类型</label>
+              <select
+                value={eventType}
+                onChange={(e) => setEventType(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择赛事类型</option>
+                {eventTypes.map((eventType) => (
+                  <option key={eventType.value} value={eventType.value}>
+                    {eventType.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如足球、篮球"
+                value={eventType}
+                onChange={(e) => setEventType(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+          </>
+        )}
+        {/* 纪录片相关字段 */}
+        {showdocFields && (
+          <>
+            <div>
+              <label className="block mb-1">年份</label>
+              <input
+                type="text"
+                placeholder="如 1999, 2020"
+                value={year}
+                onChange={(e) => setYear(e.target.value)}
+                className="w-full p-2 border rounded"
+              />
+            </div>
+            <div>
+              <label className="block mb-1">视频源</label>
+              <select
+                value={source}
+                onChange={(e) => setSource(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择视频源</option>
+                {sourceTypes.map((source) => (
+                  <option key={source.value} value={source.value}>
+                    {source.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如BlibliBili, YouTube"
+                value={source}
+                onChange={(e) => setSource(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+              <label className="block mb-1">格式</label>
+              <select
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择格式</option>
+                {resolutions.map((format) => (
+                  <option key={format.value} value={format.value}>
+                    {format.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如1080P, 4K"
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+          </>
+        )}
+        {/*音乐相关字段 */}
+        {showMusicFields && (
+          <>
+            <div>
+              <label className="block mb-1">类型</label>
+              <select
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择类型</option>
+                {musicgenres.map((genre) => (
+                  <option key={genre.value} value={genre.value}>
+                    {genre.label}
+                  </option>
+                ))}
+              </select>
+{/*               
+              <input
+                type="text"
+                placeholder="如专辑、单曲"
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+              <label className="block mb-1">地区</label>
+              <select
+                value={region}
+                onChange={(e) => setRegion(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择地区</option>
+                {regions.map((regions) => (
+                  <option key={regions.value} value={regions.value}>
+                    {regions.label}
+                  </option>
+                ))}
+              </select>
+{/*           <input
+                type="text"
+                placeholder="如 中国, 美国"
+                value={region}
+                onChange={(e) => setRegion(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+              <label className="block mb-1">风格</label>
+              <select
+                value={style}
+                onChange={(e) => setStyle(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择风格</option>
+                {musicstyles.map((style) => (
+                  <option key={style.value} value={style.value}>
+                    {style.label}
+                  </option>
+                ))}
+                </select>
+              {/* <input
+                type="text"
+                placeholder="如流行、摇滚"
+                value={style}
+                onChange={(e) => setStyle(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+            <div>
+              <label className="block mb-1">格式</label>
+              <select
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择格式</option>
+                {musicformats.map((format) => (
+                  <option key={format.value} value={format.value}>
+                    {format.label}
+                  </option>
+                ))}
+              </select>
+{/*           <input
+                type="text"
+                placeholder="如MP3, FLAC"
+                value={format}
+                onChange={(e) => setFormat(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+          </>
+        )}
+        {/* 其他分类字段 */}
+        {showotherFields && (
+          <>
+            <div>
+              <label className="block mb-1">类型</label>
+              <select
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              >
+                <option value="">请选择类型</option>
+                {othergenres.map((genre) => (
+                  <option key={genre.value} value={genre.value}>
+                    {genre.label}
+                  </option>
+                ))}
+              </select>
+              {/* <input
+                type="text"
+                placeholder="如视频、音频"
+                value={genre}
+                onChange={(e) => setGenre(e.target.value)}
+                className="w-full p-2 border rounded"
+              /> */}
+            </div>
+          </>
+        )}
+
+        {/* 其他分类字段... */}
+        {/* 提交按钮 */}
+
+        <button 
+          type="submit" 
+          className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
+        >
+          上传
+        </button>
+      </form>
+
+      {showSuccess && (
+        <div className="mt-4 p-3 bg-green-100 text-green-800 border border-green-300 rounded">
+          上传成功!
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default UploadTorrent;
\ No newline at end of file
diff --git a/pt--frontend/src/index.css b/pt--frontend/src/index.css
new file mode 100644
index 0000000..08a3ac9
--- /dev/null
+++ b/pt--frontend/src/index.css
@@ -0,0 +1,68 @@
+:root {
+  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}
diff --git a/pt--frontend/src/main.jsx b/pt--frontend/src/main.jsx
new file mode 100644
index 0000000..055899d
--- /dev/null
+++ b/pt--frontend/src/main.jsx
@@ -0,0 +1,15 @@
+// src/main.jsx
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import App from './App';
+
+import './index.css'; // 你的全局样式(如果有)
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+  <React.StrictMode>
+    <BrowserRouter>
+      <App />
+    </BrowserRouter>
+  </React.StrictMode>
+);
diff --git a/pt--frontend/src/pages/Home.jsx b/pt--frontend/src/pages/Home.jsx
new file mode 100644
index 0000000..2e3aa80
--- /dev/null
+++ b/pt--frontend/src/pages/Home.jsx
@@ -0,0 +1,146 @@
+// // src/pages/Home.jsx
+// import React from 'react';
+// import TorrentList from '../components/torrentlist';
+// import { Link } from 'react-router-dom';
+
+// const Home = () => {
+//   return (
+//     <div className="min-h-screen bg-gray-100 p-4">
+//       <div className="flex justify-between items-center mb-4">
+//         <h1 className="text-2xl font-bold">种子列表</h1>
+//         <Link to="/upload" className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
+//           上传种子
+//         </Link>
+//       </div>
+//       <TorrentList />
+//     </div>
+//   );
+// };
+
+// export default Home;
+import React, { useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import TorrentList from '../components/torrentlist';
+import Post from '../components/Post';
+import FriendManager from '../components/FriendManager';
+import ChatBox from '../components/ChatBox';
+import RequestBoard from '../components/RequestBoard';
+import { getActivityPreviews, getFullActivities } from '../api/activity';
+
+const Home = () => {
+  const currentUser = {
+    id: 1,
+    username: '测试用户',
+  };
+
+  const [selectedRelation, setSelectedRelation] = useState(null);
+  const [activityPreviews, setActivityPreviews] = useState([]);
+  const [fullActivities, setFullActivities] = useState([]);
+  const [selectedActivityId, setSelectedActivityId] = useState(null);
+
+  useEffect(() => {
+    getActivityPreviews().then(res => setActivityPreviews(res.data));
+    getFullActivities().then(res => setFullActivities(res.data));
+  }, []);
+
+  const selectedActivity = fullActivities.find(
+    activity => activity.activityid === selectedActivityId
+  );
+
+  return (
+    <div className="min-h-screen bg-gray-100 p-6">
+      <div className="flex justify-between items-center mb-6">
+        <h1 className="text-3xl font-bold">Pt站</h1>
+        <Link to="/upload" className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
+          上传种子
+        </Link>
+      </div>
+
+      {/* 种子列表区域 */}
+      <div className="bg-white p-4 rounded shadow mb-8">
+        <h2 className="text-xl font-semibold mb-4">种子列表</h2>
+        <TorrentList />
+      </div>
+
+      {/* 活动区域 */}
+      <div className="bg-white p-4 rounded shadow mb-8">
+        <h2 className="text-xl font-semibold mb-4">活动预览</h2>
+        {!selectedActivity ? (
+          <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
+            {activityPreviews.map(activity => (
+              <div key={activity.activityid} className="border p-3 rounded shadow">
+                <h3 className="text-lg font-medium mb-2">{activity.title}</h3>
+                <img
+                  src={activity.photo}
+                  alt={activity.title}
+                  className="w-full h-40 object-cover mb-2 rounded"
+                />
+                <button
+                  className="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600"
+                  onClick={() => setSelectedActivityId(activity.activityid)}
+                >
+                  查看详情
+                </button>
+              </div>
+            ))}
+          </div>
+        ) : (
+          <div className="p-4 border rounded shadow">
+            <button
+              className="mb-4 text-blue-600 underline"
+              onClick={() => setSelectedActivityId(null)}
+            >
+              ← 返回列表
+            </button>
+            <h3 className="text-2xl font-bold mb-2">{selectedActivity.title}</h3>
+            <img
+              src={selectedActivity.photo}
+              alt={selectedActivity.title}
+              className="w-full h-60 object-cover rounded mb-4"
+            />
+            <p className="mb-2"><strong>内容:</strong>{selectedActivity.content}</p>
+            <p className="mb-2"><strong>时间:</strong>{selectedActivity.time}</p>
+            <p className="mb-2"><strong>奖励:</strong>{selectedActivity.award}</p>
+          </div>
+        )}
+      </div>
+
+      {/* 主内容区 */}
+      <div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
+        {/* 帖子区域 */}
+        <div className="bg-white p-4 rounded shadow xl:col-span-1">
+          <h2 className="text-xl font-semibold mb-4">最新帖子</h2>
+          <Post />
+        </div>
+
+        {/* 好友管理区 */}
+        <div className="bg-white p-4 rounded shadow xl:col-span-1">
+          <h2 className="text-xl font-semibold mb-4">好友管理</h2>
+          <FriendManager
+            currentUser={currentUser}
+            onSelectRelation={setSelectedRelation}
+          />
+        </div>
+
+        {/* 聊天窗口 */}
+        {selectedRelation && (
+          <div className="bg-white p-4 rounded shadow xl:col-span-1">
+            <h2 className="text-xl font-semibold mb-4">聊天窗口</h2>
+            <ChatBox
+              senderId={currentUser.id}
+              receiverId={selectedRelation.friendId}
+            />
+          </div>
+        )}
+      </div>
+
+      {/* 求助帖区域 */}
+      <div className="bg-white p-4 mt-8 rounded shadow">
+        <h2 className="text-xl font-semibold mb-4">求助帖管理</h2>
+        <RequestBoard currentUserId={currentUser.id} />
+      </div>
+    </div>
+  );
+};
+
+export default Home;
\ No newline at end of file
diff --git a/pt--frontend/src/pages/Torrentdetail.jsx b/pt--frontend/src/pages/Torrentdetail.jsx
new file mode 100644
index 0000000..9aa9ba0
--- /dev/null
+++ b/pt--frontend/src/pages/Torrentdetail.jsx
@@ -0,0 +1,234 @@
+// // src/pages/TorrentDetail.jsx
+// import React, { useEffect, useState } from 'react';
+// import { useParams } from 'react-router-dom';
+// import axios from 'axios';
+
+// const TorrentDetail = () => {
+//   const { id } = useParams(); // 获取 URL 中的 torrentid
+//   const [torrent, setTorrent] = useState(null);
+//   const [seeders, setSeeders] = useState([]);
+//   const [loading, setLoading] = useState(true);
+//   useEffect(() => {
+//     console.log("Received Torrent:", torrent);
+// }, [torrent]);
+//   useEffect(() => {
+//     axios.get(`http://localhost:8080/torrent/${id}`)
+//       .then(res => setTorrent(res.data))
+//       setTorrent(res.data);
+//       return axios.get(`http://localhost:8080/torrent/${res.data.infoHash}/seeders`);
+// }).then(res => 
+//         setSeeders(res.data))
+//         .catch(err => {
+//           console.error("Error fetching torrent details:", err)
+//           .finally(() => setLoading(false));
+//           },[id]);
+
+//   if (!torrent) return <div className="p-4">加载中...</div>;
+
+//   // 格式化文件大小
+//   const formatSize = (bytes) => {
+//     if (bytes === 0) return '0 B';
+//     const k = 1024;
+//     const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+//     const i = Math.floor(Math.log(bytes) / Math.log(k));
+//     return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+//   };
+
+//   // 格式化速度
+//   const formatSpeed = (bytesPerSec) => {
+//     if (bytesPerSec < 1024) return bytesPerSec.toFixed(2) + ' B/s';
+//     if (bytesPerSec < 1024 * 1024) return (bytesPerSec / 1024).toFixed(2) + ' KB/s';
+//     return (bytesPerSec / (1024 * 1024)).toFixed(2) + ' MB/s';
+//   };
+//   return (
+//     <div className="max-w-2xl mx-auto p-6 bg-white shadow rounded mt-6">
+//       <h1 className="text-2xl font-bold mb-4">{torrent.torrentTitle}</h1>
+      
+//       <div className="space-y-2 text-gray-700">
+//         <p><strong>简介:</strong>{torrent.description}</p>
+//         {/* 修复上传人显示 */}
+//         <p><strong>上传人:</strong>{torrent.uploader_id !== undefined ? torrent.uploader_id : '未知用户'}</p>
+//         <p><strong>上传时间:</strong>{new Date(torrent.uploadTime).toLocaleString()}</p>
+//         <p><strong>下载数:</strong>{torrent.downloadCount}</p>
+//         <p><strong>文件大小:</strong>{torrent.torrentSize} B</p>
+//         <p><strong>文件分辨率:</strong>{torrent.dpi}</p>
+//         <p><strong>文件字幕:</strong>{torrent.caption}</p>
+//          <p><strong>最后做种时间:</strong>{new Date(torrent.lastseed).toLocaleString()}</p>
+//       </div>
+//       {/* 做种者列表 */}
+//       <div className="mt-6">
+//         <h2 className="text-xl font-semibold mb-3">做种用户 ({seeders.length})</h2>
+//         {seeders.length > 0 ? (
+//           <div className="overflow-x-auto">
+//             <table className="min-w-full bg-white border border-gray-200">
+//               <thead className="bg-gray-100">
+//                 <tr>
+//                   <th className="py-2 px-4 border-b">用户名</th>
+//                   <th className="py-2 px-4 border-b">已上传</th>
+//                   <th className="py-2 px-4 border-b">上传速度</th>
+//                   <th className="py-2 px-4 border-b">已下载</th>
+//                   <th className="py-2 px-4 border-b">下载速度</th>
+//                   <th className="py-2 px-4 border-b">客户端</th>
+//                   <th className="py-2 px-4 border-b">最后活动</th>
+//                 </tr>
+//               </thead>
+//               <tbody>
+//                 {seeders.map((seeder, index) => (
+//                   <tr key={index} className={index % 2 === 0 ? 'bg-gray-50' : 'bg-white'}>
+//                     <td className="py-2 px-4 border-b text-center">{seeder.username}</td>
+//                     <td className="py-2 px-4 border-b text-center">{formatSize(seeder.uploaded)}</td>
+//                     <td className="py-2 px-4 border-b text-center text-green-600">
+//                       {formatSpeed(seeder.uploadSpeed)}
+//                     </td>
+//                     <td className="py-2 px-4 border-b text-center">{formatSize(seeder.downloaded)}</td>
+//                     <td className="py-2 px-4 border-b text-center">
+//                       {seeder.downloadSpeed > 0 ? formatSpeed(seeder.downloadSpeed) : '-'}
+//                     </td>
+//                     <td className="py-2 px-4 border-b text-center">{seeder.client}</td>
+//                     <td className="py-2 px-4 border-b text-center">
+//                       {new Date(seeder.lastActive).toLocaleTimeString()}
+//                     </td>
+//                   </tr>
+//                 ))}
+//               </tbody>
+//             </table>
+//           </div>
+//         ) : (
+//           <div className="p-4 text-center text-gray-500 bg-gray-50 rounded">
+//             当前没有用户在做种
+//           </div>
+//         )}
+//       </div>
+//     </div>
+//   );
+// };
+
+// export default TorrentDetail;
+// src/pages/TorrentDetail.jsx
+import React, { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
+import axios from 'axios';
+
+const TorrentDetail = () => {
+  const { id } = useParams();
+  const [torrent, setTorrent] = useState(null);
+  const [seeders, setSeeders] = useState([]);
+  const [loading, setLoading] = useState(true);
+
+  useEffect(() => {
+    // 获取种子基本信息
+    axios.get(`http://localhost:8080/torrent/${id}`)
+      .then(res => {
+        setTorrent(res.data);
+        // 获取做种者信息
+        return axios.get(`http://localhost:8080/torrent/${res.data.infoHash}/seeders`);
+      })
+      .then(res => setSeeders(res.data))
+      .catch(err => console.error('获取数据失败', err))
+      .finally(() => setLoading(false));
+  }, [id]);
+
+  if (loading) return <div className="p-4">加载中...</div>;
+  if (!torrent) return <div className="p-4">种子不存在</div>;
+  console.log('Received Torrent:', torrent);
+  console.log('Received Seeders:', seeders);
+
+  // 格式化文件大小
+  const formatSize = (bytes) => {
+    if (bytes === 0) return '0 B';
+    const k = 1024;
+    const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+  };
+
+  // 格式化速度
+  const formatSpeed = (bytesPerSec) => {
+    if (bytesPerSec < 1024) return bytesPerSec.toFixed(2) + ' B/s';
+    if (bytesPerSec < 1024 * 1024) return (bytesPerSec / 1024).toFixed(2) + ' KB/s';
+    return (bytesPerSec / (1024 * 1024)).toFixed(2) + ' MB/s';
+  };
+
+  return (
+    <div className="max-w-4xl mx-auto p-6 bg-white shadow rounded mt-6">
+      <h1 className="text-2xl font-bold mb-4">{torrent.torrentTitle}</h1>
+      
+      {/* 种子基本信息 */}
+      <div className="mb-8 p-4 bg-gray-50 rounded">
+        <h2 className="text-xl font-semibold mb-3">基本信息</h2>
+        <div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-gray-700">
+          <div>
+            <p><strong>简介:</strong>{torrent.description || '暂无简介'}</p>
+            <p><strong>上传人:</strong>{torrent.uploader_id || '未知用户'}</p>
+            <p><strong>上传时间:</strong>{new Date(torrent.uploadTime).toLocaleString()}</p>
+            <p><strong>文件大小:</strong>{formatSize(torrent.torrentSize)}</p>
+          </div>
+          <div>
+            <p><strong>下载数:</strong>{torrent.downloadCount || 0}</p>
+            <p><strong>做种数:</strong>{seeders.length}</p>
+            <p><strong>文件分辨率:</strong>{torrent.dpi || '未知'}</p>
+            <p><strong>文件字幕:</strong>{torrent.caption || '无'}</p>
+            <p><strong>最后做种时间:</strong>{torrent.lastseed ? new Date(torrent.lastseed).toLocaleString() : '暂无'}</p>
+          </div>
+        </div>
+      </div>
+
+      {/* 做种者列表 */}
+      <div className="mt-6">
+        <h2 className="text-xl font-semibold mb-3">做种用户 ({seeders.length})</h2>
+        {seeders.length > 0 ? (
+          <div className="overflow-x-auto">
+            <table className="min-w-full bg-white border border-gray-200">
+              <thead className="bg-gray-100">
+                <tr>
+                  <th className="py-2 px-4 border-b">用户名</th>
+                  <th className="py-2 px-4 border-b">已上传</th>
+                  <th className="py-2 px-4 border-b">上传时间</th>
+                  <th className="py-2 px-4 border-b">完成时间</th>
+                  <th className="py-2 px-4 border-b">上传速度</th>
+                  <th className="py-2 px-4 border-b">已下载</th>
+                  <th className="py-2 px-4 border-b">下载速度</th>
+                  <th className="py-2 px-4 border-b">客户端</th>
+                  <th className="py-2 px-4 border-b">端口</th>
+                  <th className="py-2 px-4 border-b">最后活动</th>
+                </tr>
+              </thead>
+              <tbody>
+                {seeders.map((seeder, index) => (
+                  <tr key={index} className={index % 2 === 0 ? 'bg-gray-50' : 'bg-white'}>
+                    <td className="py-2 px-4 border-b text-center">{seeder.username}</td>
+                    <td className="py-2 px-4 border-b text-center">{formatSize(seeder.uploaded)}</td>
+                    <td className="py-2 px-4 border-b text-center text-green-600">
+                      {new Date(seeder.createdAt).toLocaleString()}
+                    </td>
+                    <td className="py-2 px-4 border-b text-center text-green-600">
+                      {new Date(seeder.completed_time).toLocaleString()}
+                    </td>
+                    <td className="py-2 px-4 border-b text-center text-green-600">
+                      {formatSpeed(seeder.uploadSpeed)}
+                    </td>
+                    <td className="py-2 px-4 border-b text-center">{formatSize(seeder.downloaded)}</td>
+                    <td className="py-2 px-4 border-b text-center">
+                      {seeder.downloadSpeed > 0 ? formatSpeed(seeder.downloadSpeed) : '-'}
+                    </td>
+                    <td className="py-2 px-4 border-b text-center">{seeder.client}</td>
+                    <td className="py-2 px-4 border-b text-center">{seeder.port}</td>
+                    <td className="py-2 px-4 border-b text-center">
+                      {seeder.lastEvent}
+                    </td>
+                  </tr>
+                ))}
+              </tbody>
+            </table>
+          </div>
+        ) : (
+          <div className="p-4 text-center text-gray-500 bg-gray-50 rounded">
+            当前没有用户在做种
+          </div>
+        )}
+      </div>
+    </div>
+  );
+};
+
+export default TorrentDetail;
\ No newline at end of file
diff --git a/pt--frontend/src/pages/UploadPage.jsx b/pt--frontend/src/pages/UploadPage.jsx
new file mode 100644
index 0000000..d0ea71b
--- /dev/null
+++ b/pt--frontend/src/pages/UploadPage.jsx
@@ -0,0 +1,14 @@
+// src/pages/UploadPage.jsx
+import React from 'react';
+import UploadTorrent from '../components/upload';
+
+const UploadPage = () => {
+  return (
+    <div className="min-h-screen bg-gray-100 p-4">
+      <h1 className="text-2xl font-bold mb-4">上传种子</h1>
+      <UploadTorrent />
+    </div>
+  );
+};
+
+export default UploadPage;
diff --git a/pt--frontend/vite.config.js b/pt--frontend/vite.config.js
new file mode 100644
index 0000000..8b0f57b
--- /dev/null
+++ b/pt--frontend/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [react()],
+})