diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/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/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..7059a96 --- /dev/null +++ b/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/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..ec2b712 --- /dev/null +++ b/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/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..0c589ec --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..8683e6b --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,3758 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "@tauri-apps/api": "^1.6.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.25.0", + "@tauri-apps/cli": "^1.6.3", + "@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.npmjs.org/@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.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, + "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.npmjs.org/@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.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, + "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.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "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.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, + "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.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "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.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.3.tgz", + "integrity": "sha512-rmOWVRUbUJD7iSvJugjUbFZshTAuJ48MXoZ80Osx1GM0K/H1w7rSEvmw8m6vdWxNASgtaHIhAgre4H/E9GJiYQ==", + "dev": true, + "dependencies": { + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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/@tauri-apps/api": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.6.0.tgz", + "integrity": "sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg==", + "engines": { + "node": ">= 14.6.0", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.6.3.tgz", + "integrity": "sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==", + "dev": true, + "dependencies": { + "semver": ">=7.5.2" + }, + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "1.6.3", + "@tauri-apps/cli-darwin-x64": "1.6.3", + "@tauri-apps/cli-linux-arm-gnueabihf": "1.6.3", + "@tauri-apps/cli-linux-arm64-gnu": "1.6.3", + "@tauri-apps/cli-linux-arm64-musl": "1.6.3", + "@tauri-apps/cli-linux-x64-gnu": "1.6.3", + "@tauri-apps/cli-linux-x64-musl": "1.6.3", + "@tauri-apps/cli-win32-arm64-msvc": "1.6.3", + "@tauri-apps/cli-win32-ia32-msvc": "1.6.3", + "@tauri-apps/cli-win32-x64-msvc": "1.6.3" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.3.tgz", + "integrity": "sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.3.tgz", + "integrity": "sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.3.tgz", + "integrity": "sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.3.tgz", + "integrity": "sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.3.tgz", + "integrity": "sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.3.tgz", + "integrity": "sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.3.tgz", + "integrity": "sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.3.tgz", + "integrity": "sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.3.tgz", + "integrity": "sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.3.tgz", + "integrity": "sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/react": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", + "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", + "dev": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", + "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "dev": true, + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "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.npmjs.org/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.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "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.npmjs.org/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/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/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.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "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.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "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.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.155", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", + "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "dev": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "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.npmjs.org/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.npmjs.org/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.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "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.npmjs.org/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.npmjs.org/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.npmjs.org/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-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "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.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/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/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/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/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..cbfc053 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,33 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "@tauri-apps/api": "^1.6.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.25.0", + "@tauri-apps/cli": "^1.6.3", + "@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/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src-tauri/.gitignore b/frontend/src-tauri/.gitignore new file mode 100644 index 0000000..aba21e2 --- /dev/null +++ b/frontend/src-tauri/.gitignore @@ -0,0 +1,3 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml new file mode 100644 index 0000000..2de4348 --- /dev/null +++ b/frontend/src-tauri/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cursor-rush" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +license = "" +repository = "" +edition = "2021" +rust-version = "1.56" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +tauri-build = { version = "1.5.2", features = [] } + +[dependencies] +tauri = { version = "1.6.0", features = [ "window-start-dragging", "shell-open"] } +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +image = "0.24" +# Ensure "consoleapi" is in this features list +winapi = { version = "0.3.9", features = ["winuser", "libloaderapi", "wingdi", "errhandlingapi", "consoleapi"] } +once_cell = "1.19.0" +[features] +# this feature is used for production builds or when `devPath` points to the filesystem +# DO NOT REMOVE!! +custom-protocol = ["tauri/custom-protocol"] diff --git a/frontend/src-tauri/build.rs b/frontend/src-tauri/build.rs new file mode 100644 index 0000000..795b9b7 --- /dev/null +++ b/frontend/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/frontend/src-tauri/cursors/default.png b/frontend/src-tauri/cursors/default.png new file mode 100644 index 0000000..8643897 Binary files /dev/null and b/frontend/src-tauri/cursors/default.png differ diff --git a/frontend/src-tauri/cursors/icon.png b/frontend/src-tauri/cursors/icon.png new file mode 100644 index 0000000..8643897 Binary files /dev/null and b/frontend/src-tauri/cursors/icon.png differ diff --git a/frontend/src-tauri/cursors/red.png b/frontend/src-tauri/cursors/red.png new file mode 100644 index 0000000..fd5c6ad Binary files /dev/null and b/frontend/src-tauri/cursors/red.png differ diff --git a/frontend/src-tauri/icons/128x128.png b/frontend/src-tauri/icons/128x128.png new file mode 100644 index 0000000..77e7d23 Binary files /dev/null and b/frontend/src-tauri/icons/128x128.png differ diff --git a/frontend/src-tauri/icons/128x128@2x.png b/frontend/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..0f7976f Binary files /dev/null and b/frontend/src-tauri/icons/128x128@2x.png differ diff --git a/frontend/src-tauri/icons/32x32.png b/frontend/src-tauri/icons/32x32.png new file mode 100644 index 0000000..98fda06 Binary files /dev/null and b/frontend/src-tauri/icons/32x32.png differ diff --git a/frontend/src-tauri/icons/Square107x107Logo.png b/frontend/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..f35d84f Binary files /dev/null and b/frontend/src-tauri/icons/Square107x107Logo.png differ diff --git a/frontend/src-tauri/icons/Square142x142Logo.png b/frontend/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..1823bb2 Binary files /dev/null and b/frontend/src-tauri/icons/Square142x142Logo.png differ diff --git a/frontend/src-tauri/icons/Square150x150Logo.png b/frontend/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..dc2b22c Binary files /dev/null and b/frontend/src-tauri/icons/Square150x150Logo.png differ diff --git a/frontend/src-tauri/icons/Square284x284Logo.png b/frontend/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..0ed3984 Binary files /dev/null and b/frontend/src-tauri/icons/Square284x284Logo.png differ diff --git a/frontend/src-tauri/icons/Square30x30Logo.png b/frontend/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..60bf0ea Binary files /dev/null and b/frontend/src-tauri/icons/Square30x30Logo.png differ diff --git a/frontend/src-tauri/icons/Square310x310Logo.png b/frontend/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..c8ca0ad Binary files /dev/null and b/frontend/src-tauri/icons/Square310x310Logo.png differ diff --git a/frontend/src-tauri/icons/Square44x44Logo.png b/frontend/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..8756459 Binary files /dev/null and b/frontend/src-tauri/icons/Square44x44Logo.png differ diff --git a/frontend/src-tauri/icons/Square71x71Logo.png b/frontend/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..2c8023c Binary files /dev/null and b/frontend/src-tauri/icons/Square71x71Logo.png differ diff --git a/frontend/src-tauri/icons/Square89x89Logo.png b/frontend/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..2c5e603 Binary files /dev/null and b/frontend/src-tauri/icons/Square89x89Logo.png differ diff --git a/frontend/src-tauri/icons/StoreLogo.png b/frontend/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..17d142c Binary files /dev/null and b/frontend/src-tauri/icons/StoreLogo.png differ diff --git a/frontend/src-tauri/icons/icon.icns b/frontend/src-tauri/icons/icon.icns new file mode 100644 index 0000000..a2993ad Binary files /dev/null and b/frontend/src-tauri/icons/icon.icns differ diff --git a/frontend/src-tauri/icons/icon.ico b/frontend/src-tauri/icons/icon.ico new file mode 100644 index 0000000..06c23c8 Binary files /dev/null and b/frontend/src-tauri/icons/icon.ico differ diff --git a/frontend/src-tauri/icons/icon.png b/frontend/src-tauri/icons/icon.png new file mode 100644 index 0000000..d1756ce Binary files /dev/null and b/frontend/src-tauri/icons/icon.png differ diff --git a/frontend/src-tauri/src/cursors/default.png b/frontend/src-tauri/src/cursors/default.png new file mode 100644 index 0000000..8643897 Binary files /dev/null and b/frontend/src-tauri/src/cursors/default.png differ diff --git a/frontend/src-tauri/src/cursors/icon.png b/frontend/src-tauri/src/cursors/icon.png new file mode 100644 index 0000000..8643897 Binary files /dev/null and b/frontend/src-tauri/src/cursors/icon.png differ diff --git a/frontend/src-tauri/src/cursors/red.png b/frontend/src-tauri/src/cursors/red.png new file mode 100644 index 0000000..fd5c6ad Binary files /dev/null and b/frontend/src-tauri/src/cursors/red.png differ diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs new file mode 100644 index 0000000..f019348 --- /dev/null +++ b/frontend/src-tauri/src/lib.rs @@ -0,0 +1,294 @@ +use std::sync::Mutex; +use serde::{Serialize, Deserialize}; +use tauri::{Manager, AppHandle}; +use std::path::{Path, PathBuf}; +use std::env; + +mod platform; + +// Define cursor structure +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Cursor { + name: String, + path: String, +} + +// App state to track cursors +pub struct AppState { + cursors: Vec, + current_index: usize, +} + +// Thread-safe app state wrapper +pub struct AppStateWrapper(Mutex); + +// Command to get all cursors +#[tauri::command] +fn get_cursors(state: tauri::State) -> Vec { + let app_state = state.0.lock().unwrap(); + app_state.cursors.clone() +} + +// Command to get current cursor index +#[tauri::command] +fn get_current_cursor_index(state: tauri::State) -> usize { + let app_state = state.0.lock().unwrap(); + app_state.current_index +} + +// Command to select a cursor +#[tauri::command] +fn select_cursor(index: usize, state: tauri::State, app_handle: AppHandle) -> Result<(), String> { + println!("=== RESOURCE DEBUG [select_cursor] ==="); + + let mut app_state = state.0.lock().unwrap(); + if index < app_state.cursors.len() { + app_state.current_index = index; + + let cursor = &app_state.cursors[index]; + println!("Cursor selected: {}", cursor.name); + println!("Original path: {}", cursor.path); + + // Get the resource directory + let resource_dir = app_handle.path_resolver().resource_dir(); + println!("Resource dir: {:?}", resource_dir); + + // List contents of resource dir + if let Some(res_dir) = &resource_dir { + println!("Listing contents of resource dir: {}", res_dir.display()); + if let Ok(entries) = std::fs::read_dir(res_dir) { + for (i, entry) in entries.filter_map(Result::ok).enumerate() { + println!(" {}: {}", i, entry.path().display()); + + // List subdirectories + if entry.path().is_dir() { + if let Ok(subentries) = std::fs::read_dir(entry.path()) { + for (j, subentry) in subentries.filter_map(Result::ok).enumerate() { + println!(" {}.{}: {}", i, j, subentry.path().display()); + } + } + } + } + } else { + println!("Failed to read resource directory"); + } + } + + let filename = Path::new(&cursor.path) + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| format!("Invalid cursor path: {}", cursor.path))?; + + println!("Extracted filename: {}", filename); + + // Try using the actual file path directly + println!("Using direct file path: {}", cursor.path); + + // Verify the file exists + if Path::new(&cursor.path).exists() { + println!("File exists, applying cursor..."); + + if let Err(e) = platform::set_system_cursor(&cursor.path) { + println!("Error applying cursor: {}", e); + return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e)); + } + + // Emit an event to notify the frontend + let _ = app_handle.emit_all("cursor_changed", index); // Ignore error + + println!("Successfully applied cursor using actual file path"); + return Ok(()); + } else { + println!("File doesn't exist: {}", cursor.path); + return Err(format!("Cursor file does not exist: {}", cursor.path)); + } + } else { + Err("Index out of bounds".to_string()) + } +} + +// Command to go to the next cursor +#[tauri::command] +fn next_cursor(state: tauri::State, app_handle: AppHandle) -> Result { + println!("=== RESOURCE DEBUG [next_cursor] ==="); + + let mut app_state = state.0.lock().unwrap(); + if !app_state.cursors.is_empty() { + app_state.current_index = (app_state.current_index + 1) % app_state.cursors.len(); + + let cursor = &app_state.cursors[app_state.current_index]; + println!("Next cursor: {} ({})", cursor.name, cursor.path); + + let filename = Path::new(&cursor.path) + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| format!("Invalid cursor path: {}", cursor.path))?; + + // Try both resource paths like in select_cursor + println!("Extracted filename: {}", filename); + + // Try using the actual file path directly + println!("Using direct file path: {}", cursor.path); + + // Verify the file exists + if Path::new(&cursor.path).exists() { + println!("File exists, applying cursor..."); + + if let Err(e) = platform::set_system_cursor(&cursor.path) { + println!("Error applying cursor: {}", e); + return Err(format!("Failed to apply cursor '{}': {}", cursor.name, e)); + } + + // Emit an event to notify the frontend + let _ = app_handle.emit_all("cursor_changed", app_state.current_index); // Ignore error + + println!("Successfully applied cursor using actual file path"); + return Ok(app_state.current_index); + } else { + println!("File doesn't exist: {}", cursor.path); + return Err(format!("Cursor file does not exist: {}", cursor.path)); + } + } else { + Err("No cursors available".to_string()) + } +} + +// Command to quit the application +#[tauri::command] +fn quit_app(app_handle: AppHandle) { + // Clean up and restore default cursor before exit + match platform::restore_system_cursor() { + Ok(_) => println!("Default cursor restored"), + Err(e) => eprintln!("Failed to restore default cursor: {}", e), + } + + // Exit the application + std::thread::spawn(move || { + app_handle.exit(0); + }); +} + +// Function to load cursor files from src/cursors directory +fn load_cursor_files() -> Vec { + println!("=== RESOURCE DEBUG [load_cursor_files] ==="); + let mut cursors = Vec::new(); + + // Determine the path to src/cursors relative to src-tauri (CARGO_MANIFEST_DIR) + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + println!("CARGO_MANIFEST_DIR: {}", manifest_dir.display()); + + // Check multiple possible locations + let possible_cursor_dirs = vec![ + manifest_dir.join("../src/cursors"), + manifest_dir.join("src/cursors"), + manifest_dir.join("cursors"), + manifest_dir.clone(), // Check manifest dir itself + ]; + + println!("CHECKING FOR PNG/CUR FILES IN MULTIPLE DIRECTORIES:"); + for (i, cursors_dir) in possible_cursor_dirs.iter().enumerate() { + println!("Checking dir {}: {}", i+1, cursors_dir.display()); + println!(" Directory exists: {}", cursors_dir.exists()); + + if cursors_dir.exists() { + println!(" Looking for cursor files in this directory..."); + + if let Ok(entries) = std::fs::read_dir(cursors_dir) { + for entry in entries.filter_map(Result::ok) { + let path = entry.path(); + println!(" Found: {}", path.display()); + + if path.is_file() && path.extension().map_or(false, |ext| ext == "png" || ext == "cur") { + let name = path.file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("Unknown") + .to_string(); + + println!(" ADDING CURSOR: {} at path {}", name, path.display()); + cursors.push(Cursor { + name, + path: path.to_string_lossy().to_string(), + }); + } + } + } + + if !cursors.is_empty() { + println!(" Found {} cursor files in this directory.", cursors.len()); + break; + } + } + } + + // If we found no cursors, use mock data with paths relative to what resolve_resource expects + if cursors.is_empty() { + println!("No cursors found in any directory. Using mock data instead."); + println!("IMPORTANT: This is probably the source of your problem!"); + println!("The app could not find your cursor PNG files. Check that they exist in one of these directories:"); + for dir in &possible_cursor_dirs { + println!(" {}", dir.display()); + } + + // Mock data paths should reflect their eventual resource path structure + cursors.push(Cursor { + name: "Default".to_string(), + path: "src/cursors/default.png".to_string(), // Filename will be 'default.png' + }); + + cursors.push(Cursor { + name: "Icon".to_string(), + path: "src/cursors/icon.png".to_string(), // Filename will be 'icon.png' + }); + + cursors.push(Cursor { + name: "Red".to_string(), + path: "src/cursors/red.png".to_string(), // Filename will be 'red.png' + }); + + println!("Created {} mock cursor entries", cursors.len()); + } + + println!("Loaded {} cursor files", cursors.len()); + + // Sort cursors by name for consistent ordering + cursors.sort_by(|a, b| a.name.cmp(&b.name)); + cursors +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + println!("Starting Cursor Rush application"); + + // Initialize platform-specific cursor handling + if let Err(e) = platform::initialize() { + eprintln!("Failed to initialize platform: {}", e); + } + + // Load cursors from assets directory + let cursors = load_cursor_files(); + + // Create app state + let app_state = AppState { + cursors, + current_index: 0, + }; + + // This is one of the simplest ways to initialize Tauri + tauri::Builder::default() + .manage(AppStateWrapper(Mutex::new(app_state))) + .invoke_handler(tauri::generate_handler![ + get_cursors, + get_current_cursor_index, + select_cursor, + next_cursor, + quit_app + ]) + .setup(|_app| { + // Register for events or perform additional setup + println!("Tauri application setup complete"); + Ok(()) + }) + // This matches exactly how it was before our changes + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs new file mode 100644 index 0000000..13af675 --- /dev/null +++ b/frontend/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + cursor_rush::run() // Corrected line +} \ No newline at end of file diff --git a/frontend/src-tauri/src/platform/linux.rs b/frontend/src-tauri/src/platform/linux.rs new file mode 100644 index 0000000..b5edc9d --- /dev/null +++ b/frontend/src-tauri/src/platform/linux.rs @@ -0,0 +1,20 @@ +// Linux platform implementation for cursor handling +use super::Result; + +// Initialize cursor handling +pub fn init() -> Result<()> { + println!("Linux cursor initialization not yet implemented"); + Ok(()) // Return OK for now to not block platform-agnostic code +} + +pub fn set_cursor(path: &str) -> Result<()> { + // Linux cursor implementation would go here + println!("Linux cursor setting not yet implemented for path: {}", path); + Ok(()) // Return OK for now to not block platform-agnostic code +} + +pub fn restore_default_cursor() -> Result<()> { + // Linux cursor restoration would go here + println!("Linux cursor restoration not yet implemented"); + Ok(()) // Return OK for now to not block platform-agnostic code +} diff --git a/frontend/src-tauri/src/platform/macos.rs b/frontend/src-tauri/src/platform/macos.rs new file mode 100644 index 0000000..b0bdd8c --- /dev/null +++ b/frontend/src-tauri/src/platform/macos.rs @@ -0,0 +1,20 @@ +// macOS platform implementation for cursor handling +use super::Result; + +// Initialize cursor handling +pub fn init() -> Result<()> { + println!("macOS cursor initialization not yet implemented"); + Ok(()) // Return OK for now to not block platform-agnostic code +} + +pub fn set_cursor(path: &str) -> Result<()> { + // macOS cursor implementation would go here + println!("macOS cursor setting not yet implemented for path: {}", path); + Ok(()) // Return OK for now to not block platform-agnostic code +} + +pub fn restore_default_cursor() -> Result<()> { + // macOS cursor restoration would go here + println!("macOS cursor restoration not yet implemented"); + Ok(()) // Return OK for now to not block platform-agnostic code +} diff --git a/frontend/src-tauri/src/platform/mod.rs b/frontend/src-tauri/src/platform/mod.rs new file mode 100644 index 0000000..0594ba5 --- /dev/null +++ b/frontend/src-tauri/src/platform/mod.rs @@ -0,0 +1,64 @@ +// Platform-specific cursor handling + +// Common types for cursor handling +pub mod types; + +// Windows implementation +#[cfg(target_os = "windows")] +mod windows; + +// macOS implementation +#[cfg(target_os = "macos")] +mod macos; + +// Linux implementation +#[cfg(target_os = "linux")] +mod linux; + +// Error type for platform operations +pub type Result = std::result::Result; + +// Platform-agnostic interface for cursor handling +#[cfg(target_os = "windows")] +pub use self::windows::{set_cursor, restore_default_cursor, init}; + +#[cfg(target_os = "macos")] +pub use self::macos::{set_cursor, restore_default_cursor, init}; + +#[cfg(target_os = "linux")] +pub use self::linux::{set_cursor, restore_default_cursor, init}; + +// Initialize platform-specific cursor handling +pub fn initialize() -> Result<()> { + #[cfg(target_os = "windows")] + { + init() + } + #[cfg(not(target_os = "windows"))] + { + Ok(()) + } +} + +// Cross-platform implementation for setting cursor +pub fn set_system_cursor(path: &str) -> Result<()> { + println!("Setting system cursor: {}", path); + set_cursor(path) +} + +// Cross-platform implementation for restoring default cursor +pub fn restore_system_cursor() -> Result<()> { + println!("Restoring system cursor"); + restore_default_cursor() +} + +// Default implementations for non-supported platforms +#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] +pub fn set_cursor(_path: &str) -> Result<()> { + Err("Cursor customization not supported on this platform".to_string()) +} + +#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] +pub fn restore_default_cursor() -> Result<()> { + Err("Cursor restoration not supported on this platform".to_string()) +} diff --git a/frontend/src-tauri/src/platform/types.rs b/frontend/src-tauri/src/platform/types.rs new file mode 100644 index 0000000..2f13878 --- /dev/null +++ b/frontend/src-tauri/src/platform/types.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; +use image::RgbaImage; + +// Windows-specific cursor handle wrapper +#[cfg(target_os = "windows")] +#[derive(Debug, Clone, Copy)] +pub struct WinCursorWrapper { + pub handle: winapi::shared::windef::HCURSOR, +} + +#[cfg(target_os = "windows")] +unsafe impl Send for WinCursorWrapper {} + +// Common type for cursor information +#[derive(Debug, Clone)] +pub struct CursorInfo { + pub name: String, + pub path: PathBuf, + pub image: Option, + pub hotspot_x: i32, + pub hotspot_y: i32, + + // Platform-specific fields + #[cfg(target_os = "windows")] + pub win_cursor: Option, +} diff --git a/frontend/src-tauri/src/platform/windows/cursor.rs b/frontend/src-tauri/src/platform/windows/cursor.rs new file mode 100644 index 0000000..4792105 --- /dev/null +++ b/frontend/src-tauri/src/platform/windows/cursor.rs @@ -0,0 +1,512 @@ +// Windows platform cursor management + +use std::ptr::null_mut; +use std::sync::atomic::Ordering; +use winapi::{ + um::winuser::{ + GetDC, ReleaseDC, LoadCursorW, SetSystemCursor, CopyIcon, + SystemParametersInfoW, SPI_SETCURSORS, SetCursor, + MAKEINTRESOURCEW, IDC_ARROW, CreateIconIndirect, ICONINFO, + DestroyIcon, + }, + um::wingdi::{ + CreateCompatibleDC, DeleteDC, SelectObject, DeleteObject, + BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, BI_RGB, CreateDIBSection, + }, + um::consoleapi::SetConsoleCtrlHandler, + shared::minwindef::{TRUE, FALSE, DWORD}, +}; +use ::image::{RgbaImage, open as image_open}; + +use super::types::*; +use super::state::*; + +// Control handler function for console events (Ctrl+C, close, etc.) +extern "system" fn ctrl_handler(_ctrl_type: u32) -> i32 { + println!("Control event received, attempting cleanup..."); + cleanup(); // Call cleanup logic + FALSE // Allow other handlers (like default exit) to run +} + +// Create a cursor from an RGBA image +unsafe fn create_cursor_from_image(image: &RgbaImage) -> Option { + let (width, height) = image.dimensions(); + let width_i32 = width as i32; + let height_i32 = height as i32; + + // Create device context and bitmap for the color data + let screen_dc = GetDC(null_mut()); + if screen_dc.is_null() { + eprintln!("Failed to get screen DC"); + return None; + } + + // Create a compatible DC for our bitmap + let color_dc = CreateCompatibleDC(screen_dc); + if color_dc.is_null() { + eprintln!("Failed to create compatible DC for color"); + ReleaseDC(null_mut(), screen_dc); + return None; + } + + // Create a compatible DC for our mask bitmap + let mask_dc = CreateCompatibleDC(screen_dc); + if mask_dc.is_null() { + eprintln!("Failed to create compatible DC for mask"); + DeleteDC(color_dc); + ReleaseDC(null_mut(), screen_dc); + return None; + } + + // Set up BITMAPINFO structure for DIB + let mut bitmap_info: BITMAPINFO = std::mem::zeroed(); + bitmap_info.bmiHeader.biSize = std::mem::size_of::() as u32; + bitmap_info.bmiHeader.biWidth = width_i32; + bitmap_info.bmiHeader.biHeight = -height_i32; // Negative for top-down DIB + bitmap_info.bmiHeader.biPlanes = 1; + bitmap_info.bmiHeader.biBitCount = 32; // 32 bits per pixel (BGRA) + bitmap_info.bmiHeader.biCompression = BI_RGB; + + // Create the color DIB section + let mut color_bits_ptr: *mut winapi::ctypes::c_void = null_mut(); + let mut ppv_bits_color: *mut winapi::ctypes::c_void = color_bits_ptr; + let color_bitmap = CreateDIBSection( + color_dc, + &bitmap_info, + DIB_RGB_COLORS, + &mut ppv_bits_color, + null_mut(), + 0, + ); + color_bits_ptr = ppv_bits_color; + + if color_bitmap.is_null() || color_bits_ptr.is_null() { + eprintln!("Failed to create color DIB section"); + DeleteDC(mask_dc); + DeleteDC(color_dc); + ReleaseDC(null_mut(), screen_dc); + return None; + } + + // Create the mask DIB section (1-bit monochrome) + bitmap_info.bmiHeader.biBitCount = 1; // 1 bit per pixel for mask + let mut mask_bits_ptr: *mut winapi::ctypes::c_void = null_mut(); + let mut ppv_bits: *mut winapi::ctypes::c_void = mask_bits_ptr; + let mask_bitmap = CreateDIBSection( + mask_dc, + &bitmap_info, + DIB_RGB_COLORS, + &mut ppv_bits, + null_mut(), + 0, + ); + mask_bits_ptr = ppv_bits; + + if mask_bitmap.is_null() || mask_bits_ptr.is_null() { + eprintln!("Failed to create mask DIB section"); + DeleteObject(color_bitmap as _); + DeleteDC(mask_dc); + DeleteDC(color_dc); + ReleaseDC(null_mut(), screen_dc); + return None; + } + + // Select the bitmaps into the DCs + let old_color_bitmap = SelectObject(color_dc, color_bitmap as _); + let old_mask_bitmap = SelectObject(mask_dc, mask_bitmap as _); + + if old_color_bitmap.is_null() || old_mask_bitmap.is_null() { + eprintln!("Failed to select bitmaps into DCs"); + DeleteObject(mask_bitmap as _); + DeleteObject(color_bitmap as _); + DeleteDC(mask_dc); + DeleteDC(color_dc); + ReleaseDC(null_mut(), screen_dc); + return None; + } + + // Copy the image data to the color DIB, converting RGBA to BGRA + let color_bits = std::slice::from_raw_parts_mut(color_bits_ptr as *mut u8, (width * height * 4) as usize); + + // Calculate mask stride in bytes (1 bit per pixel, padded to DWORD) + let mask_stride = ((width + 31) / 32) * 4; + let mask_bits = std::slice::from_raw_parts_mut(mask_bits_ptr as *mut u8, (mask_stride * height) as usize); + + // Clear mask bits (all transparent initially) + for i in 0..(mask_stride * height) as usize { + mask_bits[i] = 0xFF; // All bits set = transparent + } + + // Process image pixels + for y in 0..height { + for x in 0..width { + let pixel = image.get_pixel(x, y); + let color_idx = ((y * width + x) * 4) as usize; + + // Get RGBA components + let r = pixel[0]; + let g = pixel[1]; + let b = pixel[2]; + let a = pixel[3]; + + // Set color data (BGR order) + color_bits[color_idx + 0] = b; + color_bits[color_idx + 1] = g; + color_bits[color_idx + 2] = r; + color_bits[color_idx + 3] = a; + + // Update mask bit if pixel is not transparent + if a > 0 { + // Calculate bit position in mask + let byte_idx = (y * mask_stride + (x / 8)) as usize; + let bit_idx = 7 - (x % 8); // Bits are stored MSB first + + // Clear the bit (0 = opaque in AND mask) + mask_bits[byte_idx] &= !(1 << bit_idx); + } + } + } + + // Create ICONINFO structure + let mut icon_info: ICONINFO = std::mem::zeroed(); + icon_info.fIcon = FALSE; // This is a cursor, not an icon + icon_info.xHotspot = width as DWORD / 2; // Center hotspot + icon_info.yHotspot = height as DWORD / 2; + icon_info.hbmMask = mask_bitmap; + icon_info.hbmColor = color_bitmap; + + // Create the cursor + let cursor = CreateIconIndirect(&mut icon_info as *mut _); + + // Clean up resources + SelectObject(color_dc, old_color_bitmap); + SelectObject(mask_dc, old_mask_bitmap); + DeleteDC(color_dc); + DeleteDC(mask_dc); + ReleaseDC(null_mut(), screen_dc); + + // Windows makes a copy of the bitmaps for the cursor, so we can delete them + DeleteObject(color_bitmap as _); + DeleteObject(mask_bitmap as _); + + if cursor.is_null() { + eprintln!("Failed to create cursor"); + return None; + } + + // Return the cursor wrapped in our thread-safe type + Some(SyncHCURSOR(cursor)) +} + +// Cleanup function to restore cursors and clean up resources +pub fn cleanup() { + println!("Running cursor cleanup..."); + + // Restore system cursors if they were hidden + // Use swap to ensure it runs only once + if CURSOR_HIDDEN.swap(false, Ordering::SeqCst) { + println!("Restoring system cursors..."); + + unsafe { + // Try multiple approaches to ensure cursors are restored + + // 1. First try to restore original cursors if we saved them + let original_cursors_opt = ORIGINAL_CURSORS.lock().unwrap().take(); + if let Some(original_cursors) = original_cursors_opt { + println!("Restoring {} saved original cursors", original_cursors.len()); + for (cursor_id, cursor) in original_cursors { + if !cursor.0.is_null() { + // Make a copy of the original cursor + let cursor_copy = CopyIcon(cursor.0); + if !cursor_copy.is_null() { + if SetSystemCursor(cursor_copy, cursor_id) == 0 { + eprintln!("Failed to restore original cursor for ID: {}", cursor_id); + DestroyIcon(cursor_copy); + } + } else { + eprintln!("Failed to copy original cursor for ID: {}", cursor_id); + } + + // Destroy the original cursor we saved + DestroyIcon(cursor.0); + } + } + } else { + println!("No saved original cursors found"); + } + + // 2. Force Windows to reload default cursor settings + println!("Forcing Windows to reload default cursor settings"); + SystemParametersInfoW(SPI_SETCURSORS, 0, null_mut(), 0); + + // 3. Also try the A version for maximum compatibility + super::state::reset_system_cursors(); + + // 4. Reset to default arrow cursor + let arrow_cursor = LoadCursorW(null_mut(), MAKEINTRESOURCEW(IDC_ARROW as u16)); + if !arrow_cursor.is_null() { + SetCursor(arrow_cursor); + println!("Reset to default arrow cursor."); + } + } + + println!("System cursors restoration complete."); + } + + // Destroy the custom cursor if it exists + let custom_cursor_to_destroy = CUSTOM_CURSOR.lock().unwrap().take(); + if let Some(cursor_wrapper) = custom_cursor_to_destroy { + unsafe { + if !cursor_wrapper.0.is_null() { + DestroyIcon(cursor_wrapper.0); + println!("Custom cursor destroyed during cleanup."); + } + } + } + + // Reset the last known cursor position + *LAST_CURSOR_POS.lock().unwrap() = None; + + // Clear the current cursor image + *CURRENT_CURSOR_IMAGE.lock().unwrap() = None; + + // Unregister control handler + if CTRL_HANDLER_REGISTERED.load(Ordering::SeqCst) { + unsafe { + if SetConsoleCtrlHandler(Some(ctrl_handler), FALSE) != 0 { + CTRL_HANDLER_REGISTERED.store(false, Ordering::SeqCst); + println!("Control handler unregistered."); + } else { + eprintln!("Failed to unregister control handler."); + } + } + } + + println!("Cursor cleanup complete."); +} + +// --- Public API Functions --- + +// Hide the system cursor - now just initializes the control handler +pub fn hide_system_cursor() -> bool { + // Initialize once + INIT.get_or_init(|| { + // Register control handler for clean exit + unsafe { + if SetConsoleCtrlHandler(Some(ctrl_handler), TRUE) != 0 { + CTRL_HANDLER_REGISTERED.store(true, Ordering::SeqCst); + println!("Control handler registered for clean exit."); + } else { + eprintln!("Failed to register control handler: {}", std::io::Error::last_os_error()); + } + } + }); + + // Check if already hidden + if CURSOR_HIDDEN.load(Ordering::SeqCst) { + return true; // Already hidden + } + + // Save original cursors for restoration + let mut original_cursors = Vec::new(); + unsafe { + for &cursor_id in SYSTEM_CURSORS.iter() { + // Get the original cursor + let original_cursor = CopyIcon(LoadCursorW(null_mut(), MAKEINTRESOURCEW(cursor_id as u16))); + if !original_cursor.is_null() { + // Save the original cursor for restoration + original_cursors.push((cursor_id, SyncHCURSOR(original_cursor))); + } + } + } + + // Store original cursors for restoration + if !original_cursors.is_empty() { + *ORIGINAL_CURSORS.lock().unwrap() = Some(original_cursors); + } + + // Mark as hidden - this is now just a flag to indicate we've saved the original cursors + CURSOR_HIDDEN.store(true, Ordering::SeqCst); + + true +} + +// Update the cursor image using CursorInfo (with caching) +pub fn update_cursor_info(cursor_info: &mut crate::platform::types::CursorInfo) -> bool { + println!("Updating cursor to: {}", cursor_info.name); + + // Use cached image if available, otherwise load from disk + let image: image::RgbaImage = if let Some(ref cached_image) = cursor_info.image { + println!("Using cached image for cursor: {}", cursor_info.name); + let img_clone: image::RgbaImage = cached_image.clone(); + img_clone + } else { + println!("Loading image from disk for cursor: {}", cursor_info.name); + match image_open(&cursor_info.path) { + Ok(img) => { + let rgba = img.to_rgba8(); + // Cache the image for future use + // Make sure to specify the type to avoid type inference issues + let image_copy: image::RgbaImage = rgba.clone(); + cursor_info.image = Some(image_copy); + rgba + }, + Err(e) => { + eprintln!("Failed to load cursor image: {}", e); + return false; + } + } + }; + + // Store the image in the global state for reference + *CURRENT_CURSOR_IMAGE.lock().unwrap() = Some(image.clone()); + + unsafe { + // Create a new cursor first before destroying the old one + // This minimizes the time when no custom cursor is active + let new_cursor = if let Some(ref cached_image) = cursor_info.image { + // Always create a fresh cursor from the image to avoid invalid handle issues + create_cursor_from_image(cached_image) + } else { + create_cursor_from_image(&image) + }; + + if let Some(new_cursor_wrapper) = new_cursor { + // Apply the new cursor to all system cursors first + let success = apply_cursor_to_all_system_cursors(new_cursor_wrapper.0); + + // Now that the new cursor is applied, we can clean up the old one + let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap(); + if let Some(old_cursor) = cursor_guard.take() { + // Only destroy the old cursor after the new one is applied + DestroyIcon(old_cursor.0); + } + + // Store the new cursor + *cursor_guard = Some(new_cursor_wrapper); + + // Update the cached cursor in CursorInfo + #[cfg(windows)] + { + // Convert SyncHCURSOR to WinCursorWrapper for storage in cursor_info + cursor_info.win_cursor = Some(crate::platform::types::WinCursorWrapper { + handle: new_cursor_wrapper.0 + }); + } + + if !success { + eprintln!("Failed to apply cursor to all system cursors"); + // Even if we failed to set all system cursors, try to at least set the current cursor + SetCursor(new_cursor_wrapper.0); + } + + return true; + } else { + eprintln!("Failed to create cursor from image"); + + // If we failed to create a new cursor, try to use the cached one as a fallback + #[cfg(windows)] + if let Some(cached_cursor) = &cursor_info.win_cursor { + if !cached_cursor.handle.is_null() { + println!("Falling back to cached cursor as a last resort"); + let mut cursor_guard = CUSTOM_CURSOR.lock().unwrap(); + // Convert WinCursorWrapper to SyncHCURSOR + let sync_cursor = super::types::SyncHCURSOR(cached_cursor.handle); + *cursor_guard = Some(sync_cursor); + SetCursor(cached_cursor.handle); + return true; + } + } + + return false; + } + } +} + + + +// Apply a cursor to all system cursors +unsafe fn apply_cursor_to_all_system_cursors(cursor: winapi::shared::windef::HCURSOR) -> bool { + let mut success = true; + + // First, ensure the cursor is valid + if cursor.is_null() { + eprintln!("Cannot apply null cursor to system cursors"); + return false; + } + + // Immediately set the current cursor to minimize flashing + SetCursor(cursor); + + // Apply the cursor to all system cursor types + // Start with the most commonly used cursors first + let priority_cursors = [ + 32512, // IDC_ARROW (most important) + 32513, // IDC_IBEAM (text cursor) + 32649, // IDC_HAND (links) + ]; + + // Apply to priority cursors first + for &cursor_id in priority_cursors.iter() { + // Make a copy of the cursor + let cursor_copy = CopyIcon(cursor); + if cursor_copy.is_null() { + let error = std::io::Error::last_os_error(); + eprintln!("Failed to copy cursor for priority ID: {} - Error: {}", cursor_id, error); + success = false; + continue; + } + + // Set the system cursor + if SetSystemCursor(cursor_copy, cursor_id) == 0 { + let error = std::io::Error::last_os_error(); + eprintln!("Failed to set cursor for priority ID: {} - Error: {}", cursor_id, error); + DestroyIcon(cursor_copy); // Clean up the copy if we failed to set it + success = false; + } + } + + // Now apply to the remaining cursors + for &cursor_id in SYSTEM_CURSORS.iter() { + // Skip the priority cursors we already processed + if priority_cursors.contains(&cursor_id) { + continue; + } + + // Make a copy of the cursor + let cursor_copy = CopyIcon(cursor); + if cursor_copy.is_null() { + // Don't log errors for non-priority cursors to reduce console spam + success = false; + continue; + } + + // Set the system cursor + if SetSystemCursor(cursor_copy, cursor_id) == 0 { + // Don't log errors for non-priority cursors to reduce console spam + DestroyIcon(cursor_copy); // Clean up the copy if we failed to set it + success = false; + } + } + + // If we had any failures, set the current cursor again as a fallback + if !success { + SetCursor(cursor); + } + + success +} + +// Restore the system cursor +pub fn restore_cursor() { + cleanup(); +} + +// Function to handle mouse movement and update the cursor +// Kept for potential future use but currently not needed with SetSystemCursor approach +#[allow(dead_code)] +pub fn handle_mouse_move() { + // With SetSystemCursor, we don't need to constantly update the cursor on mouse move + // This function is kept for API compatibility, but it's now a no-op + // System cursors are already set to our custom cursor +} diff --git a/frontend/src-tauri/src/platform/windows/mod.rs b/frontend/src-tauri/src/platform/windows/mod.rs new file mode 100644 index 0000000..45dad1e --- /dev/null +++ b/frontend/src-tauri/src/platform/windows/mod.rs @@ -0,0 +1,45 @@ +// Windows platform implementation module + +pub mod types; +mod cursor; +mod state; + +use std::path::PathBuf; +use crate::platform::Result; + +// Functions expected by the platform-agnostic interface +pub fn set_cursor(path: &str) -> Result<()> { + println!("Windows platform: setting cursor to {}", path); + + // Create a cursor info struct to pass to update_cursor_info + let mut cursor_info = crate::platform::types::CursorInfo { + name: path.split('\\').last().unwrap_or("unknown").to_string(), + path: PathBuf::from(path), + image: None, + hotspot_x: 0, // Default hotspot at top-left + hotspot_y: 0, + #[cfg(target_os = "windows")] + win_cursor: None, + }; + + // Use the existing update_cursor_info function to handle loading and applying the cursor + if !cursor::update_cursor_info(&mut cursor_info) { + return Err(format!("Failed to update cursor from {}", path)); + } + + Ok(()) +} + +pub fn restore_default_cursor() -> Result<()> { + println!("Windows platform: restoring default cursor"); + cursor::restore_cursor(); + Ok(()) +} + +pub fn init() -> Result<()> { + println!("Windows platform: initializing cursor system"); + if !cursor::hide_system_cursor() { + return Err("Failed to initialize cursor system".to_string()); + } + Ok(()) +} diff --git a/frontend/src-tauri/src/platform/windows/state.rs b/frontend/src-tauri/src/platform/windows/state.rs new file mode 100644 index 0000000..aa1e86c --- /dev/null +++ b/frontend/src-tauri/src/platform/windows/state.rs @@ -0,0 +1,57 @@ +// Windows platform state management + +use std::sync::{Mutex, atomic::AtomicBool}; +use winapi::{ + shared::minwindef::DWORD, + shared::windef::POINT, +}; +use ::image::RgbaImage; +use once_cell::sync::{Lazy, OnceCell}; + +use super::types::*; + +// --- Thread-Safe Static Variables --- + +// Use AtomicBool for simple flags +pub static CURSOR_HIDDEN: AtomicBool = AtomicBool::new(false); +pub static CTRL_HANDLER_REGISTERED: AtomicBool = AtomicBool::new(false); + +// Use Lazy> for complex Option types +pub static ORIGINAL_CURSORS: Lazy>>> = Lazy::new(|| Mutex::new(None)); +pub static CURRENT_CURSOR_IMAGE: Lazy>> = Lazy::new(|| Mutex::new(None)); +pub static LAST_CURSOR_POS: Lazy>> = Lazy::new(|| Mutex::new(None)); + +// Use Lazy>> for WinAPI handles with our thread-safe wrappers +pub static CUSTOM_CURSOR: Lazy>> = Lazy::new(|| Mutex::new(None)); + +// Note: Legacy variables for window-based cursor approach and mouse hook tracking +// have been removed as part of code cleanup + +// Use OnceCell for INIT to avoid `static mut` +pub static INIT: OnceCell<()> = OnceCell::new(); + +// Reset system cursors using the Windows API +pub fn reset_system_cursors() { + unsafe { + // Use both versions for maximum compatibility + winapi::um::winuser::SystemParametersInfoW( + winapi::um::winuser::SPI_SETCURSORS, + 0, + std::ptr::null_mut(), + 0 + ); + + // Import the Windows API function to reset system cursors + #[link(name = "user32")] + extern "system" { + fn SystemParametersInfoA(uiAction: u32, uiParam: u32, pvParam: *mut std::ffi::c_void, fWinIni: u32) -> i32; + } + + SystemParametersInfoA( + SPI_SETCURSORS_A, + 0, + std::ptr::null_mut(), + SPIF_UPDATEINIFILE | SPIF_SENDCHANGE + ); + } +} diff --git a/frontend/src-tauri/src/platform/windows/types.rs b/frontend/src-tauri/src/platform/windows/types.rs new file mode 100644 index 0000000..ca658ef --- /dev/null +++ b/frontend/src-tauri/src/platform/windows/types.rs @@ -0,0 +1,40 @@ +// Windows platform types and constants + + +use winapi::{ + shared::windef::HCURSOR, + shared::minwindef::DWORD, +}; + +// --- Thread-Safe Handle Wrappers --- +// These wrappers make raw Windows handles safe to share between threads + +// Wrapper for HCURSOR (cursor handle) +#[derive(Copy, Clone, Debug)] +pub struct SyncHCURSOR(pub HCURSOR); +unsafe impl Send for SyncHCURSOR {} +unsafe impl Sync for SyncHCURSOR {} + +// --- Constants --- + +// Constants for SystemParametersInfo +pub const SPI_SETCURSORS_A: u32 = 0x0057; +pub const SPIF_UPDATEINIFILE: u32 = 0x01; +pub const SPIF_SENDCHANGE: u32 = 0x02; + +// Note: Constants for window-based cursor approach have been removed as part of code cleanup + +// Define all system cursor IDs we'll replace +pub static SYSTEM_CURSORS: [DWORD; 11] = [ + 32512, // IDC_ARROW + 32513, // IDC_IBEAM + 32515, // IDC_CROSS + 32514, // IDC_WAIT + 32649, // IDC_HAND + 32650, // IDC_APPSTARTING + 32648, // IDC_NO + 32646, // IDC_SIZEALL + 32645, // IDC_SIZENS + 32644, // IDC_SIZEWE + 32642, // IDC_SIZENWSE +]; diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json new file mode 100644 index 0000000..aa3e874 --- /dev/null +++ b/frontend/src-tauri/tauri.conf.json @@ -0,0 +1,60 @@ +{ + "build": { + "beforeDevCommand": "npm run dev", + "beforeBuildCommand": "npm run build", + "devPath": "http://localhost:5173", + "distDir": "../dist" + }, + "package": { + "productName": "cursor-rush", + "version": "0.1.0" + }, + "tauri": { + "allowlist": { + "all": false, + "window": { + "all": false, + "startDragging": true + }, + "shell": { + "all": false, + "open": true + }, + "fs": { + "all": false, + "scope": [ + "$APP/cursors/*" + ] + } + }, + "bundle": { + "active": true, + "identifier": "com.cursor-rush.dev", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "targets": "all" + }, + "security": { + "csp": null + }, + "updater": { + "active": false + }, + "windows": [ + { + "title": "Cursor Rush", + "width": 310, + "height": 600, + "resizable": true, + "fullscreen": false, + "decorations": false, + "transparent": true + } + ] + } +} diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..7b75691 --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,102 @@ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --bg-color: rgba(24, 24, 37, 0.85); + --nav-bg: rgba(40, 40, 56, 0.4); + --nav-active: rgba(65, 66, 88, 0.7); + --border-color: rgba(49, 50, 68, 0.6); + --text-color: #cdd6f4; + --accent-color: #89b4fa; + --button-bg: rgba(65, 66, 88, 0.6); + --button-hover: rgba(70, 71, 90, 0.8); + --button-active: rgba(137, 180, 250, 0.8); + --tab-bg: rgba(40, 40, 56, 0.4); + --tab-active: rgba(65, 66, 88, 0.7); +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: transparent; + color: var(--text-color); + overflow: hidden; + margin: 0; + padding: 0; + height: 100vh; + width: 100vw; +} + +/* Modal Container - Draggable Area */ +.modal-container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + width: 100vw; + background-color: transparent; +} + +/* Modal - Main App Window */ +.modal { + width: 100%; + height: 100%; + background-color: var(--bg-color); + backdrop-filter: blur(10px); + border-radius: 8px; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); + border: 1px solid var(--border-color); +} + +/* Scrollbars */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--nav-bg); +} + +::-webkit-scrollbar-thumb { + background: var(--nav-bg); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--nav-active); +} + +/* Buttons */ +button { + background-color: var(--button-bg); + color: var(--text-color); + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.2s; +} + +button:hover { + background-color: var(--button-hover); +} + +button:active { + background-color: var(--button-active); + color: var(--bg-color); +} + +/* Images */ +img { + max-width: 100%; + height: auto; +} + diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..de12990 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,123 @@ +import { useState, useEffect } from "react"; +import { invoke } from "@tauri-apps/api/tauri"; +import { appWindow } from "@tauri-apps/api/window"; +import "./App.css"; + +// Import cursor images directly +import defaultCursor from "./cursors/default.png"; +import iconCursor from "./cursors/icon.png"; +import redCursor from "./cursors/red.png"; + +// Components +import Header from "./components/Header"; +import Content from "./components/Content"; +import Footer from "./components/Footer"; + +function App() { + const [activePage, setActivePage] = useState("cursors"); + const [cursors, setCursors] = useState([]); + const [currentCursorIndex, setCurrentCursorIndex] = useState(0); + + // Load cursors on mount + useEffect(() => { + const loadCursors = async () => { + try { + // Get cursors from the Tauri backend + const loadedCursors = await invoke("get_cursors"); + console.log("Loaded cursors:", loadedCursors); + setCursors(loadedCursors || []); + + // Get current cursor index + const currentIndex = await invoke("get_current_cursor_index"); + setCurrentCursorIndex(currentIndex); + } catch (error) { + console.error("Failed to load cursors:", error); + // Use the imported cursor images directly + setCursors([ + { name: "default", path: defaultCursor }, + { name: "icon", path: iconCursor }, + { name: "red", path: redCursor }, + // We don't have blue and gold cursors imported since they don't exist yet + // These will be excluded for now + ]); + setCurrentCursorIndex(0); + } + }; + + loadCursors(); + }, []); + + // Functions to handle cursor operations using Tauri backend + const selectCursor = async (index) => { + try { + console.log(`Selecting cursor at index ${index}`); + await invoke("select_cursor", { index }); + setCurrentCursorIndex(index); + } catch (error) { + console.error("Failed to select cursor:", error); + } + }; + + const nextCursor = async () => { + if (cursors.length === 0) return; + + try { + console.log('Switching to next cursor'); + const newIndex = await invoke("next_cursor"); + setCurrentCursorIndex(newIndex); + } catch (error) { + console.error("Failed to switch to next cursor:", error); + // Fallback if backend fails + const newIndex = (currentCursorIndex + 1) % cursors.length; + setCurrentCursorIndex(newIndex); + } + }; + + // Function to minimize app to tray + const minimizeToTray = async () => { + try { + console.log('Minimizing app to tray'); + await appWindow.hide(); + } catch (error) { + console.error("Failed to minimize to tray:", error); + } + }; + + // Function to exit app + const quitApp = async () => { + try { + console.log('Quitting app'); + await invoke("quit_app"); + } catch (error) { + console.error("Failed to quit app:", error); + } + }; + + return ( +
+
+
+ + + +
0 ? `${cursors.length} cursors loaded` : "No cursors found"} + /> +
+
+ ); +} + +export default App; diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/Content.css b/frontend/src/components/Content.css new file mode 100644 index 0000000..ea53723 --- /dev/null +++ b/frontend/src/components/Content.css @@ -0,0 +1,14 @@ +/* Content Component Styles */ +.app-content { + flex: 1; + overflow-y: auto; + padding: 1vh; + border-top: 2px solid rgba(49, 50, 68, 0.6); +} + +.content-title { + font-size: 18px; + color: var(--accent-color, #89b4fa); + margin-bottom: 15px; + font-weight: 500; +} diff --git a/frontend/src/components/Content.jsx b/frontend/src/components/Content.jsx new file mode 100644 index 0000000..1f0c3cc --- /dev/null +++ b/frontend/src/components/Content.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import './Content.css'; + +// Import content modules +import Inventory from './content_modules/Inventory'; +import Cursors from './content_modules/Cursors'; +import Crates from './content_modules/Crates'; +import Settings from './content_modules/Settings'; + +function Content({ activePage, cursors, currentCursorIndex, selectCursor, nextCursor }) { + // Render the appropriate content based on active page + const renderContent = () => { + switch (activePage) { + case 'inventory': + return ; + case 'cursors': + return ( + + ); + case 'crates': + return ; + case 'settings': + return ; + default: + return ; + } + }; + + return ( +
+ {renderContent()} +
+ ); +} + +export default Content; diff --git a/frontend/src/components/Footer.css b/frontend/src/components/Footer.css new file mode 100644 index 0000000..a5140cb --- /dev/null +++ b/frontend/src/components/Footer.css @@ -0,0 +1,21 @@ +/* Footer Component Styles */ +.app-footer { + width: 100%; + background-color: var(--footer-bg, #181825); + padding: 8px 15px; + display: flex; + justify-content: space-between; + align-items: center; + border-top: 1px solid var(--border-color, #313244); + font-size: 12px; +} + +.app-status { + color: var(--text-color, #cdd6f4); + opacity: 0.8; +} + +.app-version { + color: var(--text-color, #cdd6f4); + opacity: 0.6; +} diff --git a/frontend/src/components/Footer.jsx b/frontend/src/components/Footer.jsx new file mode 100644 index 0000000..ceecce0 --- /dev/null +++ b/frontend/src/components/Footer.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import './Footer.css'; + +function Footer({ version, status }) { + return ( +
+
+ {status} +
+
+ v{version} +
+
+ ); +} + +export default Footer; diff --git a/frontend/src/components/Header.css b/frontend/src/components/Header.css new file mode 100644 index 0000000..cf3e4ed --- /dev/null +++ b/frontend/src/components/Header.css @@ -0,0 +1,100 @@ +/* Header Component Styles */ +.app-header { + width: 100%; + user-select: none; + height: 40px; + position: relative; + z-index: 10; + background-color: transparent; /* Reverted to original */ + /* cursor: move; */ /* Removed, should be handled by drag region or default */ +} + +.tabs-container { + display: flex; + align-items: center; + height: 100%; + position: relative; + padding-right: 10px; + background-color: transparent; +} + +/* Chrome-like tabs */ +.tab { + -webkit-app-region: no-drag; /* Override drag region for clickable tabs */ + cursor: pointer; /* Ensure tabs have a pointer cursor */ + height: 36px; + width: 42px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + font-size: 14px; + margin-right: 5px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + color: var(--text-color); + background-color: transparent; + transition: all 0.2s ease; + cursor: pointer; + margin-top: 4px; +} + +.tab:hover:not(.active):not(.drag-handle) { + background-color: var(--tab-bg); + transform: translateY(-2px); +} + +.tab.active { + background-color: var(--tab-active); + color: var(--accent-color); + z-index: 1; + height: 40px; + margin-top: 0; + box-shadow: 0 0 10px rgba(137, 180, 250, 0.3); +} + +/* Special drag handle tab */ +.tab.drag-handle { + width: 30px; + background-color: transparent; + color: rgba(205, 214, 244, 0.5); +} + +.tab.drag-handle:hover { + color: rgba(205, 214, 244, 0.8); +} + +/* Window control buttons */ +.window-controls { + -webkit-app-region: no-drag; /* Override drag region for buttons */ + cursor: default; /* Keep default cursor for controls area */ + display: flex; + gap: 8px; + margin-left: auto; + margin-right: 10px; +} + +.minimize-button, +.close-button { + background: none; + border: none; + font-size: 16px; + line-height: 1; + color: var(--text-color); + cursor: pointer; + padding: 2px 8px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.minimize-button:hover, +.close-button:hover { + background-color: var(--button-hover); +} + +.close-button:hover { + color: #f38ba8; +} diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx new file mode 100644 index 0000000..9ff69f4 --- /dev/null +++ b/frontend/src/components/Header.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import './Header.css'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faGripVertical, + faBoxOpen, + faMousePointer, + faBoxes, + faCog, + faBoxArchive, + faBox, + faWarehouse, + faGraduationCap, + faCubesStacked, + faBorderAll, + faBagShopping +} from '@fortawesome/free-solid-svg-icons'; + +function Header({ activePage, setActivePage, minimizeToTray, quitApp }) { + // Navigation buttons for each page with icons + const navItems = [ + { id: 'inventory', icon: faBagShopping, tooltip: 'Inventory' }, + { id: 'cursors', icon: faMousePointer, tooltip: 'Cursors' }, + { id: 'crates', icon: faBoxOpen, tooltip: 'Crates' }, + { id: 'settings', icon: faCog, tooltip: 'Settings' } + ]; + + return ( +
+
+ {navItems.map((item) => ( +
setActivePage(item.id)} + title={item.tooltip} + > + +
+ ))} +
+ + +
+
+
+ ); +} + +export default Header; diff --git a/frontend/src/components/content_modules/Crates.css b/frontend/src/components/content_modules/Crates.css new file mode 100644 index 0000000..13de879 --- /dev/null +++ b/frontend/src/components/content_modules/Crates.css @@ -0,0 +1,351 @@ +/* Crates Module Styles */ +.crates-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Current crate section (top part) */ +.current-crate-section { + height: 60%; + min-height: 200px; + display: flex; + align-items: center; + justify-content: center; + border-bottom: 2px solid rgba(49, 50, 68, 0.6); + padding: 10px; +} + +.current-crate { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: rgba(30, 30, 46, 0.3); + border-radius: 8px; + padding: 15px; + border: 1px solid rgba(49, 50, 68, 0.4); + position: relative; + overflow: hidden; +} + +/* Color accents for different crate types */ +.current-crate::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + opacity: 0.8; +} + +.current-crate.common::before { background-color: #a6adc8; } +.current-crate.uncommon::before { background-color: #a6e3a1; } +.current-crate.rare::before { background-color: #89b4fa; } +.current-crate.epic::before { background-color: #cba6f7; } +.current-crate.legendary::before { + background: linear-gradient(90deg, #f9e2af, #fab387, #f9e2af); + animation: shimmer 2s infinite; +} + +@keyframes shimmer { + 0% { background-position: -100px; } + 100% { background-position: 100px; } +} + +.crate-navigation { + display: flex; + align-items: center; + justify-content: space-between; + flex: 1; +} + +.nav-button { + background-color: rgba(49, 50, 68, 0.6); + border: none; + border-radius: 50%; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-color); + cursor: pointer; + transition: all 0.2s; +} + +.nav-button:hover { + background-color: rgba(65, 66, 88, 0.8); + transform: scale(1.1); +} + +.nav-button:disabled { + opacity: 0.3; + cursor: not-allowed; + transform: none; +} + +.crate-display { + display: flex; + align-items: center; + gap: 20px; + flex: 1; + padding: 0 20px; +} + +.crate-image { + display: flex; + justify-content: center; + align-items: center; + width: 100px; + height: 100px; + background-color: rgba(24, 24, 37, 0.5); + border-radius: 12px; + transition: all 0.3s; + border: 1px solid rgba(49, 50, 68, 0.6); +} + +.crate-icon { + font-size: 48px; + transition: all 0.3s; +} + +/* Crate color styling */ +.current-crate.common .crate-icon { color: #a6adc8; text-shadow: 0 0 10px rgba(166, 173, 200, 0.4); } +.current-crate.uncommon .crate-icon { color: #a6e3a1; text-shadow: 0 0 10px rgba(166, 227, 161, 0.4); } +.current-crate.rare .crate-icon { color: #89b4fa; text-shadow: 0 0 10px rgba(137, 180, 250, 0.4); } +.current-crate.epic .crate-icon { color: #cba6f7; text-shadow: 0 0 10px rgba(203, 166, 247, 0.4); } +.current-crate.legendary .crate-icon { color: #f9e2af; text-shadow: 0 0 15px rgba(249, 226, 175, 0.6); animation: pulse 2s infinite; } + +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } +} + +/* Opening animation */ +.crate-image.opening { + animation: shake 0.5s infinite; +} + +@keyframes shake { + 0% { transform: translateX(0); } + 25% { transform: translateX(-5px); } + 50% { transform: translateX(5px); } + 75% { transform: translateX(-5px); } + 100% { transform: translateX(0); } +} + +.crate-info { + display: flex; + flex-direction: column; + flex: 1; +} + +.crate-name { + font-size: 24px; + font-weight: 600; + margin: 0 0 5px 0; +} + +.current-crate.common .crate-name { color: #a6adc8; } +.current-crate.uncommon .crate-name { color: #a6e3a1; } +.current-crate.rare .crate-name { color: #89b4fa; } +.current-crate.epic .crate-name { color: #cba6f7; } +.current-crate.legendary .crate-name { color: #f9e2af; } + +.crate-description { + font-size: 14px; + opacity: 0.8; + margin: 0 0 10px 0; +} + +.crate-stats { + font-size: 14px; +} + +.crate-actions { + display: flex; + justify-content: center; + margin-top: 15px; +} + +.open-button { + width: 100%; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; + display: flex; + align-items: center; + gap: 8px; + text-transform: uppercase; + letter-spacing: 0.5px; + min-width: 150px; + justify-content: center; + background-color: rgba(49, 50, 68, 0.7); + color: var(--text-color); +} + +.open-button:hover:not(:disabled) { + transform: translateY(-3px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2); +} + +.open-button:active:not(:disabled) { + transform: translateY(0); +} + +.open-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Button colors by rarity */ +.open-button.common:hover:not(:disabled) { background-color: #a6adc8; color: #1e1e2e; } +.open-button.uncommon:hover:not(:disabled) { background-color: #a6e3a1; color: #1e1e2e; } +.open-button.rare:hover:not(:disabled) { background-color: #89b4fa; color: #1e1e2e; } +.open-button.epic:hover:not(:disabled) { background-color: #cba6f7; color: #1e1e2e; } +.open-button.legendary:hover:not(:disabled) { background-color: #f9e2af; color: #1e1e2e; } + +/* Crates inventory section (bottom part) */ +.crates-inventory-section { + height: 40%; + display: flex; + flex-direction: column; + padding: 10px; + overflow-y: auto; +} + +.inventory-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.inventory-header h3 { + font-size: 14px; + font-weight: 600; + margin: 0; + opacity: 0.9; +} + +.total-crates { + font-size: 12px; + opacity: 0.7; +} + +.crates-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); + gap: 10px; + padding-bottom: 15px; +} + +.crate-item { + background-color: rgba(49, 50, 68, 0.4); + border-radius: 8px; + padding: 10px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s; + border: 2px solid transparent; + position: relative; + gap: 8px; +} + +.crate-item::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 3px; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; +} + +/* Crate item colors */ +.crate-item.common::after { background-color: #a6adc8; } +.crate-item.uncommon::after { background-color: #a6e3a1; } +.crate-item.rare::after { background-color: #89b4fa; } +.crate-item.epic::after { background-color: #cba6f7; } +.crate-item.legendary::after { + background: linear-gradient(90deg, #f9e2af, #fab387, #f9e2af); + background-size: 200% auto; + animation: shimmer 2s infinite; +} + +.crate-item:hover:not(.empty) { + transform: translateY(-3px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); +} + +.crate-item.selected { + border-color: var(--accent-color); + background-color: rgba(49, 50, 68, 0.6); + box-shadow: 0 0 10px rgba(137, 180, 250, 0.4); +} + +.crate-item.empty { + opacity: 0.4; + cursor: not-allowed; +} + +.crate-item-icon { + font-size: 24px; + position: relative; +} + +.crate-item.common .crate-item-icon { color: #a6adc8; } +.crate-item.uncommon .crate-item-icon { color: #a6e3a1; } +.crate-item.rare .crate-item-icon { color: #89b4fa; } +.crate-item.epic .crate-item-icon { color: #cba6f7; } +.crate-item.legendary .crate-item-icon { color: #f9e2af; } + +.crate-count { + position: absolute; + top: -5px; + right: -10px; + background-color: rgba(30, 30, 46, 0.9); + border-radius: 50%; + width: 18px; + height: 18px; + font-size: 10px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid currentColor; +} + +.crate-item-name { + font-size: 11px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; +} + +/* Responsive adjustments */ +@media (max-width: 500px) { + .crate-display { + flex-direction: column; + text-align: center; + gap: 10px; + } + + .crate-info { + text-align: center; + } +} diff --git a/frontend/src/components/content_modules/Crates.jsx b/frontend/src/components/content_modules/Crates.jsx new file mode 100644 index 0000000..c6c9744 --- /dev/null +++ b/frontend/src/components/content_modules/Crates.jsx @@ -0,0 +1,133 @@ +import React, { useState } from 'react'; +import './Crates.css'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faBox, faBoxOpen, faArrowRight, faArrowLeft, faUnlock } from '@fortawesome/free-solid-svg-icons'; + +function Crates() { + // Mock crate data - would come from backend + const crateTypes = [ + { id: 'common', name: 'Common Crate', count: 5, color: '#a6adc8', description: 'Contains common cursors' }, + { id: 'uncommon', name: 'Uncommon Crate', count: 3, color: '#a6e3a1', description: 'Contains uncommon and common cursors' }, + { id: 'rare', name: 'Rare Crate', count: 2, color: '#89b4fa', description: 'Contains rare and uncommon cursors' }, + { id: 'epic', name: 'Epic Crate', count: 1, color: '#cba6f7', description: 'Contains epic, rare and uncommon cursors' }, + { id: 'legendary', name: 'Legendary Crate', count: 0, color: '#f9e2af', description: 'Contains legendary and epic cursors' } + ]; + + const [selectedCrateIndex, setSelectedCrateIndex] = useState(0); + const [isOpening, setIsOpening] = useState(false); + const selectedCrate = crateTypes[selectedCrateIndex]; + + // Navigate through available crates + const navigateCrate = (direction) => { + // Find next available crate with count > 0 + let newIndex = selectedCrateIndex; + let attempts = 0; + + while (attempts < crateTypes.length) { + newIndex = (newIndex + direction + crateTypes.length) % crateTypes.length; + if (crateTypes[newIndex].count > 0) { + break; + } + attempts++; + } + + if (crateTypes[newIndex].count > 0) { + setSelectedCrateIndex(newIndex); + } + }; + + // Simulate opening a crate + const openCrate = () => { + if (selectedCrate.count <= 0) return; + + setIsOpening(true); + // Simulate API call and animation time + setTimeout(() => { + setIsOpening(false); + }, 2000); + }; + + return ( +
+ {/* Current selected crate section */} +
+
+
+ + +
+
+ +
+
+

{selectedCrate.name}

+
+ You have: {selectedCrate.count} +
+
+
+ + +
+ +
+ +
+
+
+ + {/* Crates inventory section */} +
+
+

Your Crates

+ + {crateTypes.reduce((total, crate) => total + crate.count, 0)} total + +
+ +
+ {crateTypes.map((crate, index) => ( +
crate.count > 0 && setSelectedCrateIndex(index)} + > +
+ + {crate.count > 1 && {crate.count}} +
+
{crate.name}
+
+ ))} +
+
+
+ ); +} + +export default Crates; diff --git a/frontend/src/components/content_modules/Cursors.css b/frontend/src/components/content_modules/Cursors.css new file mode 100644 index 0000000..e62c40d --- /dev/null +++ b/frontend/src/components/content_modules/Cursors.css @@ -0,0 +1,358 @@ +/* Cursors Module Styles */ +.cursors-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Equipped cursor banner */ +.equipped-cursor-banner { + height: 25%; + min-height: 120px; + max-height: 150px; + display: flex; + background-color: rgba(30, 30, 46, 0.3); + border-bottom: 2px solid rgba(49, 50, 68, 0.6); + padding: 10px; + margin-bottom: 10px; +} + + + +.current-preview { + max-width: 80%; + max-height: 80%; + object-fit: contain; + filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.3)); + transition: all 0.3s ease; +} + +.current-preview.legendary { + animation: float 3s ease-in-out infinite; + filter: drop-shadow(0 0 8px rgba(249, 226, 175, 0.4)); +} + +@keyframes float { + 0% { transform: translateY(0px); } + 50% { transform: translateY(-5px); } + 100% { transform: translateY(0px); } +} + +.equipped-cursor-details { + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; +} + +.equipped-cursor-info { + display: flex; + flex-direction: column; +} + +.equipped-label { + font-size: 12px; + font-weight: 600; + color: rgba(205, 214, 244, 0.6); + letter-spacing: 1px; + margin-bottom: 4px; +} + +.equipped-name { + font-size: 20px; + font-weight: 600; + margin-bottom: 5px; +} + +.equipped-rarity { + font-size: 14px; + font-weight: 500; + opacity: 0.8; +} + +.next-button { + align-self: flex-start; + background-color: rgba(65, 66, 88, 0.8); + border: none; + border-radius: 4px; + padding: 8px 15px; + color: var(--text-color); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.next-button:hover { + background-color: var(--accent-color); + color: #1e1e2e; + transform: translateY(-2px); +} + +/* Library controls section */ +.cursors-controls { + display: flex; + flex-direction: column; + gap: 10px; + padding: 5px 10px 15px 10px; + border-bottom: 1px solid rgba(49, 50, 68, 0.4); +} + +.search-box { + position: relative; + margin-bottom: 8px; +} + +.search-icon { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + color: rgba(205, 214, 244, 0.5); + pointer-events: none; +} + +.search-input { + width: 100%; + background-color: rgba(49, 50, 68, 0.3); + border: 1px solid rgba(49, 50, 68, 0.6); + border-radius: 4px; + color: var(--text-color); + padding: 8px 10px 8px 32px; + font-size: 14px; + outline: none; + transition: all 0.2s; +} + +.search-input:focus { + border-color: var(--accent-color); + box-shadow: 0 0 5px rgba(137, 180, 250, 0.3); +} + +.filter-controls { + display: flex; + gap: 8px; + flex-wrap: wrap; + overflow-x: auto; + padding-bottom: 5px; +} + +.filter-button { + background-color: rgba(49, 50, 68, 0.4); + border: none; + border-radius: 4px; + padding: 5px 10px; + font-size: 12px; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + border-left: 3px solid transparent; +} + +.filter-button:hover { + background-color: rgba(65, 66, 88, 0.6); +} + +.filter-button.active { + background-color: rgba(65, 66, 88, 0.8); + color: var(--accent-color); +} + +/* Rarity-based styling for filter buttons */ +.filter-button.common { + border-left-color: #a6adc8; +} + +.filter-button.uncommon { + border-left-color: #a6e3a1; +} + +.filter-button.rare { + border-left-color: #89b4fa; +} + +.filter-button.epic { + border-left-color: #cba6f7; +} + +.filter-button.legendary { + border-left-color: #f9e2af; +} + +/* Main cursor library */ +.cursor-library { + flex: 1; + display: flex; + flex-direction: column; + overflow-y: auto; + padding: 0 10px; + scrollbar-width: thin; + margin-top: 5px; +} + +.rarity-section { + display: flex; + flex-direction: column; + margin-bottom: 20px; +} + +.rarity-header { + display: flex; + align-items: center; + margin-bottom: 10px; + flex-wrap: nowrap; +} + +.rarity-title { + font-size: 14px; + font-weight: 600; + margin: 0; + white-space: nowrap; + margin-right: 10px; +} + +.rarity-line { + flex: 1; + height: 1px; + opacity: 0.3; +} + +/* Rarity colors */ +.equipped-name.common, .equipped-rarity.common, .rarity-line.common { background-color: #a6adc8; } +.equipped-name.uncommon, .equipped-rarity.uncommon, .rarity-line.uncommon { background-color: #a6e3a1; } +.equipped-name.rare, .equipped-rarity.rare, .rarity-line.rare { background-color: #89b4fa; } +.equipped-name.epic, .equipped-rarity.epic, .rarity-line.epic { background-color: #cba6f7; } +.equipped-name.legendary, .equipped-rarity.legendary, .rarity-line.legendary { background-color: #f9e2af; } + +/* Horizontal scrolling cursor list */ +.cursor-list { + display: flex; + gap: 12px; + overflow-x: auto; + padding: 5px 0 15px 0; + scrollbar-width: thin; + margin-bottom: 5px; +} + +.cursor-card { + min-width: 150px; + max-width: 180px; + background-color: rgba(49, 50, 68, 0.4); + border-radius: 8px; + overflow: hidden; + transition: all 0.2s ease; + cursor: pointer; + border: 1px solid rgba(49, 50, 68, 0.4); + position: relative; + flex: 0 0 auto; +} + +/* Circular rarity indicator in top right */ +.cursor-card::before { + content: ''; + position: absolute; + top: 8px; + right: 8px; + width: 12px; + height: 12px; + border-radius: 50%; + z-index: 5; + background-color: currentColor; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.3); +} + +/* Rarity colors */ +.cursor-card.common { color: #a6adc8; } +.cursor-card.uncommon { color: #a6e3a1; } +.cursor-card.rare { color: #89b4fa; } +.cursor-card.epic { color: #cba6f7; } +.cursor-card.legendary { color: #f9e2af; } + +/* Hover and active states */ +.cursor-card:hover { + transform: translateY(-3px) scale(1.02); + box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15); + border-color: currentColor; + z-index: 5; +} + +.cursor-card:active { + transform: translateY(0) scale(0.98); +} + +.cursor-card.active { + border-color: currentColor; + box-shadow: 0 0 12px currentColor; +} + +/* Preview container */ +.cursor-preview-container { + height: 100px; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(30, 30, 46, 0.5); + padding: 12px; +} + +.cursor-preview-img { + max-width: 80%; + max-height: 80%; + object-fit: contain; + transition: transform 0.3s ease; +} + +.cursor-card:hover .cursor-preview-img { + transform: scale(1.1); +} + +/* Cursor details */ +.cursor-details { + padding: 10px; + display: flex; + flex-direction: column; + gap: 3px; +} + +.cursor-name { + font-weight: 500; + font-size: 13px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.cursor-rarity-badge { + display: inline-block; + font-size: 11px; + text-transform: capitalize; + opacity: 0.7; +} + +/* Animation for legendary items */ +.cursor-card.legendary::before { + animation: legendaryGlow 2s infinite; +} + +@keyframes legendaryGlow { + 0% { opacity: 0.4; } + 50% { opacity: 0.9; } + 100% { opacity: 0.4; } +} + +/* Empty states */ +.no-cursors, .no-results { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(49, 50, 68, 0.2); + border-radius: 8px; + padding: 20px; + text-align: center; + color: var(--text-color); + opacity: 0.7; + font-size: 14px; +} diff --git a/frontend/src/components/content_modules/Cursors.jsx b/frontend/src/components/content_modules/Cursors.jsx new file mode 100644 index 0000000..3bad423 --- /dev/null +++ b/frontend/src/components/content_modules/Cursors.jsx @@ -0,0 +1,144 @@ +import React, { useState } from 'react'; +import './Cursors.css'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSort, faFilter, faSortAmountDown, faSearch } from '@fortawesome/free-solid-svg-icons'; + +function Cursors({ cursors, currentCursorIndex, selectCursor, nextCursor }) { + const [filter, setFilter] = useState('all'); + const [searchTerm, setSearchTerm] = useState(''); + + const currentCursor = cursors.length > 0 && currentCursorIndex < cursors.length + ? cursors[currentCursorIndex] + : null; + + // This will eventually come from backend with proper data + const getRarity = (index) => { + const rarities = ['common', 'uncommon', 'rare', 'epic', 'legendary']; + return rarities[index % rarities.length]; + }; + + // Group cursors by rarity + const cursorsByRarity = cursors.reduce((acc, cursor, index) => { + const rarity = getRarity(index); + if (!acc[rarity]) acc[rarity] = []; + acc[rarity].push({ ...cursor, index, rarity }); + return acc; + }, {}); + + // Filter logic with search + const filteredCursors = Object.entries(cursorsByRarity) + .filter(([rarity, _]) => filter === 'all' || rarity === filter) + .map(([rarity, cursorsOfRarity]) => [ + rarity, + cursorsOfRarity.filter(c => + !searchTerm || c.name.toLowerCase().includes(searchTerm.toLowerCase()) + ) + ]) + .filter(([_, cursors]) => cursors.length > 0); + + return ( +
+ {/* Current cursor equipped display */} + {currentCursor && ( +
+
+ {currentCursor.name} +
+
+
+
EQUIPPED
+
{currentCursor.name}
+
+ {getRarity(currentCursorIndex).toUpperCase()} +
+
+ +
+
+ )} + + {/* Library controls */} +
+
+ + setSearchTerm(e.target.value)} + className="search-input" + /> +
+
+ + {Object.keys(cursorsByRarity).map(rarity => ( + + ))} +
+
+ + {/* Cursor library */} + {cursors.length > 0 ? ( +
+ {filteredCursors.map(([rarity, cursorsInRarity]) => ( +
+
+

+ {rarity.toUpperCase()} ({cursorsInRarity.length}) +

+
+
+ +
+ {cursorsInRarity.map((cursor) => ( +
selectCursor(cursor.index)} + > +
+ {cursor.name} +
+
+ {cursor.name} + {cursor.rarity} +
+
+ ))} +
+
+ ))} + + {filteredCursors.length === 0 && ( +
No cursors match your search
+ )} +
+ ) : ( +
+

No cursors found in assets directory

+
+ )} +
+ ); +} + +export default Cursors; diff --git a/frontend/src/components/content_modules/Inventory.css b/frontend/src/components/content_modules/Inventory.css new file mode 100644 index 0000000..dfd6619 --- /dev/null +++ b/frontend/src/components/content_modules/Inventory.css @@ -0,0 +1,359 @@ +/* Inventory Module Styles */ +.inventory-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +} + +/* Search and filter controls */ +.inventory-controls { + display: flex; + justify-content: space-between; + padding: 8px 10px; + background-color: rgba(49, 50, 68, 0.4); + border-radius: 8px; + margin-top: 2%; + border: 1px solid rgba(49, 50, 68, 0.8); +} + +.search-bar { + flex: 1; + margin-right: 10px; +} + +.search-bar input { + width: 100%; + padding: 8px 12px; + border-radius: 4px; + border: 1px solid rgba(49, 50, 68, 0.8); + background-color: rgba(30, 30, 46, 0.7); + color: #cdd6f4; + font-size: 14px; +} + +.search-bar input:focus { + outline: none; + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.3); +} + +.search-bar input::placeholder { + color: rgba(205, 214, 244, 0.5); +} + +.rarity-filter { + width: 50px; + position: relative; + display: flex; + align-items: center; +} + +.rarity-filter select { + width: 100%; + padding: 8px; + border-radius: 4px; + border: 1px solid rgba(49, 50, 68, 0.8); + background-color: rgba(30, 30, 46, 0.7); + color: transparent; + text-indent: -9999px; + font-size: 14px; + appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 4px center; + cursor: pointer; +} + +.rarity-filter .rarity-circle { + position: absolute; + aspect-ratio: 1/1; + width: 30%; + margin-left: 8px; + + + border-radius: 50%; + pointer-events: none; + z-index: 1; +} + +/* Rarity colors for circle indicators */ +.rarity-circle.all { + background: linear-gradient(45deg, #a6adc8, #a6e3a1, #89b4fa, #cba6f7, #f9e2af); +} +.rarity-circle.common { background-color: #a6adc8; } +.rarity-circle.uncommon { background-color: #a6e3a1; } +.rarity-circle.rare { background-color: #89b4fa; } +.rarity-circle.epic { background-color: #cba6f7; } +.rarity-circle.legendary { + background-color: #f9e2af; + box-shadow: 0 0 5px #f9e2af; +} + +/* Style dropdown options to show text and circles */ +.rarity-filter select option { + color: white; + position: relative; + +} + +.rarity-filter .rarity-option { + display: flex; + align-items: center; + gap: 8px; + color: white; +} + +.rarity-filter select:focus { + outline: none; + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.3); +} + +/* Equipped cursor display */ +.equipped-cursor { + display: flex; + background-color: rgba(49, 50, 68); + border-radius: 8px; + padding: 10px; + min-height: 25%; + max-height: 25%; + border: 1px solid rgba(49, 50, 68, 0.8); + margin-top: 10px; +} + +.equipped-cursor-image { + aspect-ratio: 1/1; + + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(30, 30, 46, 0.5); + border-radius: 6px; + border: 1px solid rgba(49, 50, 68, 0.6); + margin-right: 10px; +} + +.equipped-image { + max-width: 80%; + max-height: 80%; + object-fit: contain; +} + +.equipped-cursor-info { + display: flex; + flex-direction: column; +} + +.equipped-cursor-info h3 { + font-size: 14px; + margin: 0 0 3px 0; + opacity: 0.8; +} + +.equipped-cursor-name { + font-size: 16px; + font-weight: 500; +} + +.equipped-cursor-rarity { + font-size: 12px; + opacity: 0.8; +} + +/* Minecraft-style inventory */ +.cute-inventory { + min-height: 58%; + max-height: 58%; + display: flex; + overflow-y: auto; + flex-direction: column; + align-items: center; + background-color: rgba(30, 30, 46, 0.3); + border-radius: 8px; + box-sizing: border-box; + margin-top: 2%; + border: 1px solid rgba(49, 50, 68, 0.6); +} + +.inventory-grid { + display: grid; + box-sizing: border-box; + width: 100%; + grid-template-columns: repeat(auto-fill, minmax(9vh, 1fr)); +} + +.inventory-slot { + aspect-ratio: 1; + background-color: rgba(49, 50, 68, 0.5); + border: 2px solid rgba(30, 30, 46, 0.8); + border-radius: 1vh; + display: flex; + align-items: center; + justify-content: center; + margin: 0.5vh; + position: relative; + transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); + overflow: hidden; +} + +/* Rarity indicator as small circle in top right */ +.inventory-slot.has-item { + cursor: pointer; + position: relative; + border-color: rgba(30, 30, 46, 0.8); +} + +/* Small circle rarity indicator in top right */ +.inventory-slot.has-item::after { + content: ''; + position: absolute; + top: 5px; + right: 5px; + width: 10px; + height: 10px; + border-radius: 50%; + z-index: 5; +} + +.inventory-slot.has-item.common::after { + background-color: #a6adc8; +} + +.inventory-slot.has-item.uncommon::after { + background-color: #a6e3a1; +} + +.inventory-slot.has-item.rare::after { + background-color: #89b4fa; +} + +.inventory-slot.has-item.epic::after { + background-color: #cba6f7; +} + +.inventory-slot.has-item.legendary::after { + background-color: #f9e2af; + box-shadow: 0 0 5px rgba(249, 226, 175, 0.8); + animation: pulseDot 2s infinite; +} + +@keyframes pulseDot { + 0% { transform: scale(1); box-shadow: 0 0 5px rgba(249, 226, 175, 0.5); } + 50% { transform: scale(1.2); box-shadow: 0 0 8px rgba(249, 226, 175, 0.8); } + 100% { transform: scale(1); box-shadow: 0 0 5px rgba(249, 226, 175, 0.5); } +} + +.inventory-slot.has-item:hover { + transform: scale(1.08); + z-index: 5; +} + +.inventory-slot.has-item:active { + transform: scale(0.95); +} + +/* Selected item overrides rarity glow */ +.inventory-slot.selected { + border-color: var(--accent-color) !important; + box-shadow: 0 0 12px var(--accent-color) !important; + z-index: 10; + animation: selectedPulse 1.5s infinite; +} + +@keyframes selectedPulse { + 0% { box-shadow: 0 0 8px var(--accent-color) !important; } + 50% { box-shadow: 0 0 15px var(--accent-color) !important; } + 100% { box-shadow: 0 0 8px var(--accent-color) !important; } +} + +/* Shine effect when clicked */ +.inventory-slot.has-item:active::after { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 70%); + opacity: 0.6; + transform: scale(0); + animation: clickRipple 0.5s ease-out; +} + +@keyframes clickRipple { + 0% { transform: scale(0); opacity: 0.6; } + 100% { transform: scale(1); opacity: 0; } +} + +.inventory-item { + width: 70%; + height: 70%; + object-fit: contain; +} + +/* Tooltip that appears on hover */ +.inventory-tooltip { + position: absolute; + top: -40px; + left: 50%; + transform: translateX(-50%); + background-color: rgba(30, 30, 46, 0.95); + border-radius: 4px; + padding: 4px 8px; + min-width: 80px; + z-index: 10; + display: none; + flex-direction: column; + align-items: center; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); + pointer-events: none; + white-space: nowrap; +} + +.inventory-slot:hover .inventory-tooltip { + display: flex; +} + +.tooltip-name { + font-size: 12px; + font-weight: 500; +} + +.tooltip-rarity { + font-size: 10px; + opacity: 0.8; +} + +/* Rarity colors */ +.common, .common.equipped-cursor-name, .tooltip-name.common { color: #a6adc8; } +.uncommon, .uncommon.equipped-cursor-name, .tooltip-name.uncommon { color: #a6e3a1; } +.rare, .rare.equipped-cursor-name, .tooltip-name.rare { color: #89b4fa; } +.epic, .epic.equipped-cursor-name, .tooltip-name.epic { color: #cba6f7; } +.legendary, .legendary.equipped-cursor-name, .tooltip-name.legendary { color: #f9e2af; } + +/* Legendary glow effect */ +.inventory-slot.selected .inventory-item.legendary { + filter: drop-shadow(0 0 3px #f9e2af); + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { filter: drop-shadow(0 0 2px rgba(249, 226, 175, 0.5)); } + 50% { filter: drop-shadow(0 0 4px rgba(249, 226, 175, 0.8)); } + 100% { filter: drop-shadow(0 0 2px rgba(249, 226, 175, 0.5)); } +} + +.inventory-footer { + width: 100%; + display: flex; + justify-content: center; + padding: 5px 0; +} + +.cursor-count { + font-size: 12px; + color: var(--text-color); + opacity: 0.7; +} + diff --git a/frontend/src/components/content_modules/Inventory.jsx b/frontend/src/components/content_modules/Inventory.jsx new file mode 100644 index 0000000..435dcc1 --- /dev/null +++ b/frontend/src/components/content_modules/Inventory.jsx @@ -0,0 +1,157 @@ +import React, { useState, useEffect } from 'react'; +import './Inventory.css'; + +function Inventory({ cursors, currentCursorIndex, selectCursor }) { + const [searchQuery, setSearchQuery] = useState(''); + const [selectedRarity, setSelectedRarity] = useState('all'); + const [filteredSlots, setFilteredSlots] = useState([]); + // Minecraft inventory typically has 27 slots (9x3) + const totalSlots = 36; // 9x4 grid + + // This will eventually come from backend with proper data + const cursorRarities = { + common: 'Common', + uncommon: 'Uncommon', + rare: 'Rare', + epic: 'Epic', + legendary: 'Legendary' + }; + + // Assign mock rarities to cursors for demonstration + const getCursorRarity = (index) => { + const rarities = Object.keys(cursorRarities); + return rarities[index % rarities.length]; + }; + + // Get currently selected cursor + const currentCursor = cursors.length > 0 && currentCursorIndex < cursors.length + ? cursors[currentCursorIndex] + : null; + + // Create array of slots for the inventory grid + const inventorySlots = Array(totalSlots).fill(null).map((_, slotIndex) => { + // Map cursor to slot if available + const cursorIndex = slotIndex < cursors.length ? slotIndex : null; + const cursor = cursorIndex !== null ? cursors[cursorIndex] : null; + const rarity = cursor ? getCursorRarity(cursorIndex) : null; + + return { + slotIndex, + cursor, + cursorIndex, + rarity + }; + }); + + // Apply filters to inventory slots + useEffect(() => { + // First, filter the slots that match our criteria + let filtered = inventorySlots.filter(slot => { + // Filter by search query + const matchesSearch = !searchQuery || + (slot.cursor && slot.cursor.name.toLowerCase().includes(searchQuery.toLowerCase())); + + // Filter by rarity + const matchesRarity = selectedRarity === 'all' || + (slot.rarity && slot.rarity === selectedRarity); + + return matchesSearch && matchesRarity; + }); + + // Create a new array for the filtered slots, prioritizing slots with items + const filledSlots = filtered.filter(slot => slot.cursor); + const emptySlots = Array(totalSlots - filledSlots.length).fill(null) + .map((_, index) => ({ + slotIndex: filledSlots.length + index, + cursor: null, + cursorIndex: null, + rarity: null + })); + + // Combine filled slots at the beginning and empty ones at the end + const result = [...filledSlots, ...emptySlots]; + + setFilteredSlots(result); + }, [searchQuery, selectedRarity, inventorySlots]); + + return ( +
+ {/* Search and Filter Controls */} + + {/* Equipped cursor display */} + {currentCursor && ( +
+
+ {currentCursor.name} +
+
+

Equipped

+ + {currentCursor.name} + + + {cursorRarities[getCursorRarity(currentCursorIndex)]} + +
+
+ )} +
+
+ setSearchQuery(e.target.value)} + /> +
+
+
+ +
+
+ {/* Minecraft-like inventory grid */} +
+
+ {filteredSlots.map((slot) => ( +
slot.cursor && selectCursor(slot.cursorIndex)} + > + {slot.cursor && ( + <> + {slot.cursor.name} +
+ {slot.cursor.name} + {cursorRarities[slot.rarity]} +
+ + )} +
+ ))} +
+
+ + +
+ ); +} + +export default Inventory; diff --git a/frontend/src/components/content_modules/Settings.css b/frontend/src/components/content_modules/Settings.css new file mode 100644 index 0000000..4c6cd4f --- /dev/null +++ b/frontend/src/components/content_modules/Settings.css @@ -0,0 +1,75 @@ +/* Settings Module Styles */ +.settings-container { + width: 100%; + display: flex; + flex-direction: column; + gap: 15px; +} + +.settings-content { + display: flex; + flex-direction: column; + gap: 15px; +} + +.settings-group { + background-color: var(--button-bg, #313244); + border-radius: 8px; + padding: 15px; + color: var(--text-color, #cdd6f4); +} + +.settings-group h3 { + font-size: 16px; + font-weight: 500; + margin: 0 0 12px 0; + color: var(--accent-color, #89b4fa); +} + +.setting-item { + padding: 8px 0; + display: flex; + align-items: center; +} + +.setting-item label { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + font-size: 14px; +} + +.setting-item input[type="checkbox"] { + width: 16px; + height: 16px; + margin: 0; + appearance: none; + background-color: var(--modal-bg, #181825); + border: 1px solid var(--text-color, #cdd6f4); + border-radius: 3px; + outline: none; + cursor: pointer; + position: relative; +} + +.setting-item input[type="checkbox"]:checked { + background-color: var(--accent-color, #89b4fa); + border-color: var(--accent-color, #89b4fa); +} + +.setting-item input[type="checkbox"]:checked::after { + content: '✓'; + font-size: 12px; + color: var(--bg-color, #1e1e2e); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.settings-group p { + margin: 5px 0; + font-size: 14px; + opacity: 0.8; +} diff --git a/frontend/src/components/content_modules/Settings.jsx b/frontend/src/components/content_modules/Settings.jsx new file mode 100644 index 0000000..73beb2b --- /dev/null +++ b/frontend/src/components/content_modules/Settings.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import './Settings.css'; + +function Settings() { + return ( +
+

Settings

+
+
+

Application Settings

+
+ +
+
+ +
+
+ +
+

Cursor Settings

+
+ +
+
+ +
+

About

+

Cursor Rush v0.1.0

+

A cross-platform application for customizing your cursor.

+
+
+
+ ); +} + +export default Settings; diff --git a/frontend/src/cursors/default.png b/frontend/src/cursors/default.png new file mode 100644 index 0000000..8643897 Binary files /dev/null and b/frontend/src/cursors/default.png differ diff --git a/frontend/src/cursors/icon.png b/frontend/src/cursors/icon.png new file mode 100644 index 0000000..8643897 Binary files /dev/null and b/frontend/src/cursors/icon.png differ diff --git a/frontend/src/cursors/red.png b/frontend/src/cursors/red.png new file mode 100644 index 0000000..fd5c6ad Binary files /dev/null and b/frontend/src/cursors/red.png differ diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..08a3ac9 --- /dev/null +++ b/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/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 0000000..774fd95 --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + , +); diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/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()], +})