diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3b66410 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "git.ignoreLimitWarning": true +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index adab236..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "cursor_rush" -version = "0.1.0" -edition = "2021" - -[dependencies] -iced = { version = "0.13.1", default-features = true, features = ["image", "advanced"] } # GUI, bundles wgpu + winit -tray-icon = "0.20.1" # cross‑platform system tray -image = "0.24.7" # Image processing -walkdir = "2.4.0" # Directory traversal -global-hotkey = "0.4.2" # Global hotkey registration -lazy_static = "1.4.0" # Thread-safe static initialization -steamworks = { version = "0.11.0", features = ["raw-bindings"] } -anyhow = "1.0.98" -once_cell = "1.18.0" # Thread-safe lazy initialized global variables - -# Platform-specific dependencies -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.9", features = ["winuser", "wingdi", "consoleapi"] } # Windows API -ctor = "0.2.4" # Constructor/destructor attributes - -[target.'cfg(target_os = "macos")'.dependencies] -objc = "0.2.7" # Objective-C bindings -cocoa = "0.24.1" # macOS Cocoa bindings - -[target.'cfg(target_os = "linux")'.dependencies] -x11 = { version = "2.21.0", features = ["xlib"] } # X11 bindings diff --git a/README.md b/README.md index b3e775b..eb77e07 100644 --- a/README.md +++ b/README.md @@ -11,47 +11,31 @@ A cross-platform application for customizing your cursor with gameified logic. - System tray integration with context menu - Global hotkey support - Steam integration for item drops +- Tauri-based frontend with React ## Project Structure ``` -src/ -├── app/ # Application core logic -│ ├── mod.rs # Main app module exports -│ ├── state.rs # Application state management -│ ├── messages.rs # Message definitions -│ └── cursor.rs # Cursor-related functionality -├── ui/ # User interface components -│ ├── mod.rs # UI module exports -│ ├── view.rs # View rendering logic -│ └── tray.rs # System tray functionality -├── platform/ # Platform-specific implementations -│ ├── mod.rs # Platform module exports -│ ├── windows/ # Windows-specific code -│ │ ├── mod.rs # Windows module exports -│ │ ├── types.rs # Windows type definitions -│ │ ├── state.rs # Windows state management -│ │ └── cursor.rs # Windows cursor handling -│ ├── macos/ # macOS-specific code (in development) -│ │ ├── mod.rs # macOS module exports -│ │ ├── state.rs # macOS state management -│ │ └── cursor.rs # macOS cursor handling -│ ├── linux/ # Linux-specific code (in development) -│ │ ├── mod.rs # Linux module exports -│ │ ├── state.rs # Linux state management -│ │ └── cursor.rs # Linux cursor handling -│ └── unsupported/ # Fallback for unsupported platforms -│ ├── mod.rs # Unsupported module exports -│ ├── state.rs # Unsupported state management -│ └── cursor.rs # Unsupported cursor handling -├── integrations/ # External integrations -│ ├── mod.rs # Integrations module exports -│ └── steam.rs # Steam integration -├── utils/ # Utility functions and helpers -│ ├── mod.rs # Utils module exports -│ ├── config.rs # Configuration management -│ └── logging.rs # Logging utilities -└── main.rs # Application entry point +frontend/ +├── src/ # React frontend code +│ ├── App.jsx # Main React component +│ ├── components/ # UI components +│ └── cursors/ # Cursor image files +├── src-tauri/ # Rust backend code +│ ├── src/ # Rust source code +│ │ ├── lib.rs # Main library code +│ │ ├── main.rs # Application entry point +│ │ ├── integrations/ # External integrations +│ │ │ ├── mod.rs # Integrations module exports +│ │ │ └── steam.rs # Steam integration +│ │ └── platform/ # Platform-specific code +│ │ ├── mod.rs # Platform module exports +│ │ ├── types.rs # Common type definitions +│ │ ├── windows/ # Windows-specific code +│ │ ├── macos/ # macOS-specific code +│ │ └── linux/ # Linux-specific code +│ └── Cargo.toml # Rust dependencies +└── package.json # npm dependencies ``` ## Building @@ -60,6 +44,8 @@ src/ - Rust 1.70.0 or later - Cargo +- Node.js 16.0.0 or later +- npm 7.0.0 or later - Platform-specific dependencies: - Windows: None (fully supported) - macOS: Development in progress @@ -68,41 +54,49 @@ src/ ### Dependencies The application uses the following major dependencies: -- iced 0.13.1 - GUI framework -- tray-icon 0.20.1 - System tray functionality +- Tauri 1.6.0 - Cross-platform framework +- React - Frontend framework - image 0.24.7 - Image processing -- global-hotkey 0.4.2 - Global hotkey registration -- steamworks 0.11.0 - Steam integration +- winapi 0.3.9 - Windows API bindings +- once_cell 1.19.0 - Lazy initialization ### Build Commands ```bash -# Debug build -cargo build +# Navigate to the frontend directory +cd frontend -# Release build -cargo build --release +# Install npm dependencies +npm install + +# Run in development mode +npm run tauri dev + +# Build for production +npm run tauri build ``` ## Usage -1. Place your cursor PNG files in the `assets` directory (PNG format only) -2. Run the application using `cargo run` or the compiled executable -3. The application will load all PNG files from the assets directory -4. Select a cursor from the list in the UI -5. The application will replace your system cursor with the selected image -6. When closing the window, the application minimizes to the system tray -7. To fully exit, use the system tray menu or press the exit hotkey +1. Make sure you have Node.js and npm installed +2. Navigate to the frontend directory: `cd frontend` +3. Install dependencies if you haven't already: `npm install` +4. Run the application in development mode: `npm run tauri dev` +5. The application will start with a React frontend and Rust backend +6. Use the UI to browse and select cursors +7. The application will replace your system cursor with the selected image +8. To exit, use the "Quit" button in the application or from the system tray ### Custom Cursors -The application comes with two sample cursors in the `assets` directory: +The application comes with sample cursors: - `default.png` - A simple default cursor +- `icon.png` - An icon cursor - `red.png` - A red-colored cursor To add your own cursors: 1. Create PNG images with transparent backgrounds -2. Place them in the `assets` directory +2. Place them in the `frontend/src/cursors` directory 3. Restart the application to load the new cursors 4. The cursor name in the UI will be derived from the filename @@ -147,6 +141,7 @@ Contributions to Cursor Rush are welcome! Here are some ways you can contribute: - Submit pull requests with bug fixes or new features - Help with documentation - Contribute to macOS and Linux platform support +- Improve the Tauri integration ## License 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..39137b9 --- /dev/null +++ b/frontend/src-tauri/Cargo.toml @@ -0,0 +1,28 @@ +[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", "system-tray", "window-minimize", "window-hide", "window-show", "window-close"] } +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" +steamworks = "0.10.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/src/integrations/mod.rs b/frontend/src-tauri/src/integrations/mod.rs similarity index 100% rename from src/integrations/mod.rs rename to frontend/src-tauri/src/integrations/mod.rs diff --git a/frontend/src-tauri/src/integrations/steam.rs b/frontend/src-tauri/src/integrations/steam.rs new file mode 100644 index 0000000..876c818 --- /dev/null +++ b/frontend/src-tauri/src/integrations/steam.rs @@ -0,0 +1,26 @@ +// Steam integration + +use steamworks::{AppId, Client, SingleClient}; + +#[allow(dead_code)] +pub struct Steam { + client: Client, + single: SingleClient, +} + +impl Steam { + pub fn new() -> Self { + let (client, single) = Client::init_app(AppId(1430270)).unwrap(); + Self { client, single } + } + + #[allow(dead_code)] + pub fn drop_item(&self) { + // Placeholder for item drop functionality + // The actual implementation would use the Steam API to trigger an item drop + println!("Steam item drop triggered (placeholder)"); + + // In a real implementation with the correct API version, we would use: + // self.client.get_inventory().trigger_item_drop(item_def_id); + } +} diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs new file mode 100644 index 0000000..8ee9422 --- /dev/null +++ b/frontend/src-tauri/src/lib.rs @@ -0,0 +1,432 @@ +use std::sync::Mutex; +use serde::{Serialize, Deserialize}; +use tauri::{Manager, AppHandle, CustomMenuItem, SystemTray, SystemTrayMenu, SystemTrayMenuItem, SystemTrayEvent, WindowEvent}; +use std::path::{Path, PathBuf}; +use std::env; + +mod platform; +mod integrations; + +// 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); + }); +} + +// Command to minimize the window to tray +#[tauri::command] +fn minimize_to_tray(app_handle: AppHandle) -> Result<(), String> { + println!("Minimizing to tray"); + + // Get the main window + let window = app_handle.get_window("main") + .ok_or_else(|| "Failed to get main window".to_string())?; + + // Hide the window + window.hide().map_err(|e| format!("Failed to hide window: {}", e))?; + + // The tray should already be visible as configured in tauri.conf.json + println!("Window hidden, application should now be accessible from the system tray"); + + Ok(()) +} + +// 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, + }; + + // Create system tray menu + let next_cursor_item = CustomMenuItem::new("next_cursor".to_string(), "Next cursor"); + let show_window_item = CustomMenuItem::new("show_window".to_string(), "Show window"); + let quit_item = CustomMenuItem::new("quit".to_string(), "Quit"); + + let tray_menu = SystemTrayMenu::new() + .add_item(next_cursor_item) + .add_item(show_window_item) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(quit_item); + + let system_tray = SystemTray::new().with_menu(tray_menu); + + // This is one of the simplest ways to initialize Tauri + tauri::Builder::default() + .manage(AppStateWrapper(Mutex::new(app_state))) + .system_tray(system_tray) + .on_system_tray_event(|app, event| match event { + SystemTrayEvent::LeftClick { .. } => { + println!("System tray received a left click"); + if let Some(window) = app.get_window("main") { + match window.is_visible() { + Ok(visible) => { + if visible { + println!("Window is already visible, focusing it"); + let _ = window.set_focus(); + } else { + println!("Window is hidden, showing it"); + if let Err(e) = window.show() { + eprintln!("Failed to show window: {}", e); + } + if let Err(e) = window.set_focus() { + eprintln!("Failed to focus window: {}", e); + } + } + }, + Err(e) => { + eprintln!("Failed to check window visibility: {}", e); + // Try to show it anyway + if let Err(e) = window.show() { + eprintln!("Failed to show window: {}", e); + } + } + } + } else { + eprintln!("Main window not found"); + } + } + SystemTrayEvent::MenuItemClick { id, .. } => { + println!("Menu item clicked: {}", id); + match id.as_str() { + "next_cursor" => { + println!("Next cursor requested from tray"); + let state = app.state::(); + 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]; + + if Path::new(&cursor.path).exists() { + if let Err(e) = platform::set_system_cursor(&cursor.path) { + eprintln!("Error applying cursor from tray: {}", e); + } else { + // Emit an event to notify the frontend + let _ = app.emit_all("cursor_changed", app_state.current_index); + } + } + } + } + "show_window" => { + println!("Show window requested from tray"); + if let Some(window) = app.get_window("main") { + if let Err(e) = window.show() { + eprintln!("Failed to show window: {}", e); + } + if let Err(e) = window.set_focus() { + eprintln!("Failed to focus window: {}", e); + } + } else { + eprintln!("Main window not found"); + } + } + "quit" => { + println!("Quit requested from tray"); + // 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), + } + app.exit(0); + } + _ => {} + } + } + _ => {} + }) + .on_window_event(move |event| { + match event.event() { + WindowEvent::CloseRequested { api, .. } => { + // Prevent the default close action + api.prevent_close(); + + // Get the window and hide it + let window = event.window(); + println!("Window close requested in backend handler, hiding window"); + + // Hide the window instead of closing it + if let Err(e) = window.hide() { + eprintln!("Failed to hide window: {}", e); + } else { + println!("Window hidden successfully, app is now in system tray"); + } + } + _ => {} + } + }) + .invoke_handler(tauri::generate_handler![ + get_cursors, + get_current_cursor_index, + select_cursor, + next_cursor, + quit_app, + minimize_to_tray + ]) + .setup(|_app| { + // Register for events or perform additional setup + println!("Tauri application setup complete"); + + // We're not setting up a window close handler here anymore + // The frontend will handle this based on the minimize-to-tray setting + + 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/src/platform/windows/cursor.rs b/frontend/src-tauri/src/platform/windows/cursor.rs similarity index 92% rename from src/platform/windows/cursor.rs rename to frontend/src-tauri/src/platform/windows/cursor.rs index 0d1e3bd..4792105 100644 --- a/src/platform/windows/cursor.rs +++ b/frontend/src-tauri/src/platform/windows/cursor.rs @@ -68,15 +68,17 @@ unsafe fn create_cursor_from_image(image: &RgbaImage) -> Option { bitmap_info.bmiHeader.biCompression = BI_RGB; // Create the color DIB section - let mut color_bits_ptr: *mut std::ffi::c_void = null_mut(); + 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 color_bits_ptr, + &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"); @@ -88,15 +90,17 @@ unsafe fn create_cursor_from_image(image: &RgbaImage) -> Option { // 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 std::ffi::c_void = null_mut(); + 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 mask_bits_ptr, + &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"); @@ -328,20 +332,23 @@ pub fn hide_system_cursor() -> bool { } // Update the cursor image using CursorInfo (with caching) -pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool { +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 = if let Some(ref cached_image) = cursor_info.image { + let image: image::RgbaImage = if let Some(ref cached_image) = cursor_info.image { println!("Using cached image for cursor: {}", cursor_info.name); - cached_image.clone() + 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 - cursor_info.image = Some(rgba.clone()); + // 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) => { @@ -381,8 +388,10 @@ pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool // Update the cached cursor in CursorInfo #[cfg(windows)] { - // Always update the cached cursor to the fresh one - cursor_info.win_cursor = Some(new_cursor_wrapper); + // 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 { @@ -397,12 +406,14 @@ pub fn update_cursor_info(cursor_info: &mut crate::platform::CursorInfo) -> bool // 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.0.is_null() { + 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(); - *cursor_guard = Some(cached_cursor); - SetCursor(cached_cursor.0); + // Convert WinCursorWrapper to SyncHCURSOR + let sync_cursor = super::types::SyncHCURSOR(cached_cursor.handle); + *cursor_guard = Some(sync_cursor); + SetCursor(cached_cursor.handle); return true; } } 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/src/platform/windows/state.rs b/frontend/src-tauri/src/platform/windows/state.rs similarity index 100% rename from src/platform/windows/state.rs rename to frontend/src-tauri/src/platform/windows/state.rs diff --git a/src/platform/windows/types.rs b/frontend/src-tauri/src/platform/windows/types.rs similarity index 100% rename from src/platform/windows/types.rs rename to frontend/src-tauri/src/platform/windows/types.rs diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json new file mode 100644 index 0000000..4981592 --- /dev/null +++ b/frontend/src-tauri/tauri.conf.json @@ -0,0 +1,72 @@ +{ + "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, + "minimize": true, + "hide": true, + "show": true, + "close": true + }, + "shell": { + "all": false, + "open": true + }, + "fs": { + "all": false, + "scope": [ + "$APP/cursors/*" + ] + } + }, + "systemTray": { + "iconPath": "icons/icon.png", + "iconAsTemplate": false, + "menuOnLeftClick": false, + "title": "Cursor Rush" + }, + "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": [ + { + "label": "main", + "title": "Cursor Rush", + "width": 310, + "height": 600, + "resizable": true, + "fullscreen": false, + "decorations": false, + "transparent": true, + "visible": 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..00caadf --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,192 @@ +import { useState, useEffect, useCallback } 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); + const [settings, setSettings] = useState({ + minimizeToTrayOnClose: true, + startWithWindows: false, + autoApplyCursorOnSelection: true + }); + + // Function to minimize app to tray + const minimizeToTray = useCallback(async () => { + try { + console.log('Minimizing app to tray'); + // First hide the window to ensure it disappears immediately + await appWindow.hide(); + console.log('Window hidden, now ensuring tray is visible'); + + // Then call the backend to ensure the tray is visible + await invoke("minimize_to_tray"); + console.log('minimize_to_tray command completed successfully'); + } catch (error) { + console.error("Failed to minimize to tray:", error); + // Fallback to window API if the invoke fails + try { + console.log('Trying fallback hide'); + await appWindow.hide(); + console.log('Fallback hide succeeded'); + } catch (fallbackError) { + console.error("Fallback hide also failed:", fallbackError); + } + } + }, []); + + // Load cursors on mount and set up event listeners + 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(); + }, []); + + // Set up window close event listener + useEffect(() => { + const setupCloseHandler = async () => { + const unlisten = await appWindow.onCloseRequested(async (event) => { + console.log('Window close requested'); + + if (settings.minimizeToTrayOnClose) { + console.log('Minimize to tray setting is enabled, preventing close and hiding instead'); + // Prevent the window from closing + event.preventDefault(); + + // Hide the window instead + try { + console.log('Calling minimizeToTray function'); + await minimizeToTray(); + console.log('Window should now be hidden and app should be in system tray'); + } catch (error) { + console.error('Failed to minimize to tray on close:', error); + } + } else { + console.log('Minimize to tray setting is disabled, allowing window to close'); + // Let the window close normally, but we need to call quit_app to ensure proper cleanup + try { + await invoke("quit_app"); + } catch (error) { + console.error("Failed to quit app:", error); + } + } + }); + + return unlisten; + }; + + let unlistenFn; + setupCloseHandler().then(unlisten => { + unlistenFn = unlisten; + console.log('Close handler set up successfully'); + }).catch(err => console.error('Failed to set up close handler:', err)); + + // Clean up event listener on unmount + return () => { + if (unlistenFn) { + unlistenFn(); + } + }; + }, [settings.minimizeToTrayOnClose, minimizeToTray]); + + // 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 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..6d5be4e --- /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, settings, setSettings }) { + // 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..891c749 --- /dev/null +++ b/frontend/src/components/Header.jsx @@ -0,0 +1,69 @@ +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, settings }) { + // 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' } + ]; + + // Handle close button click based on settings + const handleCloseClick = () => { + if (settings && settings.minimizeToTrayOnClose) { + console.log('Close button clicked, minimizing to tray based on settings'); + minimizeToTray(); + } else { + console.log('Close button clicked, quitting application based on settings'); + quitApp(); + } + }; + + 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..6f4a6da --- /dev/null +++ b/frontend/src/components/content_modules/Settings.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import './Settings.css'; + +function Settings({ settings, setSettings }) { + // Handler for checkbox changes + const handleSettingChange = (settingName) => { + setSettings({ + ...settings, + [settingName]: !settings[settingName] + }); + }; + + 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()], +}) diff --git a/src/app/cursor.rs b/src/app/cursor.rs deleted file mode 100644 index 9e4a08d..0000000 --- a/src/app/cursor.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Cursor management functionality - -use std::path::{Path, PathBuf}; -use walkdir::WalkDir; -use ::image::open as image_open; -use ::image::GenericImageView; - -use crate::platform; - -// Define a struct to hold cursor information -#[derive(Clone)] -pub struct CursorInfo { - pub name: String, - pub path: PathBuf, - pub image: Option<::image::RgbaImage>, - #[cfg(windows)] - pub win_cursor: Option, -} - -// Function to load all PNG files from the assets directory -pub fn load_cursor_files() -> Vec { - let mut cursors = Vec::new(); - - println!("Looking for cursor PNG files in assets directory..."); - - // Check if assets directory exists - if !std::path::Path::new("assets").exists() { - println!("Assets directory not found!"); - return cursors; - } - - for entry in WalkDir::new("assets").into_iter().filter_map(|e| e.ok()) { - let path = entry.path(); - println!("Found file: {}", path.display()); - if path.is_file() && path.extension().map_or(false, |ext| ext == "png") { - let name = path.file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("Unknown") - .to_string(); - - println!("Adding cursor: {} at path {}", name, path.display()); - - // Try to load the image during initialization - let image = match image_open(path) { - Ok(img) => { - println!("Successfully pre-loaded image for cursor: {}", name); - Some(img.to_rgba8()) - }, - Err(e) => { - eprintln!("Failed to pre-load image for cursor {}: {}", name, e); - None - } - }; - - cursors.push(CursorInfo { - name, - path: path.to_path_buf(), - image, - #[cfg(windows)] - win_cursor: None, // Windows cursor will be created on first use - }); - } - } - - println!("Found {} cursor PNG files", cursors.len()); - - // Sort cursors by name for consistent ordering - cursors.sort_by(|a, b| a.name.cmp(&b.name)); - cursors -} - -// Function to validate a cursor image -pub fn validate_cursor_image(path: &Path) -> bool { - // Load the image to validate it - match image_open(path) { - Ok(img) => { - let (width, height) = img.dimensions(); - - // Ensure reasonable dimensions - if width > 128 || height > 128 || width == 0 || height == 0 { - eprintln!("Image dimensions not suitable for cursor: {}x{}", width, height); - return false; - } - - true - }, - Err(e) => { - eprintln!("Failed to open image {}: {}", path.display(), e); - false - }, - } -} - -// Apply the cursor from CursorInfo -pub fn apply_cursor_info(cursor_info: &mut CursorInfo) -> bool { - // Validate the cursor image if not already cached - if cursor_info.image.is_none() && !validate_cursor_image(&cursor_info.path) { - return false; - } - - // Use platform-specific cursor image update with the cursor info - if platform::update_cursor_info(cursor_info) { - println!("Successfully updated cursor image: {}", cursor_info.name); - return true; - } else { - eprintln!("Failed to update cursor image: {}", cursor_info.name); - return false; - } -} - - diff --git a/src/app/messages.rs b/src/app/messages.rs deleted file mode 100644 index fd84eff..0000000 --- a/src/app/messages.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Application messages - -// Messages that can be sent in our application -#[derive(Debug, Clone)] -pub enum Message { - NextCursor, - SelectCursor(usize), - Quit, - TrayEvent, // Used as a no-op message - CloseRequested(iced::window::Id), // Message for window close requests with the real window ID - ShowWindow, // Message to show the window from tray - WindowHidden, // Message indicating window was successfully hidden - WindowShown, // Message indicating window was successfully shown -} diff --git a/src/app/mod.rs b/src/app/mod.rs deleted file mode 100644 index ab255e7..0000000 --- a/src/app/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -// Application core module -// Contains the main application state and logic - -mod state; -mod messages; -mod cursor; - -pub use state::CursorRush; -pub use messages::Message; -pub use cursor::{CursorInfo, load_cursor_files, apply_cursor_info}; -pub use state::SharedState; - -use iced::{Subscription, Task}; -use once_cell::sync::Lazy; -use iced::futures::channel::mpsc; - -// Global channel for tray events -pub static TRAY_SENDER: Lazy>>> = - Lazy::new(|| std::sync::Mutex::new(None)); - -// Boot function to initialize the application -pub fn boot() -> (CursorRush, Task) { - state::boot_app() -} - -// Update function to handle messages -pub fn update(state: &mut CursorRush, message: Message) -> Task { - state::update_app(state, message) -} - -// Top-level subscription function to listen for events -pub fn subscription(_state: &CursorRush) -> Subscription { - state::app_subscription(_state) -} - -// Helper function to hide a window to the tray -pub fn hide_window_to_tray(window_id: iced::window::Id) -> Task { - // Use ONLY the iced method for hiding. Platform specific might interfere. - let hide_task: Task<()> = iced::window::change_mode(window_id, iced::window::Mode::Hidden); - - hide_task.map(|_: ()| { - println!("Window hide task completed (via iced)"); - // Return a specific message to confirm the window was hidden - Message::WindowHidden - }) -} diff --git a/src/app/state.rs b/src/app/state.rs deleted file mode 100644 index 7b87cd9..0000000 --- a/src/app/state.rs +++ /dev/null @@ -1,534 +0,0 @@ -// Application state management - -use std::{ - sync::{Arc, Mutex}, - thread, - time::Duration, -}; -use ::image::{RgbaImage, open as image_open}; -use iced::{Task, Subscription, window, event}; -use iced::futures::channel::mpsc; -use global_hotkey::{ - GlobalHotKeyManager, - hotkey::HotKey as GlobalHotKey, - HotKeyState, -}; - -// Update function to handle messages -pub fn update_app(state: &mut CursorRush, message: Message) -> Task { - match message { - Message::NextCursor => { - if !state.cursors.is_empty() { - state.current_index = (state.current_index + 1) % state.cursors.len(); - - // Apply the new cursor - if let Some(cursor) = state.cursors.get_mut(state.current_index) { - // Use the cached version with the cursor info - apply_cursor_info(cursor); - - // Update shared state - if let Some(shared) = &state.shared_state { - if let Ok(mut shared_state) = shared.lock() { - shared_state.current_index = state.current_index; - // Use the cached image directly - shared_state.current_cursor_image = cursor.image.clone(); - } - } - } - } - Task::none() - }, - Message::SelectCursor(idx) => { - if idx < state.cursors.len() { - state.current_index = idx; - - // Apply the selected cursor - if let Some(cursor) = state.cursors.get_mut(state.current_index) { - // Use the cached version with the cursor info - apply_cursor_info(cursor); - - // Update shared state - if let Some(shared) = &state.shared_state { - if let Ok(mut shared_state) = shared.lock() { - shared_state.current_index = state.current_index; - // Use the cached image directly - shared_state.current_cursor_image = cursor.image.clone(); - } - } - } - } - Task::none() - }, - Message::Quit => { - println!("Update: Handling Quit message"); - - // Set running flag to false to signal other threads (like hotkey listener) - if let Some(shared) = &state.shared_state { - if let Ok(mut shared_state) = shared.lock() { - shared_state.running = false; - println!("Set running flag to false in shared state"); - } else { - println!("Failed to lock shared state to set running flag"); - } - } else { - println!("Shared state not available when handling Quit message"); - } - - // Unregister hotkeys if possible - if let Some(_) = &state.hotkey_manager { - println!("Unregistering hotkeys before exit"); - // We don't have a list of registered hotkeys, so we'll just drop the manager - // The hotkeys will be unregistered when the manager is dropped - println!("Hotkey manager will be dropped during exit"); - } else { - println!("No hotkey manager available to unregister hotkeys"); - } - - // Restore the system cursor before exiting - platform::restore_cursor(); - println!("System cursor restored"); - - // Cleanly drop the tray icon *before* exiting iced - // This removes the icon from the system tray immediately - if let Some(tray) = state.tray_icon.take() { - println!("Dropping tray icon..."); - drop(tray); - println!("Tray icon explicitly dropped."); - } else { - println!("No tray icon to drop"); - } - - // Use iced's exit mechanism for a cleaner shutdown - println!("Requesting application exit via iced::exit..."); - iced::exit() - }, - Message::TrayEvent => { - // Sync with shared state - if let Some(shared) = &state.shared_state { - if let Ok(shared_state) = shared.lock() { - state.current_index = shared_state.current_index; - } - } - Task::none() - }, - Message::CloseRequested(window_id) => { - println!("Handling CloseRequested: Hiding window with ID: {:?}", window_id); - - // Store the real window ID for future use - // This is critical for showing the window later - state.main_window = Some(window_id); - println!("Stored window ID: {:?} for future use", window_id); - - // Update shared state first - if let Some(shared) = &state.shared_state { - match shared.lock() { - Ok(mut shared_state) => { - if shared_state.window_visible { // Only hide if visible - shared_state.window_visible = false; - println!("Set window_visible to false in shared state"); - println!("Hiding window with ID: {:?}, app continues running in tray", window_id); - return crate::app::hide_window_to_tray(window_id); - } else { - println!("Window already hidden, ignoring CloseRequested."); - return Task::none(); // Don't try to hide again - } - } - Err(e) => { - eprintln!("Error locking shared state for hiding: {}", e); - return Task::none(); - } - } - } else { - eprintln!("Error: Shared state not available for hiding."); - return Task::none(); - } - }, - Message::WindowHidden => { - println!("Window successfully hidden to tray"); - Task::none() - }, - Message::WindowShown => { - println!("Update: Received WindowShown message. Window should now be visible."); - Task::none() - }, - Message::ShowWindow => { - println!("Handling ShowWindow: Attempting to show window"); // More specific starting log - - if let Some(window_id) = state.main_window { - println!("Using stored window ID: {:?}", window_id); - - // It's generally safer to attempt the show operation even if the state *thinks* it's visible. - // Let's update the shared state *after* we know the task is being created. - - println!("Creating show/focus tasks for window ID: {:?}", window_id); - let show_task = iced::window::change_mode(window_id, iced::window::Mode::Windowed); - let focus_task = iced::window::gain_focus(window_id); - - // Update shared state *before* returning the task, but after creating it. - if let Some(shared) = &state.shared_state { - match shared.lock() { - Ok(mut shared_state) => { - if !shared_state.window_visible { // Only log if changing state - println!("Updating shared state: window_visible = true"); - shared_state.window_visible = true; - } else { - println!("Shared state already indicates window is visible."); - } - } - Err(e) => { - eprintln!("Error locking shared state during show: {}", e); - // Proceed with showing the window anyway, state might be out of sync - } - } - } else { - eprintln!("Shared state unavailable during show."); - // Proceed with showing the window anyway - } - - println!("Returning Task::batch to show and focus window."); - // Return the batch task - return Task::batch(vec![show_task, focus_task]) - .map(|_: ()| { - // This message confirms the Iced runtime *completed* the tasks. - println!("Iced runtime reports show/focus tasks completed successfully."); - Message::WindowShown // Send confirmation message - }); - - } else { - eprintln!("Error: Main window ID not found when trying to show. Cannot create tasks."); - Task::none() // Cannot proceed without window_id - } - }, - } -} - -use crate::platform; -use crate::ui::tray::setup_tray_icon; -use crate::app::{Message, CursorInfo, load_cursor_files, apply_cursor_info, TRAY_SENDER}; - -// Shared state between the UI and the tray icon -pub struct SharedState { - pub current_index: usize, - pub cursors: Vec, - pub current_cursor_image: Option, - pub running: bool, - pub window_visible: bool, // Track if the window is visible -} - -// Our application state -pub struct CursorRush { - pub cursors: Vec, - pub current_index: usize, - pub shared_state: Option>>, - pub main_window: Option, // Store the main window ID - pub tray_icon: Option, // Store the TrayIcon object to keep it alive - pub hotkey_manager: Option, // Store the hotkey manager to keep it alive -} - -// Make sure the default impl initializes tray_icon and hotkey_manager to None -impl Default for CursorRush { - fn default() -> Self { - Self { - cursors: Vec::new(), - current_index: 0, - shared_state: None, - main_window: None, - tray_icon: None, // Initialize as None - hotkey_manager: None, // Initialize as None - } - } -} - -// Top-level subscription function to listen for events -pub fn app_subscription(_state: &CursorRush) -> Subscription { - // Create a vector to hold all subscriptions - let mut subs = vec![ - // Listen for events - event::listen().map(|event| { - match event { - // We no longer need to handle cursor movement events since we're using SetSystemCursor - iced::Event::Mouse(iced::mouse::Event::CursorMoved { .. }) => { - // Return TrayEvent which is essentially a no-op - Message::TrayEvent - }, - // Add a fallback keyboard handler for Ctrl+Shift+F12 - iced::Event::Keyboard(iced::keyboard::Event::KeyPressed { - key, - modifiers, - .. - }) => { - // Check if Ctrl+Shift+F12 is pressed - if key == iced::keyboard::Key::Named(iced::keyboard::key::Named::F12) && - modifiers.control() && - modifiers.shift() { - println!("Detected Ctrl+Shift+F12 via iced keyboard event"); - Message::Quit - } else { - Message::TrayEvent // No-op for other key events - } - }, - // Handle other events - _ => Message::TrayEvent, // Use TrayEvent as a no-op message - } - }), - - // Specifically subscribe to window close requests - // This is the key to handling the X button properly - window::close_requests().map(|window_id| { - println!("Window close requested via X button for window ID: {:?}", window_id); - Message::CloseRequested(window_id) - }), - ]; - - // Add a subscription for tray events - // We'll use a simple channel that the tray icon can send messages to - let tray_subscription = Subscription::run( - // Stream generator function - || { - // Create a channel that the tray icon can send messages to - let (tx, rx) = mpsc::unbounded::(); - - // Store the sender in the global state - *TRAY_SENDER.lock().unwrap() = Some(tx); - - // Return the receiver as a stream - rx - } - ); - - subs.push(tray_subscription); - - // Combine all subscriptions - Subscription::batch(subs) -} - -// Boot function implementation -pub fn boot_app() -> (CursorRush, Task) { - // Load cursor files from assets directory - let mut cursors = load_cursor_files(); - - if cursors.is_empty() { - eprintln!("No cursor PNG files found in assets directory!"); - return (CursorRush::default(), Task::none()); - } - - // Load the first cursor image - let current_cursor_image = if let Some(cursor) = cursors.first() { - match image_open(&cursor.path) { - Ok(img) => Some(img.to_rgba8()), - Err(_) => None, - } - } else { - None - }; - - // Hide the system cursor once at startup - if !platform::hide_system_cursor() { - eprintln!("Failed to hide system cursor at startup!"); - } - - // Apply the first cursor by default - if let Some(cursor) = cursors.first_mut() { - apply_cursor_info(cursor); - } - - // Create shared state - let shared_state = Arc::new(Mutex::new(SharedState { - current_index: 0, - cursors: cursors.clone(), - current_cursor_image, - running: true, - window_visible: true, // Window is initially visible - })); - - // Initialize the application state with loaded cursors - let mut state = CursorRush::default(); - state.cursors = cursors.clone(); // Clone here - state.shared_state = Some(shared_state.clone()); - - // Set up the tray icon AND store the result in the state - state.tray_icon = setup_tray_icon(&state.cursors, shared_state.clone()); // Pass the cloned cursors - if state.tray_icon.is_none() { - eprintln!("Failed to initialize tray icon, application might not function correctly in background."); - } - - // Set up hotkeys and store the manager in the application state - state.hotkey_manager = setup_hotkeys(shared_state.clone()); - if state.hotkey_manager.is_none() { - eprintln!("Failed to initialize hotkey manager, hotkeys will not work!"); - } else { - println!("Hotkey manager successfully initialized and stored in application state"); - } - - (state, Task::none()) -} - -// Set up global hotkeys -fn setup_hotkeys(shared_state: Arc>) -> Option { - // Set up global hotkey manager - let hotkey_manager = match GlobalHotKeyManager::new() { - Ok(manager) => { - println!("Successfully created hotkey manager"); - manager - }, - Err(e) => { - eprintln!("Failed to create hotkey manager: {}", e); - // Continue without hotkey support - match GlobalHotKeyManager::new() { - Ok(manager) => { - println!("Second attempt to create hotkey manager succeeded"); - manager - }, - Err(e) => { - eprintln!("Critical failure: could not create hotkey manager on second attempt: {}", e); - return None; - } - } - } - }; - - // Register Ctrl+Shift+F12 as the exit hotkey (uncommon but easy to remember) - use global_hotkey::hotkey::{Code, Modifiers}; - - let hotkey = GlobalHotKey::new( - Some(Modifiers::CONTROL | Modifiers::SHIFT), - Code::F12, - ); - - println!("Attempting to register Ctrl+Shift+F12 as exit hotkey"); - - // Register the hotkey with better error handling - let registered_hotkey = match hotkey_manager.register(hotkey) { - Ok(_) => { - println!("Successfully registered Ctrl+Shift+F12 as exit hotkey"); - hotkey - }, - Err(e) => { - eprintln!("Failed to register hotkey: {}", e); - // Try registering with a different hotkey as fallback - let fallback_hotkey = GlobalHotKey::new( - Some(Modifiers::CONTROL | Modifiers::ALT), - Code::F12, - ); - - println!("Attempting to register fallback hotkey Ctrl+Alt+F12"); - match hotkey_manager.register(fallback_hotkey) { - Ok(_) => { - println!("Registered fallback hotkey Ctrl+Alt+F12 instead"); - fallback_hotkey - }, - Err(e) => { - eprintln!("Failed to register fallback hotkey: {}", e); - - // Try one more fallback with just F12 - let last_fallback = GlobalHotKey::new(None, Code::F12); - println!("Attempting to register last fallback hotkey F12"); - match hotkey_manager.register(last_fallback) { - Ok(_) => { - println!("Registered last fallback hotkey F12"); - last_fallback - }, - Err(e) => { - eprintln!("Failed to register any hotkey: {}", e); - return None; - } - } - } - } - } - }; - - println!("Hotkey registered: {:?}", registered_hotkey); - println!("Press Ctrl+Shift+F12 to exit the application"); - - // We can't clone the hotkey manager, so we'll use Arc to share it - let manager_arc = Arc::new(hotkey_manager); - let manager_for_thread = Arc::clone(&manager_arc); - - // Spawn a thread to listen for hotkeys - let state_clone = shared_state.clone(); - thread::spawn(move || { - println!("Hotkey listener thread started"); - let event_receiver = global_hotkey::GlobalHotKeyEvent::receiver(); - - // Keep a reference to the manager in this thread to ensure it stays alive - let _manager_ref = &manager_for_thread; - println!("Hotkey manager reference maintained in thread"); - - // Create a channel to signal thread termination - let (exit_tx, exit_rx) = std::sync::mpsc::channel(); - - // Spawn a thread to monitor the running flag - let monitor_state = state_clone.clone(); - thread::spawn(move || { - loop { - // Check if we should exit - if let Ok(state) = monitor_state.lock() { - if !state.running { - println!("Monitor thread detected running=false, signaling hotkey thread to exit"); - let _ = exit_tx.send(()); - break; - } - } - // Sleep briefly to avoid high CPU usage - std::thread::sleep(Duration::from_millis(100)); - } - }); - - // Main event loop - efficiently wait for events using blocking recv() - loop { - // Use std::sync::mpsc::TryRecv to check if we should exit without blocking - if exit_rx.try_recv().is_ok() { - println!("Hotkey listener received exit signal, terminating thread"); - break; - } - - // Use blocking recv() with a timeout to efficiently wait for events - // This avoids busy-waiting and reduces CPU usage to near 0% when idle - match event_receiver.recv_timeout(Duration::from_millis(500)) { - Ok(event) => { - println!("Received hotkey event: {:?}", event); - if event.state == HotKeyState::Pressed { - println!("Exit hotkey pressed, sending Quit message..."); - // Send Quit message instead of direct exit - // Get the global sender - if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() { - println!("Found global sender, sending Quit message"); - if let Err(e) = tx.unbounded_send(Message::Quit) { - eprintln!("Failed to send Quit message from hotkey thread: {}", e); - // Fallback to direct exit if channel fails - println!("Using fallback exit mechanism"); - platform::restore_cursor(); - std::process::exit(0); - } else { - println!("Successfully sent Quit message through channel"); - } - } else { - eprintln!("Hotkey: Global sender not initialized yet"); - // Fallback to direct exit if sender not available - println!("Global sender not available, using direct exit"); - platform::restore_cursor(); - std::process::exit(0); - } - // Don't exit directly here, let the main thread handle it. - } - }, - Err(_) => { - // Timeout or channel disconnected, just continue the loop - // No need to sleep as recv_timeout already waited - } - } - } - }); - - // Return the hotkey manager from the Arc so it can be stored in the application state - match Arc::try_unwrap(manager_arc) { - Ok(manager) => Some(manager), - Err(_arc) => { - // If we can't unwrap the Arc (which shouldn't happen), extract a clone from it - println!("Warning: Could not unwrap Arc, using a new instance"); - // Create a new instance as a fallback - GlobalHotKeyManager::new().ok() - } - } -} diff --git a/src/integrations/steam.rs b/src/integrations/steam.rs deleted file mode 100644 index f1d3843..0000000 --- a/src/integrations/steam.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Steam integration - -use steamworks::{ - sys::{SteamAPI_ISteamInventory_TriggerItemDrop, SteamAPI_SteamInventory_v003}, - AppId, Client, SingleClient, -}; - -#[allow(dead_code)] -pub struct Steam { - client: Client, - single: SingleClient, -} - -impl Steam { - pub fn new() -> Self { - let (client, single) = Client::init_app(AppId(1430270)).unwrap(); - Self { client, single } - } - - #[allow(dead_code)] - pub fn drop_item(&self) { - unsafe { - let mut r = 0; - SteamAPI_ISteamInventory_TriggerItemDrop(SteamAPI_SteamInventory_v003(), &mut r, 1); - } - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 6ac58e0..0000000 --- a/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Main entry point for the Cursor Rush application - -mod app; -mod ui; -mod platform; -mod integrations; - -fn main() -> iced::Result { - // Initialize Steam - let _steam = integrations::steam::Steam::new(); - - // Run the application - iced::application("Cursor Rush", app::update, ui::view_app) - .theme(|_| iced::Theme::Dark) - .subscription(app::subscription) - .centered() - // This is the key - we need to tell iced not to exit when the window close button is clicked - .exit_on_close_request(false) - // run_with expects a function that returns (State, Task), not the tuple directly - .run_with(app::boot) -} diff --git a/src/platform/linux/cursor.rs b/src/platform/linux/cursor.rs deleted file mode 100644 index 54630f5..0000000 --- a/src/platform/linux/cursor.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Linux cursor management - -use std::path::Path; -use crate::platform::CursorInfo; - -// Hide the system cursor -pub fn hide_system_cursor() -> bool { - println!("Linux system cursor hiding not yet implemented"); - false -} - -// Update the cursor image using CursorInfo (with caching) -pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool { - println!("Linux cursor image update with caching not yet implemented"); - false -} - - - -// Restore the original cursor -pub fn restore_cursor() { - println!("Linux cursor restoration not yet implemented"); -} - -// Render the cursor -pub fn render_cursor() { - // No-op -} diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs deleted file mode 100644 index 34b272c..0000000 --- a/src/platform/linux/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Linux platform implementation module - -mod cursor; -mod state; - -// Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/linux/state.rs b/src/platform/linux/state.rs deleted file mode 100644 index e32f987..0000000 --- a/src/platform/linux/state.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Linux platform state management - -// This file will contain Linux-specific state management -// Currently a placeholder for future implementation diff --git a/src/platform/macos/cursor.rs b/src/platform/macos/cursor.rs deleted file mode 100644 index 0b1ea28..0000000 --- a/src/platform/macos/cursor.rs +++ /dev/null @@ -1,28 +0,0 @@ -// macOS cursor management - -use std::path::Path; -use crate::platform::CursorInfo; - -// Hide the system cursor -pub fn hide_system_cursor() -> bool { - println!("macOS system cursor hiding not yet implemented"); - false -} - -// Update the cursor image using CursorInfo (with caching) -pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool { - println!("macOS cursor image update with caching not yet implemented"); - false -} - - - -// Restore the original cursor -pub fn restore_cursor() { - println!("macOS cursor restoration not yet implemented"); -} - -// Render the cursor -pub fn render_cursor() { - // No-op -} diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs deleted file mode 100644 index d35a84d..0000000 --- a/src/platform/macos/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// macOS platform implementation module - -mod cursor; -mod state; - -// Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/macos/state.rs b/src/platform/macos/state.rs deleted file mode 100644 index 9e5a511..0000000 --- a/src/platform/macos/state.rs +++ /dev/null @@ -1,4 +0,0 @@ -// macOS platform state management - -// This file will contain macOS-specific state management -// Currently a placeholder for future implementation diff --git a/src/platform/mod.rs b/src/platform/mod.rs deleted file mode 100644 index 95f1057..0000000 --- a/src/platform/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Platform-specific cursor handling - -// Windows implementation -#[cfg(windows)] -mod windows; - -// macOS implementation -#[cfg(target_os = "macos")] -mod macos; - -// Linux implementation -#[cfg(target_os = "linux")] -mod linux; - -// Unsupported platforms -#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] -mod unsupported; - -// --- Platform-independent types --- - -// Define a cross-platform cursor handle type -#[cfg(windows)] -pub use windows::types::SyncHCURSOR; - -#[cfg(not(windows))] -#[derive(Copy, Clone, Debug)] -pub struct SyncHCURSOR(pub usize); - -#[cfg(not(windows))] -unsafe impl Send for SyncHCURSOR {} - -#[cfg(not(windows))] -unsafe impl Sync for SyncHCURSOR {} - -// --- Public Exports --- - -// Export the platform-specific functions -#[cfg(windows)] -pub use windows::{hide_system_cursor, update_cursor_info, restore_cursor}; - -// Re-export CursorInfo for platform-specific implementations -pub use crate::app::CursorInfo; - -#[cfg(target_os = "macos")] -pub use macos::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; - -#[cfg(target_os = "linux")] -pub use linux::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; - -// Default implementation for other platforms -#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] -pub use unsupported::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/unsupported/cursor.rs b/src/platform/unsupported/cursor.rs deleted file mode 100644 index 365f401..0000000 --- a/src/platform/unsupported/cursor.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Unsupported platform cursor management - -use std::path::Path; -use crate::platform::CursorInfo; - -// Hide the system cursor -pub fn hide_system_cursor() -> bool { - println!("System cursor hiding not supported"); - false -} - -// Update the cursor image using CursorInfo (with caching) -pub fn update_cursor_info(_cursor_info: &mut CursorInfo) -> bool { - println!("Cursor image update with caching not supported"); - false -} - - - -// Restore the original cursor -pub fn restore_cursor() { - println!("Cursor restoration not supported"); -} - -// Render the cursor -pub fn render_cursor() { - // No-op -} diff --git a/src/platform/unsupported/mod.rs b/src/platform/unsupported/mod.rs deleted file mode 100644 index 70332b5..0000000 --- a/src/platform/unsupported/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Unsupported platform implementation module - -mod cursor; -mod state; - -// Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor, render_cursor}; diff --git a/src/platform/unsupported/state.rs b/src/platform/unsupported/state.rs deleted file mode 100644 index caeca00..0000000 --- a/src/platform/unsupported/state.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Unsupported platform state management - -// This file will contain fallback state management for unsupported platforms -// Currently a placeholder for future implementation diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs deleted file mode 100644 index 534fbc7..0000000 --- a/src/platform/windows/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Windows platform implementation module - -pub mod types; -mod cursor; -mod state; - -// Re-export the public API -pub use cursor::{hide_system_cursor, update_cursor_info, restore_cursor}; diff --git a/src/ui/mod.rs b/src/ui/mod.rs deleted file mode 100644 index c7aecaf..0000000 --- a/src/ui/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// UI module -// Contains UI components and rendering logic - -pub mod view; -pub mod tray; - -pub use view::view_app; diff --git a/src/ui/tray.rs b/src/ui/tray.rs deleted file mode 100644 index c772b84..0000000 --- a/src/ui/tray.rs +++ /dev/null @@ -1,195 +0,0 @@ -// System tray functionality - -use std::{ - sync::{Arc, Mutex}, - thread, -}; -use ::image::open as image_open; -use tray_icon::{ - TrayIconBuilder, - TrayIcon, - TrayIconEvent, - MouseButton, - menu::{Menu, MenuEvent}, -}; - -use crate::app::{Message, CursorInfo, TRAY_SENDER, SharedState}; - -// Define menu item IDs -const NEXT_CURSOR_ID: &str = "next_cursor"; -const SHOW_WINDOW_ID: &str = "show_window"; -const QUIT_ID: &str = "quit"; - -// Set up the system tray icon -pub fn setup_tray_icon( - cursors: &[CursorInfo], - shared_state: Arc>, -) -> Option { // Return Option - if cursors.is_empty() { - return None; // Return None if no cursors - } - - // Create a menu - let menu = Menu::with_items(&[ - &tray_icon::menu::MenuItem::with_id(NEXT_CURSOR_ID, "Next cursor", true, None), - &tray_icon::menu::MenuItem::with_id(SHOW_WINDOW_ID, "Show window", true, None), - &tray_icon::menu::MenuItem::with_id(QUIT_ID, "Quit", true, None), - ]).ok()?; // Use ok()? to convert Result to Option - - - // Use the first PNG as the tray icon - let icon_path = &cursors[0].path; - let img = image_open(icon_path).ok()?; // Use ok()? - let rgba_img = img.to_rgba8(); - let (width, height) = rgba_img.dimensions(); - let icon = tray_icon::Icon::from_rgba(rgba_img.into_raw(), width, height).ok()?; // Use ok()? - - // Build the tray icon - println!("Building tray icon..."); - let tray_result = TrayIconBuilder::new() - .with_tooltip("Cursor Rush") - .with_icon(icon) - .with_menu(Box::new(menu)) - .with_menu_on_left_click(false) // Disable menu on left click - only show on right click - .build(); - - println!("Tray icon configuration: menu_on_left_click = false"); - - // Store the tray icon itself - let tray = match tray_result { - Ok(tray_instance) => { - println!("Tray icon built successfully"); - tray_instance // Assign the TrayIcon object - }, - Err(e) => { - eprintln!("Failed to build tray icon: {}", e); - return None; // Return None on error - } - }; - - // Listen for tray menu events in a separate thread - let state_clone = shared_state.clone(); - println!("Spawning tray menu event listener thread..."); - thread::spawn(move || { - let menu_channel = MenuEvent::receiver(); - println!("Tray menu event listener waiting for events..."); - - while let Ok(event) = menu_channel.recv() { - println!("Tray menu event received: ID = {:?}", event.id); - let msg_to_send = match event.id.as_ref() { - NEXT_CURSOR_ID => { - println!("Tray: Next Cursor requested"); - match state_clone.lock() { - Ok(mut state) => { - if !state.cursors.is_empty() { - state.current_index = (state.current_index + 1) % state.cursors.len(); - let current_index = state.current_index; - if let Some(cursor) = state.cursors.get_mut(current_index) { - println!("Tray: Updating cursor image: {}", cursor.name); - crate::app::apply_cursor_info(cursor); - // Use the cached image directly - state.current_cursor_image = cursor.image.clone(); - } - } - }, - Err(e) => eprintln!("Tray: Failed to lock shared state for next cursor: {}", e), - } - // Send TrayEvent just to trigger UI update if needed - Some(Message::TrayEvent) - }, - SHOW_WINDOW_ID => { - println!("Tray: Show Window requested"); - // Send ShowWindow message to main thread - Some(Message::ShowWindow) - }, - QUIT_ID => { - println!("Tray: Quit requested"); - // Send Quit message to main thread - Some(Message::Quit) - }, - _ => { - println!("Tray: Unknown menu event ID: {:?}", event.id); - None // Don't send a message for unknown IDs - } - }; - - // Send the determined message to the main UI thread using the global sender - if let Some(msg) = msg_to_send { - // Get the global sender - if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() { - if let Err(e) = tx.unbounded_send(msg.clone()) { // Use clone if msg is needed after send - eprintln!("Tray: Failed to send {:?} message: {}", msg, e); - break; // Stop thread if channel broken - } - // If Quit message was sent, break the loop to exit the listener thread - if matches!(msg, Message::Quit) { - println!("Tray: Quit message sent, exiting listener thread"); - break; - } - } else { - eprintln!("Tray: Global sender not initialized yet"); - } - } - } - - println!("Tray menu event listener thread finished"); - }); - - // Set up a direct event handler for tray icon events - // This is the preferred way to handle tray icon clicks - println!("Setting up tray icon click event handler..."); - - // Use set_event_handler to directly handle tray icon events - TrayIconEvent::set_event_handler(Some(move |event| { - // Only process important events, filter out mouse movements - match event { - // Handle click events - TrayIconEvent::Click { button, .. } => { - println!("Tray: Click detected with button: {:?}", button); - // Left click should show the window - if button == MouseButton::Left { - println!("Tray: Left click detected, sending ShowWindow message"); - // Get the global sender - if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() { - if let Err(e) = tx.unbounded_send(Message::ShowWindow) { - eprintln!("Tray: Failed to send ShowWindow message via handler: {}", e); - } - } else { - eprintln!("Tray: Global sender not initialized yet for click handler"); - } - } - }, - // Handle double-click events - TrayIconEvent::DoubleClick { button, .. } => { - println!("Tray: Double click detected with button: {:?}", button); - // Double left click should also show the window - if button == MouseButton::Left { - println!("Tray: Double left click detected, sending ShowWindow message"); - // Get the global sender - if let Some(tx) = TRAY_SENDER.lock().unwrap().as_ref() { - if let Err(e) = tx.unbounded_send(Message::ShowWindow) { - eprintln!("Tray: Failed to send ShowWindow message via handler on double click: {}", e); - } - } else { - eprintln!("Tray: Global sender not initialized yet for double click handler"); - } - } - }, - // Silently ignore mouse movement events - TrayIconEvent::Move { .. } => { - // Don't log anything for mouse movements - }, - // Silently ignore mouse enter/leave events - TrayIconEvent::Enter { .. } | TrayIconEvent::Leave { .. } => { - // Don't log anything for mouse enter/leave - }, - // Log other events for debugging (but not mouse-related ones) - _ => { - println!("Tray: Event received: {:?}", event); - } - } - })); - - println!("Tray setup complete using set_event_handler for clicks."); // Updated log - Some(tray) // Return the TrayIcon object -} diff --git a/src/ui/view.rs b/src/ui/view.rs deleted file mode 100644 index e6cd934..0000000 --- a/src/ui/view.rs +++ /dev/null @@ -1,77 +0,0 @@ -// UI view components - -use iced::{ - widget::{button, text, Column, Row, container, image as iced_image}, - Element, Length, -}; - -use crate::app::{CursorRush, Message}; - -// View function to render the UI -pub fn view_app(state: &CursorRush) -> Element { - let mut content = Column::new() - .spacing(16) - .push(text("Cursor Rush").size(24)); - - // Add a button for each cursor - if !state.cursors.is_empty() { - let current_cursor = &state.cursors[state.current_index]; - content = content.push(text(format!("Current cursor: {}", current_cursor.name))); - - // Display the current cursor image as a preview - let img_handle = iced_image::Handle::from_path(¤t_cursor.path); - let cursor_image = iced_image::Image::new(img_handle) - .width(Length::Fixed(64.0)) - .height(Length::Fixed(64.0)); - - content = content.push(cursor_image); - - // Create a row of cursor buttons - let mut cursor_row = Row::new().spacing(10); - - for (i, cursor) in state.cursors.iter().enumerate() { - // Create a small preview image for each cursor - let preview_handle = iced_image::Handle::from_path(&cursor.path); - let preview_image = iced_image::Image::new(preview_handle) - .width(Length::Fixed(32.0)) - .height(Length::Fixed(32.0)); - - let btn = button( - Row::new() - .spacing(5) - .push(preview_image) - .push(text(&cursor.name).size(16)) - ) - .on_press(Message::SelectCursor(i)); - - cursor_row = cursor_row.push(btn); - } - - content = content - .push(cursor_row) - .push(button("Next cursor").on_press(Message::NextCursor)) - .push(button("Quit").on_press(Message::Quit)); - - // Add instructions for the hotkey and close button behavior - content = content.push( - container( - Column::new() - .spacing(5) - .push(text("Press Ctrl+Shift+F12 to exit the application").size(16)) - .push(text("Close button (X) minimizes to system tray").size(16)) - ) - .padding(10) - ); - } else { - content = content.push(text("No cursors found in assets directory")); - } - - // Create the main container - container(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x(Length::Fill) - .center_y(Length::Fill) - .padding(20) - .into() -} diff --git a/src/utils/config.rs b/src/utils/config.rs deleted file mode 100644 index 78a5c4e..0000000 --- a/src/utils/config.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Application configuration - -use std::path::PathBuf; - -// Application configuration -pub struct Config { - pub assets_dir: PathBuf, - pub default_cursor: String, - pub auto_start: bool, - pub minimize_to_tray: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { - assets_dir: PathBuf::from("assets"), - default_cursor: String::from("default"), - auto_start: false, - minimize_to_tray: true, - } - } -} - -// Load configuration from file or use defaults -pub fn load_config() -> Config { - // TODO: Implement loading from a config file - Config::default() -} - -// Save configuration to file -pub fn save_config(_config: &Config) -> Result<(), String> { - // TODO: Implement saving to a config file - Ok(()) -} diff --git a/src/utils/logging.rs b/src/utils/logging.rs deleted file mode 100644 index bf2a00f..0000000 --- a/src/utils/logging.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Logging utilities - -// Log levels -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum LogLevel { - Debug, - Info, - Warning, - Error, -} - -// Global log level (could be made configurable) -static mut CURRENT_LOG_LEVEL: LogLevel = LogLevel::Info; - -// Set the global log level -pub fn set_log_level(level: LogLevel) { - unsafe { - CURRENT_LOG_LEVEL = level; - } -} - -// Get the current log level -pub fn get_log_level() -> LogLevel { - unsafe { - CURRENT_LOG_LEVEL - } -} - -// Log a message at the specified level -pub fn log(level: LogLevel, message: &str) { - if level >= unsafe { CURRENT_LOG_LEVEL } { - let prefix = match level { - LogLevel::Debug => "[DEBUG]", - LogLevel::Info => "[INFO]", - LogLevel::Warning => "[WARNING]", - LogLevel::Error => "[ERROR]", - }; - - println!("{} {}", prefix, message); - } -} - -// Convenience functions for different log levels -pub fn debug(message: &str) { - log(LogLevel::Debug, message); -} - -pub fn info(message: &str) { - log(LogLevel::Info, message); -} - -pub fn warning(message: &str) { - log(LogLevel::Warning, message); -} - -pub fn error(message: &str) { - log(LogLevel::Error, message); -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs deleted file mode 100644 index 952511b..0000000 --- a/src/utils/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Utility functions and helpers - -pub mod config; -pub mod logging;