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 (
+
+
+
+
+
+
+
+
+ );
+}
+
+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 (
+
+ );
+}
+
+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 (
+
+ );
+}
+
+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 && (
+
+
+

+
+
+
+
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.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 && (
+
+
+

+
+
+
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}
+ {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;