diff --git a/.gitignore b/.gitignore index a547bf3..12e486c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,24 @@ +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Production builds +dist/ +dist-ssr/ +build/ +*.tsbuildinfo + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +*.local + # Logs -logs +logs/ *.log npm-debug.log* yarn-debug.log* @@ -7,18 +26,59 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -node_modules -dist -dist-ssr -*.local +# Package manager files +package-lock.json +yarn.lock +.yarn/ +.yarnrc.yml + +# Testing and coverage +coverage/ +*.lcov +.nyc_output/ +.coverage/ +junit.xml + +# Cache directories +.cache/ +.parcel-cache/ +.npm/ +.eslintcache +.stylelintcache + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db # Editor directories and files .vscode/* !.vscode/extensions.json -.idea -.DS_Store +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +.idea/ +*.swp +*.swo +*~ *.suo *.ntvs* *.njsproj *.sln *.sw? + +# Temporary files +*.tmp +*.temp +.temp/ +.tmp/ + +# Runtime data +pids +*.pid +*.seed +*.pid.lock diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..fee5660 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,26 @@ +# Dependencies +node_modules/ +pnpm-lock.yaml + +# Build outputs +dist/ +dist-ssr/ + +# Generated files +*.min.js +*.min.css + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Temporary files +*.tmp +*.temp \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b48b6af --- /dev/null +++ b/.prettierrc @@ -0,0 +1,12 @@ +{ + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100, + "useTabs": false, + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "avoid", + "endOfLine": "lf" +} diff --git a/CLAUDE.md b/CLAUDE.md index 8c13957..305006c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,12 +16,15 @@ CoolSoup is a React + TypeScript + Vite application that generates visual patter ## Architecture Overview ### Core Application Structure + - **State Management**: Uses Nanostores for reactive state with persistent atoms - **UI Layout**: Two-panel design - main content area (generator + image grid) and collapsible audio panel - **Generator Pattern**: Pluggable generator system where each generator implements a common interface ### Main Store (`src/stores/index.ts`) + Central state management with these key atoms: + - `appSettings` - Generator selection, grid size, colors - `generatedImages` - Array of generated images with metadata - `selectedImage` - Current image for audio synthesis @@ -29,13 +32,16 @@ Central state management with these key atoms: - `panelOpen` - Audio panel visibility ### Generator System + Four built-in generators located in `src/generators/`: + - **Tixy** (`tixy.ts`) - Mathematical expressions using t,i,x,y variables - **Waveform** (`waveform.ts`) - Procedural random waveforms with various interpolation curves - **Picsum** (`picsum.ts`) - External random images API - **Art Institute** (`art-institute.ts`) - Art Institute of Chicago API Each generator returns `GeneratedImage[]` with: + - `id` - Unique identifier - `canvas` - HTMLCanvasElement - `imageData` - ImageData for synthesis @@ -43,13 +49,16 @@ Each generator returns `GeneratedImage[]` with: - `params` - Generation parameters ### Spectral Synthesis Engine (`src/spectral-synthesis/`) + Advanced image-to-audio synthesis library: + - **Core Logic** (`core/synthesizer.ts`) - Main ImageToAudioSynthesizer class - **Types** (`core/types.ts`) - SynthesisParams and related interfaces - **Audio Export** (`audio/export.ts`) - WAV file generation and download - **Utilities** (`core/utils.ts`) - Helper functions for frequency mapping and peak detection Key features: + - Mel-scale frequency mapping for perceptual accuracy - Spectral peak detection to reduce noise - Temporal smoothing for coherent audio trajectories @@ -57,7 +66,9 @@ Key features: - Configurable synthesis parameters (duration, frequency range, resolution) ### Audio Export System (`src/audio-export/`) + Batch audio processing capabilities: + - Single image export with custom parameters - Batch export of all generated images - ZIP file generation for batch downloads @@ -66,6 +77,7 @@ Batch audio processing capabilities: ## Key Implementation Details ### Image Generation Flow + 1. User selects generator and parameters in `GeneratorSelector` 2. `App.tsx` calls appropriate generator function 3. Generator returns array of `GeneratedImage` objects @@ -73,6 +85,7 @@ Batch audio processing capabilities: 5. User can click image to select for audio synthesis ### Audio Synthesis Flow + 1. User selects image from grid (sets `selectedImage`) 2. `AudioPanel` provides synthesis parameter controls 3. Synthesis triggered via spectral-synthesis library @@ -80,6 +93,7 @@ Batch audio processing capabilities: 5. Batch export processes all generated images ### Component Architecture + - **App.tsx** - Main layout and generation orchestration - **GeneratorSelector** - Generator picker with settings - **ImageGrid** - Grid display of generated images @@ -87,15 +101,18 @@ Batch audio processing capabilities: - **AudioControls** - Playback controls for generated audio ## Generator Module Structure + Both tixy-generator and waveform-generator modules are designed as standalone packages: **Tixy Generator**: + - `README.md` - Documentation and usage examples - `core/types.ts` - Type definitions - `index.ts` - Main exports - Generator-specific files (evaluator, patterns, etc.) **Waveform Generator**: + - `README.md` - Documentation and usage examples - `core/types.ts` - Interfaces and configuration - `core/interpolation.ts` - Curve interpolation functions (linear, exponential, logarithmic, cubic) @@ -104,8 +121,9 @@ Both tixy-generator and waveform-generator modules are designed as standalone pa - `index.ts` - Main exports and convenience functions ## Development Notes + - Uses Rolldown Vite for improved performance - Tailwind CSS for styling (no rounded corners per user preference) - ESLint configured for React + TypeScript - All generators support time-based animation parameters -- Image synthesis supports real-time parameter adjustment \ No newline at end of file +- Image synthesis supports real-time parameter adjustment diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 285763b..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3197 +0,0 @@ -{ - "name": "coolsoup", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "coolsoup", - "version": "0.0.0", - "dependencies": { - "react": "^19.1.1", - "react-dom": "^19.1.1" - }, - "devDependencies": { - "@eslint/js": "^9.36.0", - "@types/react": "^19.1.13", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.3", - "eslint": "^9.36.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", - "globals": "^16.4.0", - "typescript": "~5.8.3", - "typescript-eslint": "^8.44.0", - "vite": "npm:rolldown-vite@7.1.12" - } - }, - "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, - "license": "MIT", - "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.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "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-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "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, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "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.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", - "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "license": "MIT", - "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, - "license": "Apache-2.0", - "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, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", - "dev": true, - "license": "Apache-2.0", - "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.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", - "dev": true, - "license": "Apache-2.0", - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", - "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "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, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.15.2", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "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, - "license": "Apache-2.0", - "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, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz", - "integrity": "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@tybys/wasm-util": "^0.10.1" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@oxc-project/runtime": { - "version": "0.90.0", - "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.90.0.tgz", - "integrity": "sha512-TfWn2tT97Weq1/1kTc+6ZeQ3TTj8350HoovtWaUYkX1nie7ONBqeMvudpluj4rmt2jc+l1QsBV/U70Oqsv1S4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.90.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.90.0.tgz", - "integrity": "sha512-fWvaufWUcLtm/OBKcNmxUkR0kQW5ZKAF0t03BXPqdzpxmnVCmSKzvUDRCOKnSagSfNzG/3ZdKpComH3GMy881g==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.39.tgz", - "integrity": "sha512-mjraAJQ3VRLPb3BUgVigHvmAYhiBpEeSM0dhvaO6XHtJ0k1o9Ng1Z6Qvlp4/1wDiUf7a10L5c3yleoGZ2r0Maw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.39.tgz", - "integrity": "sha512-tnuiLq9vd08KsZeFkFgzCXVKsTgSZGn+YBQjHSEiUvXJy5pfUf82X/YyLCG8P6I+WDd2cgrcLilMBQPZgaNwkg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.39.tgz", - "integrity": "sha512-wLFoB3ZM4AoeBlsP0eVbPzWfkEgvmnibMQEKUgWRfJnKhUWiSxl0kGdSw1fNYdX3KAqIeA5gPJNvSJmf6g5S3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.39.tgz", - "integrity": "sha512-wzFZlixF9VMbyi++rHCU4Cy72SH11aBNnkadmvwTAbokwjYHi8NqxQ3/Lx00c700N6kwwuiTsbcGt5DEA9aROw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.39.tgz", - "integrity": "sha512-eVnZcwGbje1uwdFjeQZQ6918RHgGIK7iTC+AoDsgetgAXQmQpnuWYQ9OWa5oTHNQyCkZbMfiHKgpkUPpceMecw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.39.tgz", - "integrity": "sha512-Td96iRQA0nmRZM6kJ3+LDDKWLh4bl0zqeR+IYxXwPZBw4iXSREzXrcZ3QqgFHqnXPgryIJEW1U1Ebh2xf+b2UA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.39.tgz", - "integrity": "sha512-bcSIh1TFUoPcexJH+gO1sE6wpSR0j3UpWBnjAwyM1PRKfjtqN4R9Du90ofH5KsR/A35FT3eP4mdnhMDTd5Yt+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.39.tgz", - "integrity": "sha512-tYEcZdVGovEemh7ELr+VUoezGkuBgRZYvDHHW/HVIw9LQW5HKLtBIGLzFlOfu/Lq5b9FlDKl+lrY6weviaNnKw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.39.tgz", - "integrity": "sha512-xf9QdMC+qwQxtFAty/9RxgCLFdp9pFl09g86hxGPzlzCtHUjd+BmeUnUTXvVC8CHJLWECLQbFP6/233XHG0blA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.39.tgz", - "integrity": "sha512-QCvN02VpE6zFYry0zAU+29D5+O9tJELNt+OjuCubilZdD/S8xFdho7qBJaa3YhFYyA9cReOMVH8Z8b3yWb4hcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.39.tgz", - "integrity": "sha512-LFgshxApyBNiBHFVpun7tPrIQ4TvxW0f/endC5C4RzEHu7mxexBCQEkO5XrZ42Cr5DUY+ERNbkfNTUv+vVCaxQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.5" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.39.tgz", - "integrity": "sha512-Mykirawg+s1e0uzVSEFhUBTShvXrOghPnyuLYkCfw8gzy8bMYiJuxsAfcopzZIIAVOHeSblJoiA/e7gYFjg8HA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.39.tgz", - "integrity": "sha512-4PQJfWx7mdzXbAa4y+3OSSo911BZyJ/Is4pJKiwcGUqtvY66MX7BqlNWMr9QAozArAGE2knDubLqCQwZpK631w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.39.tgz", - "integrity": "sha512-0zmmPOWbFfp1g9ofieimHwhuclZMcib0HL52Q+JTRpOHChI2f83TtH3duKWtAaxqhLUndTr/Z5sxzb+G2FNL9g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.35", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.35.tgz", - "integrity": "sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/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, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.1.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.14.tgz", - "integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.1.9", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", - "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz", - "integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/type-utils": "8.44.1", - "@typescript-eslint/utils": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.44.1", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz", - "integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz", - "integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.44.1", - "@typescript-eslint/types": "^8.44.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz", - "integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz", - "integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz", - "integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1", - "@typescript-eslint/utils": "8.44.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz", - "integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz", - "integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.44.1", - "@typescript-eslint/tsconfig-utils": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/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, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.1.tgz", - "integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz", - "integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.44.1", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.3.tgz", - "integrity": "sha512-PFVHhosKkofGH0Yzrw1BipSedTH68BFF8ZWy1kfUpCtJcouXXY0+racG8sExw7hw0HoX36813ga5o3LTWZ4FUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.4", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.35", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ansis": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", - "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - } - }, - "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, - "license": "Python-2.0" - }, - "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, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.7.tgz", - "integrity": "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.26.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", - "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", - "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" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.8.3", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001745", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz", - "integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==", - "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" - } - ], - "license": "CC-BY-4.0" - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "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, - "license": "MIT", - "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, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", - "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.224", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz", - "integrity": "sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==", - "dev": true, - "license": "ISC" - }, - "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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", - "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.36.0", - "@eslint/plugin-kit": "^0.3.5", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@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.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.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" - }, - "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, - "license": "MIT", - "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.22", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.22.tgz", - "integrity": "sha512-atkAG6QaJMGoTLc4MDAP+rqZcfwQuTIh2IqHWFLy2TEjxr0MOK+5BSG4RzL2564AAPpZkDRsZXAUz68kjnU6Ug==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.40" - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "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.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "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, - "license": "BSD-3-Clause", - "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, - "license": "BSD-2-Clause", - "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, - "license": "BSD-2-Clause", - "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, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "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, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "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, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "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, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", - "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "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==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/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, - "license": "MIT", - "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, - "license": "MIT" - }, - "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, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.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, - "license": "ISC", - "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, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", - "dev": true, - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "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" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", - "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.26.0" - }, - "peerDependencies": { - "react": "^19.1.1" - } - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.39.tgz", - "integrity": "sha512-05bTT0CJU9dvCRC0Uc4zwB79W5N9MV9OG/Inyx8KNE2pSrrApJoWxEEArW6rmjx113HIx5IreCoTjzLfgvXTdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.90.0", - "@rolldown/pluginutils": "1.0.0-beta.39", - "ansis": "^4.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-beta.39", - "@rolldown/binding-darwin-arm64": "1.0.0-beta.39", - "@rolldown/binding-darwin-x64": "1.0.0-beta.39", - "@rolldown/binding-freebsd-x64": "1.0.0-beta.39", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.39", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.39", - "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.39", - "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.39", - "@rolldown/binding-linux-x64-musl": "1.0.0-beta.39", - "@rolldown/binding-openharmony-arm64": "1.0.0-beta.39", - "@rolldown/binding-wasm32-wasi": "1.0.0-beta.39", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.39", - "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.39", - "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.39" - } - }, - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.39", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.39.tgz", - "integrity": "sha512-GkTtNCV8ObWbq3LrJStPBv9jkRPct8WlwotVjx3aU0RwfH3LyheixWK9Zhaj22C4EQj/TJxYyetoX+uOn/MWKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "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" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "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, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.1.tgz", - "integrity": "sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.44.1", - "@typescript-eslint/parser": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1", - "@typescript-eslint/utils": "8.44.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "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" - } - ], - "license": "MIT", - "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, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "name": "rolldown-vite", - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.1.12.tgz", - "integrity": "sha512-JREtUS+Lpa3s5Ha3ajf2F4LMS4BFxlVjpGz0k0ZR8rV3ZO3tzk5hukqyi9yRBcrvnTUg/BEForyCDahALFYAZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/runtime": "0.90.0", - "fdir": "^6.5.0", - "lightningcss": "^1.30.1", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rolldown": "1.0.0-beta.39", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "esbuild": "^0.25.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "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/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "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, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "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, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json index a5c186e..7515d81 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,11 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", + "build:vite": "vite build", + "typecheck": "tsc -b", "lint": "eslint .", + "format": "prettier --write .", + "format:check": "prettier --check .", "preview": "vite preview" }, "dependencies": { @@ -31,6 +35,7 @@ "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.4.0", "postcss": "^8.5.6", + "prettier": "^3.6.2", "tailwindcss": "^4.1.13", "typescript": "~5.8.3", "typescript-eslint": "^8.44.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05b0ad8..3475ac6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: postcss: specifier: ^8.5.6 version: 8.5.6 + prettier: + specifier: ^3.6.2 + version: 3.6.2 tailwindcss: specifier: ^4.1.13 version: 4.1.13 @@ -1078,6 +1081,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -2242,6 +2250,8 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.6.2: {} + process-nextick-args@2.0.1: {} punycode@2.3.1: {} diff --git a/postcss.config.js b/postcss.config.js index db3c3d1..a7f73a2 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,5 +1,5 @@ export default { plugins: { - "@tailwindcss/postcss": {}, + '@tailwindcss/postcss': {}, }, -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 46ba4ec..4106b83 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import { useStore } from '@nanostores/react' import { appSettings, generatedImages, isGenerating, helpPopupOpen } from './stores' +import type { GeneratedImage } from './stores' import { generateTixyImages } from './generators/tixy' import { generatePicsumImages } from './generators/picsum' import { generateArtInstituteImages } from './generators/art-institute' @@ -27,7 +28,7 @@ function App() { isGenerating.set(true) try { - let newImages + let newImages: GeneratedImage[] if (settings.selectedGenerator === 'tixy') { newImages = generateTixyImages(settings.gridSize, 64) @@ -76,10 +77,7 @@ function App() { )} - helpPopupOpen.set(false)} - /> + helpPopupOpen.set(false)} /> ) } diff --git a/src/audio-export/index.ts b/src/audio-export/index.ts index 776dabc..de7f51f 100644 --- a/src/audio-export/index.ts +++ b/src/audio-export/index.ts @@ -104,20 +104,20 @@ export function exportSingleAudio( const filename = generateFilename(image.id, image.generator, { ...options, - includeGeneratorInName: true + includeGeneratorInName: true, }) downloadFile(blob, filename) return { success: true, - filename + filename, } } catch (error) { console.error('Error exporting single audio:', error) return { success: false, - error: error instanceof Error ? error.message : 'Unknown error' + error: error instanceof Error ? error.message : 'Unknown error', } } } @@ -146,7 +146,9 @@ export async function exportBatchAudio( await new Promise(resolve => setTimeout(resolve, 500)) } catch (error) { - errors.push(`Failed to export ${image.id}: ${error instanceof Error ? error.message : 'Unknown error'}`) + errors.push( + `Failed to export ${image.id}: ${error instanceof Error ? error.message : 'Unknown error'}` + ) } } @@ -154,7 +156,7 @@ export async function exportBatchAudio( success: errors.length === 0, totalFiles: images.length, successfulFiles, - errors + errors, } } @@ -167,14 +169,16 @@ export async function exportBatchAudio( const filename = generateFilename(image.id, image.generator, { ...options, - includeGeneratorInName: true + includeGeneratorInName: true, }) zip.file(filename, buffer) successfulFiles++ } catch (error) { console.error(`Error processing image ${image.id}:`, error) - errors.push(`Failed to process ${image.id}: ${error instanceof Error ? error.message : 'Unknown error'}`) + errors.push( + `Failed to process ${image.id}: ${error instanceof Error ? error.message : 'Unknown error'}` + ) } } @@ -183,7 +187,7 @@ export async function exportBatchAudio( success: false, totalFiles: images.length, successfulFiles: 0, - errors: [...errors, 'No files were successfully processed'] + errors: [...errors, 'No files were successfully processed'], } } @@ -198,7 +202,7 @@ export async function exportBatchAudio( totalFiles: images.length, successfulFiles, zipFilename, - errors + errors, } } catch (error) { console.error('Error creating ZIP file:', error) @@ -206,7 +210,10 @@ export async function exportBatchAudio( success: false, totalFiles: images.length, successfulFiles, - errors: [...errors, `Failed to create ZIP file: ${error instanceof Error ? error.message : 'Unknown error'}`] + errors: [ + ...errors, + `Failed to create ZIP file: ${error instanceof Error ? error.message : 'Unknown error'}`, + ], } } } @@ -220,7 +227,7 @@ export function quickExportSingle( ): ExportResult { return exportSingleAudio(image, synthesisParams, { includeGeneratorInName: true, - includeTimestamp: false + includeTimestamp: false, }) } @@ -234,6 +241,6 @@ export async function quickExportBatch( return exportBatchAudio(images, synthesisParams, { includeGeneratorInName: true, includeTimestamp: true, - createZip: true + createZip: true, }) -} \ No newline at end of file +} diff --git a/src/components/AudioControls.tsx b/src/components/AudioControls.tsx index c7b7e7a..aa85620 100644 --- a/src/components/AudioControls.tsx +++ b/src/components/AudioControls.tsx @@ -1,4 +1,3 @@ -import { useState, useRef } from 'react' interface AudioControlsProps { isPlaying: boolean @@ -17,9 +16,8 @@ export default function AudioControls({ onPause, onStop, onVolumeChange, - disabled = false + disabled = false, }: AudioControlsProps) { - const [isDragging, setIsDragging] = useState(false) const handleVolumeChange = (e: React.ChangeEvent) => { onVolumeChange(Number(e.target.value)) @@ -58,7 +56,13 @@ export default function AudioControls({
- + @@ -74,8 +78,9 @@ export default function AudioControls({ className="w-full h-2 bg-black border border-white appearance-none cursor-pointer volume-slider disabled:cursor-not-allowed" title={`Volume: ${Math.round(volume * 100)}%`} /> - + `, + }} + />
- - {Math.round(volume * 100)}% - + {Math.round(volume * 100)}% ) -} \ No newline at end of file +} diff --git a/src/components/AudioPanel.tsx b/src/components/AudioPanel.tsx index 12c82e2..449d5af 100644 --- a/src/components/AudioPanel.tsx +++ b/src/components/AudioPanel.tsx @@ -1,6 +1,10 @@ import { useStore } from '@nanostores/react' -import { selectedImage, panelOpen, generatedImages, synthesisParams } from '../stores' -import { synthesizeFromImage, playAudio, createAudioPlayer, type WindowType, type AudioPlayer } from '../spectral-synthesis' +import { selectedImage, generatedImages, synthesisParams } from '../stores' +import { + synthesizeFromImage, + createAudioPlayer, + type AudioPlayer, +} from '../spectral-synthesis' import { quickExportSingle, quickExportBatch } from '../audio-export' import { useState, useRef, useEffect } from 'react' import AudioControls from './AudioControls' @@ -14,14 +18,8 @@ export default function AudioPanel() { const [volume, setVolume] = useState(0.7) const audioPlayerRef = useRef(null) - const handleClearSelection = () => { - selectedImage.set(null) - if (audioPlayerRef.current) { - audioPlayerRef.current.stop() - } - } - const updateParam = (key: K, value: typeof params[K]) => { + const updateParam = (key: K, value: (typeof params)[K]) => { synthesisParams.set({ ...params, [key]: value }) } @@ -82,18 +80,6 @@ export default function AudioPanel() { } } - const handlePlaySingle = async () => { - if (!selected) return - - setIsProcessing(true) - try { - const audio = synthesizeFromImage(selected.imageData, params) - await playAudio(audio, params.sampleRate) - } catch (error) { - console.error('Error playing audio:', error) - } - setIsProcessing(false) - } // Keyboard shortcuts useEffect(() => { @@ -131,7 +117,6 @@ export default function AudioPanel() { if (!result.success) { console.error('Batch export failed:', result.errors) } else { - console.log(`Exported ${result.successfulFiles}/${result.totalFiles} files to ${result.zipFilename}`) } } catch (error) { console.error('Error generating all audio:', error) @@ -147,7 +132,7 @@ export default function AudioPanel() {
{ + ref={canvas => { if (canvas && selected.canvas) { const ctx = canvas.getContext('2d')! canvas.width = selected.canvas.width @@ -186,9 +171,7 @@ export default function AudioPanel() {
{/* Synthesis Mode Selection - Top Priority */}
- +
-
-
- - updateParam('duration', Number(e.target.value))} - className="w-full" - /> -
- - {/* Custom Mode Only Parameters */} - {params.synthesisMode === 'custom' && ( - <> -
- - updateParam('maxPartials', Number(e.target.value))} - className="w-full" - /> -

Controls audio complexity vs performance

-
- -
- - updateParam('frequencyResolution', Number(e.target.value))} - className="w-full" - /> -

Higher values create broader, richer frequency bands

-
- - )} - - {/* Direct Mode Only Parameters */} - {(params.synthesisMode || 'direct') === 'direct' && ( - <> -
- -
- -
- {[1024, 2048, 4096, 8192].map(size => ( - - ))} -
-

Higher = better frequency resolution

-
- -
- - updateParam('frameOverlap', Number(e.target.value))} - className="w-full" - /> -

Higher = smoother temporal resolution

-
- -
- - -
-
- - )} - - -
- - updateParam('minFreq', Number(e.target.value))} - className="w-full" - /> -
- -
- - updateParam('maxFreq', Number(e.target.value))} - className="w-full" - /> -
- - {/* Custom Mode Only Parameters */} - {params.synthesisMode === 'custom' && ( - <> -
- - updateParam('amplitudeThreshold', Number(e.target.value))} - className="w-full" - /> -

Minimum amplitude to include

-
- -
- -
- - - - -
-

Reduces clicking/popping between time frames

-
- - )} - - {/* Color Inversion - Available for both modes */} -
+
-
- - -
-
- - {/* Contrast - Available for both modes */} -
- updateParam('contrast', Number(e.target.value))} + max="30" + step="1" + value={params.duration} + onChange={e => updateParam('duration', Number(e.target.value))} className="w-full" /> -

Power curve for brightness perception

{/* Custom Mode Only Parameters */} @@ -482,89 +220,345 @@ export default function AudioPanel() { <>
-
- - - - -
-

How image height maps to frequency

+ updateParam('maxPartials', Number(e.target.value))} + className="w-full" + /> +

+ Controls audio complexity vs performance +

updateParam('spectralDensity', Number(e.target.value))} + value={params.frequencyResolution} + onChange={e => updateParam('frequencyResolution', Number(e.target.value))} className="w-full" /> -

Tones per frequency peak (richer = higher)

+

+ Higher values create broader, richer frequency bands +

+
+ + )} + + {/* Direct Mode Only Parameters */} + {(params.synthesisMode || 'direct') === 'direct' && ( + <> +
+
+ +
+ {[1024, 2048, 4096, 8192].map(size => ( + + ))} +
+

+ Higher = better frequency resolution +

+
+ +
+ + updateParam('frameOverlap', Number(e.target.value))} + className="w-full" + /> +

+ Higher = smoother temporal resolution +

+
+ +
+ + +
+
+ + )} + +
+ + updateParam('minFreq', Number(e.target.value))} + className="w-full" + /> +
+ +
+ + updateParam('maxFreq', Number(e.target.value))} + className="w-full" + /> +
+ + {/* Custom Mode Only Parameters */} + {params.synthesisMode === 'custom' && ( + <> +
+ + updateParam('amplitudeThreshold', Number(e.target.value))} + className="w-full" + /> +

Minimum amplitude to include

-
- +
+ +
+ + + + +
+

+ Reduces clicking/popping between time frames +

+
+ + )} + + {/* Color Inversion - Available for both modes */} +
+
+ +
+
-

Squared RGB sum for better brightness perception

- - )} +
+ {/* Contrast - Available for both modes */} +
+ + updateParam('contrast', Number(e.target.value))} + className="w-full" + /> +

Power curve for brightness perception

+
+ + {/* Custom Mode Only Parameters */} + {params.synthesisMode === 'custom' && ( + <> +
+ +
+ + + + +
+

How image height maps to frequency

+
+ +
+ + updateParam('spectralDensity', Number(e.target.value))} + className="w-full" + /> +

+ Tones per frequency peak (richer = higher) +

+
+ +
+ + +
+

+ Squared RGB sum for better brightness perception +

+ + )} +
-
{/* Audio Controls - Below Parameters */} @@ -603,4 +597,4 @@ export default function AudioPanel() {
) -} \ No newline at end of file +} diff --git a/src/components/GeneratorSelector.tsx b/src/components/GeneratorSelector.tsx index f6c4345..08402b3 100644 --- a/src/components/GeneratorSelector.tsx +++ b/src/components/GeneratorSelector.tsx @@ -9,13 +9,9 @@ interface GeneratorSelectorProps { } export default function GeneratorSelector({ onGenerate, isGenerating }: GeneratorSelectorProps) { - console.log('GeneratorSelector rendering with props:', { onGenerate, isGenerating }) - const settings = useStore(appSettings) - console.log('GeneratorSelector settings:', settings) const handleGeneratorChange = (generator: GeneratorType) => { - console.log('Changing generator to:', generator) appSettings.set({ ...settings, selectedGenerator: generator }) // Clear the grid when switching to photo or webcam modes to show drag/drop zones @@ -25,8 +21,6 @@ export default function GeneratorSelector({ onGenerate, isGenerating }: Generato } const handleGenerateClick = () => { - console.log('Generate button clicked in GeneratorSelector!') - console.log('onGenerate function:', onGenerate) onGenerate() } @@ -43,7 +37,7 @@ export default function GeneratorSelector({ onGenerate, isGenerating }: Generato { id: 'from-photo' as const, name: 'Photo', description: 'Upload your image' }, { id: 'webcam' as const, name: 'Webcam', description: 'Capture from camera' }, { id: 'art-institute' as const, name: 'Artworks', description: 'Famous artworks' }, - { id: 'picsum' as const, name: 'Picsum', description: 'Random photos' } + { id: 'picsum' as const, name: 'Picsum', description: 'Random photos' }, ] // Automatically split generators into two rows @@ -55,7 +49,10 @@ export default function GeneratorSelector({ onGenerate, isGenerating }: Generato const handleKeyPress = (event: KeyboardEvent) => { if (event.key.toLowerCase() === 'g' && !isGenerating) { // Don't trigger if user is typing in an input field - if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) { + if ( + event.target instanceof HTMLInputElement || + event.target instanceof HTMLTextAreaElement + ) { return } event.preventDefault() @@ -81,22 +78,23 @@ export default function GeneratorSelector({ onGenerate, isGenerating }: Generato
- {settings.selectedGenerator !== 'from-photo' && settings.selectedGenerator !== 'webcam' && ( - - )} + {settings.selectedGenerator !== 'from-photo' && + settings.selectedGenerator !== 'webcam' && ( + + )}
{/* Second container: 85% width - Generator modes in two lines */}
- {firstRowGenerators.map((generator) => ( + {firstRowGenerators.map(generator => (
- {secondRowGenerators.map((generator) => ( + {secondRowGenerators.map(generator => (
) -} \ No newline at end of file +} diff --git a/src/components/HelpPopup.tsx b/src/components/HelpPopup.tsx index 2b35f4d..d306c83 100644 --- a/src/components/HelpPopup.tsx +++ b/src/components/HelpPopup.tsx @@ -28,21 +28,21 @@ export default function HelpPopup({ isOpen, onClose }: HelpPopupProps) { >
e.stopPropagation()} + onClick={e => e.stopPropagation()} >

About CoolSoup

-

- CoolSoup generates visual patterns and converts them to audio through spectral/additive synthesis. Create images using mathematical expressions, waveforms, geometric patterns, or upload your own photos, then transform them into sound by treating the image as a spectrogram. + CoolSoup generates visual patterns and converts them to audio through spectral/additive + synthesis. Create images using mathematical expressions, waveforms, geometric patterns, + or upload your own photos, then transform them into sound by treating the image as a + spectrogram.

@@ -63,4 +63,4 @@ export default function HelpPopup({ isOpen, onClose }: HelpPopupProps) {
) -} \ No newline at end of file +} diff --git a/src/components/ImageGrid.tsx b/src/components/ImageGrid.tsx index 1f282e4..f5f9acf 100644 --- a/src/components/ImageGrid.tsx +++ b/src/components/ImageGrid.tsx @@ -21,7 +21,7 @@ export default function ImageGrid() { const rect = event.currentTarget.getBoundingClientRect() setLocalMousePosition({ x: (event.clientX - rect.left) / rect.width, - y: (event.clientY - rect.top) / rect.height + y: (event.clientY - rect.top) / rect.height, }) } @@ -31,7 +31,7 @@ export default function ImageGrid() { const rect = event.currentTarget.getBoundingClientRect() setLocalMousePosition({ x: (event.clientX - rect.left) / rect.width, - y: (event.clientY - rect.top) / rect.height + y: (event.clientY - rect.top) / rect.height, }) } } @@ -47,7 +47,10 @@ export default function ImageGrid() {
Generating images...
Please wait while we fetch your images
-
+
@@ -69,21 +72,19 @@ export default function ImageGrid() {
- {images.map((image) => ( + {images.map(image => (
) -} \ No newline at end of file +} diff --git a/src/components/PhotoDropZone.tsx b/src/components/PhotoDropZone.tsx index 39c558e..456bd3c 100644 --- a/src/components/PhotoDropZone.tsx +++ b/src/components/PhotoDropZone.tsx @@ -2,7 +2,6 @@ import { useState, useCallback } from 'react' import { useStore } from '@nanostores/react' import { generateFromPhotoImage } from '../generators/from-photo' import { generatedImages, selectedImage } from '../stores' -import type { GeneratedImage } from '../stores' interface PhotoDropZoneProps { size: number @@ -13,7 +12,6 @@ export default function PhotoDropZone({ size }: PhotoDropZoneProps) { const [isProcessing, setIsProcessing] = useState(false) const [error, setError] = useState(null) const images = useStore(generatedImages) - const selected = useStore(selectedImage) const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault() @@ -25,71 +23,45 @@ export default function PhotoDropZone({ size }: PhotoDropZoneProps) { setIsDragOver(false) }, []) - const handleDrop = useCallback(async (e: React.DragEvent) => { - e.preventDefault() - setIsDragOver(false) - setError(null) - - const files = Array.from(e.dataTransfer.files) - const imageFiles = files.filter(file => file.type.startsWith('image/')) - - if (imageFiles.length === 0) { - setError('Please drop an image file (PNG, JPG, GIF, etc.)') - return - } - - if (imageFiles.length > 1) { - setError('Please drop only one image at a time') - return - } - - const file = imageFiles[0] - - try { - setIsProcessing(true) - const processedImage = await generateFromPhotoImage(file, size) - - // Set the single processed image and automatically select it - generatedImages.set([processedImage]) - selectedImage.set(processedImage) - - console.log('Photo processed successfully:', processedImage) - } catch (error) { - console.error('Error processing image:', error) - setError('Failed to process the image. Please try again.') - } finally { - setIsProcessing(false) - } - }, [size]) - - const handleFileInput = useCallback(async (e: React.ChangeEvent) => { - const files = e.target.files - if (!files || files.length === 0) return - - const file = files[0] - - if (!file.type.startsWith('image/')) { - setError('Please select an image file (PNG, JPG, GIF, etc.)') - return - } - - try { - setIsProcessing(true) + const handleDrop = useCallback( + async (e: React.DragEvent) => { + e.preventDefault() + setIsDragOver(false) setError(null) - const processedImage = await generateFromPhotoImage(file, size) - // Set the single processed image and automatically select it - generatedImages.set([processedImage]) - selectedImage.set(processedImage) + const files = Array.from(e.dataTransfer.files) + const imageFiles = files.filter(file => file.type.startsWith('image/')) + + if (imageFiles.length === 0) { + setError('Please drop an image file (PNG, JPG, GIF, etc.)') + return + } + + if (imageFiles.length > 1) { + setError('Please drop only one image at a time') + return + } + + const file = imageFiles[0] + + try { + setIsProcessing(true) + const processedImage = await generateFromPhotoImage(file, size) + + // Set the single processed image and automatically select it + generatedImages.set([processedImage]) + selectedImage.set(processedImage) + + } catch (error) { + console.error('Error processing image:', error) + setError('Failed to process the image. Please try again.') + } finally { + setIsProcessing(false) + } + }, + [size] + ) - console.log('Photo processed successfully:', processedImage) - } catch (error) { - console.error('Error processing image:', error) - setError('Failed to process the image. Please try again.') - } finally { - setIsProcessing(false) - } - }, [size]) const processedImage = images.length > 0 ? images[0] : null @@ -107,7 +79,7 @@ export default function PhotoDropZone({ size }: PhotoDropZoneProps) { onDrop={handleDrop} > { + ref={canvas => { if (canvas && processedImage.canvas) { const ctx = canvas.getContext('2d')! canvas.width = processedImage.canvas.width @@ -131,9 +103,7 @@ export default function PhotoDropZone({ size }: PhotoDropZoneProps) { // Show the drop zone
)} - {error && ( -
- {error} -
- )} + {error &&
{error}
}
) -} \ No newline at end of file +} diff --git a/src/components/PhotoGrid.tsx b/src/components/PhotoGrid.tsx index 5c3eb90..7345432 100644 --- a/src/components/PhotoGrid.tsx +++ b/src/components/PhotoGrid.tsx @@ -30,24 +30,27 @@ function GridSquare({ index, image, onDrop, onSelect, selected }: GridSquareProp setIsDragOver(false) }, []) - const handleDrop = useCallback(async (e: React.DragEvent) => { - e.preventDefault() - setIsDragOver(false) + const handleDrop = useCallback( + async (e: React.DragEvent) => { + e.preventDefault() + setIsDragOver(false) - const files = Array.from(e.dataTransfer.files) - const imageFiles = files.filter(file => file.type.startsWith('image/')) + const files = Array.from(e.dataTransfer.files) + const imageFiles = files.filter(file => file.type.startsWith('image/')) - if (imageFiles.length === 0) return + if (imageFiles.length === 0) return - const file = imageFiles[0] - setIsProcessing(true) + const file = imageFiles[0] + setIsProcessing(true) - try { - await onDrop(file, index) - } finally { - setIsProcessing(false) - } - }, [index, onDrop]) + try { + await onDrop(file, index) + } finally { + setIsProcessing(false) + } + }, + [index, onDrop] + ) const handleClick = useCallback(() => { if (image) { @@ -72,7 +75,7 @@ function GridSquare({ index, image, onDrop, onSelect, selected }: GridSquareProp
) : image ? ( { + ref={canvas => { if (canvas && image.canvas) { const ctx = canvas.getContext('2d')! canvas.width = image.canvas.width @@ -102,26 +105,28 @@ export default function PhotoGrid({ size }: PhotoGridProps) { const images = useStore(generatedImages) const selected = useStore(selectedImage) - const handleDrop = useCallback(async (file: File, index: number) => { - try { - const processedImage = await generateFromPhotoImage(file, size) + const handleDrop = useCallback( + async (file: File, index: number) => { + try { + const processedImage = await generateFromPhotoImage(file, size) - // Create new images array with the processed image at the specified index - const newImages = [...images] - newImages[index] = processedImage + // Create new images array with the processed image at the specified index + const newImages = [...images] + newImages[index] = processedImage - // Filter out any null/undefined values and update the store - const filteredImages = newImages.filter(Boolean) as GeneratedImage[] - generatedImages.set(filteredImages) + // Filter out any null/undefined values and update the store + const filteredImages = newImages.filter(Boolean) as GeneratedImage[] + generatedImages.set(filteredImages) - // Auto-select the newly processed image - selectedImage.set(processedImage) + // Auto-select the newly processed image + selectedImage.set(processedImage) - console.log('Photo processed and added to grid at index:', index) - } catch (error) { - console.error('Error processing photo:', error) - } - }, [images, size]) + } catch (error) { + console.error('Error processing photo:', error) + } + }, + [images, size] + ) const handleSelect = useCallback((image: GeneratedImage) => { selectedImage.set(image) @@ -146,9 +151,7 @@ export default function PhotoGrid({ size }: PhotoGridProps) { return (
-
- {gridSquares} -
+
{gridSquares}
) -} \ No newline at end of file +} diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx index 7c7fb88..613dccb 100644 --- a/src/components/Tooltip.tsx +++ b/src/components/Tooltip.tsx @@ -1,4 +1,5 @@ -import { ReactNode, useState } from 'react' +import { useState } from 'react' +import type { ReactNode } from 'react' interface TooltipProps { content: string @@ -13,7 +14,7 @@ export default function Tooltip({ content, children, position = 'bottom' }: Tool top: 'bottom-full left-1/2 transform -translate-x-1/2 mb-2', bottom: 'top-full left-1/2 transform -translate-x-1/2 mt-2', left: 'right-full top-1/2 transform -translate-y-1/2 mr-2', - right: 'left-full top-1/2 transform -translate-y-1/2 ml-2' + right: 'left-full top-1/2 transform -translate-y-1/2 ml-2', } return ( @@ -24,10 +25,12 @@ export default function Tooltip({ content, children, position = 'bottom' }: Tool > {children} {isVisible && ( -
+
{content}
)}
) -} \ No newline at end of file +} diff --git a/src/components/WebcamGrid.tsx b/src/components/WebcamGrid.tsx index ef55836..b538112 100644 --- a/src/components/WebcamGrid.tsx +++ b/src/components/WebcamGrid.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useRef, useEffect } from 'react' +import { useState, useCallback, useEffect } from 'react' import { useStore } from '@nanostores/react' import { generateFromWebcamImage } from '../generators/webcam' import { WebcamProcessor } from '../generators/webcam-generator' @@ -37,7 +37,7 @@ function GridSquare({ index, image, onCapture, onSelect, selected, isCapturing } onClick={handleImageClick} > { + ref={canvas => { if (canvas && image.canvas) { const ctx = canvas.getContext('2d')! canvas.width = image.canvas.width @@ -60,9 +60,7 @@ function GridSquare({ index, image, onCapture, onSelect, selected, isCapturing } {isCapturing ? (
Capturing...
) : ( -
- Capture -
+
Capture
)} )} @@ -100,37 +98,39 @@ export default function WebcamGrid({ size }: WebcamGridProps) { } }, [processor]) - const handleCapture = useCallback(async (index: number) => { - if (!cameraInitialized) { - console.error('Camera not initialized') - return - } + const handleCapture = useCallback( + async (index: number) => { + if (!cameraInitialized) { + console.error('Camera not initialized') + return + } - setIsCapturing(true) - setCapturingIndex(index) + setIsCapturing(true) + setCapturingIndex(index) - try { - const capturedImage = await generateFromWebcamImage(processor, size) + try { + const capturedImage = await generateFromWebcamImage(processor, size) - // Create new images array with the captured image at the specified index - const newImages = [...images] - newImages[index] = capturedImage + // Create new images array with the captured image at the specified index + const newImages = [...images] + newImages[index] = capturedImage - // Filter out any null/undefined values and update the store - const filteredImages = newImages.filter(Boolean) as GeneratedImage[] - generatedImages.set(filteredImages) + // Filter out any null/undefined values and update the store + const filteredImages = newImages.filter(Boolean) as GeneratedImage[] + generatedImages.set(filteredImages) - // Auto-select the newly captured image - selectedImage.set(capturedImage) + // Auto-select the newly captured image + selectedImage.set(capturedImage) - console.log('Photo captured and added to grid at index:', index) - } catch (error) { - console.error('Error capturing photo:', error) - } finally { - setIsCapturing(false) - setCapturingIndex(null) - } - }, [images, size, cameraInitialized, processor]) + } catch (error) { + console.error('Error capturing photo:', error) + } finally { + setIsCapturing(false) + setCapturingIndex(null) + } + }, + [images, size, cameraInitialized, processor] + ) const handleSelect = useCallback((image: GeneratedImage) => { selectedImage.set(image) @@ -163,9 +163,7 @@ export default function WebcamGrid({ size }: WebcamGridProps) {
)} -
- {gridSquares} -
+
{gridSquares}
) -} \ No newline at end of file +} diff --git a/src/generators/art-institute.ts b/src/generators/art-institute.ts index f03fa2c..c144666 100644 --- a/src/generators/art-institute.ts +++ b/src/generators/art-institute.ts @@ -1,10 +1,32 @@ import type { GeneratedImage } from '../stores' const searchTerms = [ - 'painting', 'sculpture', 'drawing', 'pottery', 'portrait', 'landscape', - 'impressionist', 'modern', 'contemporary', 'abstract', 'still life', - 'figure', 'nature', 'urban', 'color', 'light', 'texture', 'pattern', - 'architecture', 'street', 'woman', 'man', 'cat', 'dog', 'flower', 'tree' + 'painting', + 'sculpture', + 'drawing', + 'pottery', + 'portrait', + 'landscape', + 'impressionist', + 'modern', + 'contemporary', + 'abstract', + 'still life', + 'figure', + 'nature', + 'urban', + 'color', + 'light', + 'texture', + 'pattern', + 'architecture', + 'street', + 'woman', + 'man', + 'cat', + 'dog', + 'flower', + 'tree', ] interface ArticArtwork { @@ -27,7 +49,10 @@ interface ArticSearchResponse { } } -export async function generateArtInstituteImages(count: number, size: number): Promise { +export async function generateArtInstituteImages( + count: number, + size: number +): Promise { const images: GeneratedImage[] = [] const maxRetries = count * 3 // Try 3x more objects to account for failures @@ -63,7 +88,9 @@ async function loadArtInstituteImage(size: number, index: number): Promise artwork.image_id && artwork.is_public_domain) + const artworksWithImages = searchData.data.filter( + artwork => artwork.image_id && artwork.is_public_domain + ) if (artworksWithImages.length === 0) { throw new Error('No artworks with images found') @@ -74,7 +101,7 @@ async function loadArtInstituteImage(size: number, index: number): Promise { + return new Promise(resolve => { const img = new Image() img.crossOrigin = 'anonymous' @@ -122,8 +149,8 @@ async function loadArtInstituteImage(size: number, index: number): Promise ({ x: band.x, width: band.width, - y: band.y + y: band.y, })), - opacity: layer.opacity + opacity: layer.opacity, })), strokeHeight, - size - } + size, + }, } images.push(image) @@ -113,4 +113,4 @@ export function generateBandsImages(count: number, size: number): GeneratedImage } return images -} \ No newline at end of file +} diff --git a/src/generators/dust.ts b/src/generators/dust.ts index 45c14dd..104ecd4 100644 --- a/src/generators/dust.ts +++ b/src/generators/dust.ts @@ -16,36 +16,50 @@ const greyLevels = [ '#333333', // Almost black '#222222', // Nearly black '#111111', // Extremely dark - '#0f0f0f' // Almost black (minimal amplitude) + '#0f0f0f', // Almost black (minimal amplitude) ] interface DustParams { pointCount: number - distributionType: 'uniform' | 'clustered' | 'scattered' | 'ring' | 'spiral' | 'grid' | 'noise' | 'radial' | 'perlin' + distributionType: + | 'uniform' + | 'clustered' + | 'scattered' + | 'ring' + | 'spiral' + | 'grid' + | 'noise' + | 'radial' + | 'perlin' concentration: number // 0-1, how concentrated the distribution is clusterCount?: number // for clustered distribution } -function generateUniformDistribution(count: number, size: number): Array<{x: number, y: number}> { +function generateUniformDistribution(count: number, size: number): Array<{ x: number; y: number }> { const points = [] for (let i = 0; i < count; i++) { points.push({ x: Math.random() * size, - y: Math.random() * size + y: Math.random() * size, }) } return points } -function generateClusteredDistribution(count: number, size: number, clusterCount: number, concentration: number): Array<{x: number, y: number}> { - const points = [] - const clusters = [] +function generateClusteredDistribution( + count: number, + size: number, + clusterCount: number, + concentration: number +): Array<{ x: number; y: number }> { + const points: Array<{ x: number; y: number }> = [] + const clusters: Array<{ x: number; y: number }> = [] // Generate cluster centers for (let i = 0; i < clusterCount; i++) { clusters.push({ x: Math.random() * size, - y: Math.random() * size + y: Math.random() * size, }) } @@ -64,7 +78,7 @@ function generateClusteredDistribution(count: number, size: number, clusterCount points.push({ x: Math.max(0, Math.min(size, cluster.x + Math.cos(angle) * distance)), - y: Math.max(0, Math.min(size, cluster.y + Math.sin(angle) * distance)) + y: Math.max(0, Math.min(size, cluster.y + Math.sin(angle) * distance)), }) } }) @@ -72,23 +86,28 @@ function generateClusteredDistribution(count: number, size: number, clusterCount return points } -function generateScatteredDistribution(count: number, size: number, concentration: number): Array<{x: number, y: number}> { - const points = [] +function generateScatteredDistribution( + count: number, + size: number, + concentration: number +): Array<{ x: number; y: number }> { + const points: Array<{ x: number; y: number }> = [] const exclusionRadius = size * concentration * 0.1 // minimum distance between points for (let i = 0; i < count; i++) { let attempts = 0 - let point: {x: number, y: number} + let point: { x: number; y: number } do { point = { x: Math.random() * size, - y: Math.random() * size + y: Math.random() * size, } attempts++ - } while (attempts < 50 && points.some(p => - Math.sqrt((p.x - point.x) ** 2 + (p.y - point.y) ** 2) < exclusionRadius - )) + } while ( + attempts < 50 && + points.some(p => Math.sqrt((p.x - point.x) ** 2 + (p.y - point.y) ** 2) < exclusionRadius) + ) points.push(point) } @@ -96,7 +115,11 @@ function generateScatteredDistribution(count: number, size: number, concentratio return points } -function generateRingDistribution(count: number, size: number, concentration: number): Array<{x: number, y: number}> { +function generateRingDistribution( + count: number, + size: number, + concentration: number +): Array<{ x: number; y: number }> { const points = [] const centerX = size / 2 const centerY = size / 2 @@ -110,14 +133,18 @@ function generateRingDistribution(count: number, size: number, concentration: nu points.push({ x: centerX + Math.cos(angle) * finalRadius, - y: centerY + Math.sin(angle) * finalRadius + y: centerY + Math.sin(angle) * finalRadius, }) } return points } -function generateSpiralDistribution(count: number, size: number, concentration: number): Array<{x: number, y: number}> { +function generateSpiralDistribution( + count: number, + size: number, + concentration: number +): Array<{ x: number; y: number }> { const points = [] const centerX = size / 2 const centerY = size / 2 @@ -131,14 +158,18 @@ function generateSpiralDistribution(count: number, size: number, concentration: points.push({ x: centerX + Math.cos(t) * radius + noise, - y: centerY + Math.sin(t) * radius + noise + y: centerY + Math.sin(t) * radius + noise, }) } return points } -function generateGridDistribution(count: number, size: number, concentration: number): Array<{x: number, y: number}> { +function generateGridDistribution( + count: number, + size: number, + concentration: number +): Array<{ x: number; y: number }> { const points = [] const gridSize = Math.ceil(Math.sqrt(count)) const cellSize = size / gridSize @@ -156,15 +187,18 @@ function generateGridDistribution(count: number, size: number, concentration: nu points.push({ x: Math.max(0, Math.min(size, baseX + jitterX)), - y: Math.max(0, Math.min(size, baseY + jitterY)) + y: Math.max(0, Math.min(size, baseY + jitterY)), }) } return points } - -function generateNoiseDistribution(count: number, size: number, concentration: number): Array<{x: number, y: number}> { +function generateNoiseDistribution( + count: number, + size: number, + concentration: number +): Array<{ x: number; y: number }> { const points = [] const gridSize = 64 const cellSize = size / gridSize @@ -177,7 +211,7 @@ function generateNoiseDistribution(count: number, size: number, concentration: n for (let i = 0; i < count; i++) { let attempts = 0 - let point: {x: number, y: number} + let point: { x: number; y: number } do { const x = Math.random() * size @@ -204,7 +238,11 @@ function generateNoiseDistribution(count: number, size: number, concentration: n return points } -function generateRadialDistribution(count: number, size: number, concentration: number): Array<{x: number, y: number}> { +function generateRadialDistribution( + count: number, + size: number, + concentration: number +): Array<{ x: number; y: number }> { const points = [] const centerX = size / 2 const centerY = size / 2 @@ -221,14 +259,18 @@ function generateRadialDistribution(count: number, size: number, concentration: points.push({ x: centerX + Math.cos(angle) * finalRadius, - y: centerY + Math.sin(angle) * finalRadius + y: centerY + Math.sin(angle) * finalRadius, }) } return points } -function generatePerlinLikeDistribution(count: number, size: number, concentration: number): Array<{x: number, y: number}> { +function generatePerlinLikeDistribution( + count: number, + size: number, + concentration: number +): Array<{ x: number; y: number }> { const points = [] const scale = 0.01 + concentration * 0.05 @@ -243,7 +285,7 @@ function generatePerlinLikeDistribution(count: number, size: number, concentrati for (let i = 0; i < count; i++) { let attempts = 0 - let point: {x: number, y: number} + let point: { x: number; y: number } do { const x = Math.random() * size @@ -270,7 +312,17 @@ function generatePerlinLikeDistribution(count: number, size: number, concentrati export function generateDustImages(count: number, size: number): GeneratedImage[] { const images: GeneratedImage[] = [] - const distributions = ['uniform', 'clustered', 'scattered', 'ring', 'spiral', 'grid', 'noise', 'radial', 'perlin'] as const + const distributions = [ + 'uniform', + 'clustered', + 'scattered', + 'ring', + 'spiral', + 'grid', + 'noise', + 'radial', + 'perlin', + ] as const for (let i = 0; i < count; i++) { try { @@ -295,11 +347,11 @@ export function generateDustImages(count: number, size: number): GeneratedImage[ pointCount, distributionType, concentration, - clusterCount + clusterCount, } // Generate points based on distribution type - let points: Array<{x: number, y: number}> + let points: Array<{ x: number; y: number }> switch (distributionType) { case 'uniform': @@ -350,8 +402,8 @@ export function generateDustImages(count: number, size: number): GeneratedImage[ ...params, pointColor, actualPointCount: points.length, - size - } + size, + }, } images.push(image) @@ -361,4 +413,4 @@ export function generateDustImages(count: number, size: number): GeneratedImage[ } return images -} \ No newline at end of file +} diff --git a/src/generators/from-photo-generator/index.ts b/src/generators/from-photo-generator/index.ts index 1ef194e..b25befc 100644 --- a/src/generators/from-photo-generator/index.ts +++ b/src/generators/from-photo-generator/index.ts @@ -1,2 +1,2 @@ export * from './types' -export * from './processor' \ No newline at end of file +export * from './processor' diff --git a/src/generators/from-photo-generator/processor.ts b/src/generators/from-photo-generator/processor.ts index 9414a92..87ca6e1 100644 --- a/src/generators/from-photo-generator/processor.ts +++ b/src/generators/from-photo-generator/processor.ts @@ -36,7 +36,7 @@ export function processPhoto(file: File, config: PhotoProcessingConfig): Promise canvas, imageData, originalFile: file, - config + config, }) } catch (error) { reject(error) @@ -55,7 +55,7 @@ function calculateImageDimensions( imgWidth: number, imgHeight: number, targetSize: number -): { drawWidth: number, drawHeight: number, offsetX: number, offsetY: number } { +): { drawWidth: number; drawHeight: number; offsetX: number; offsetY: number } { const aspectRatio = imgWidth / imgHeight let drawWidth: number @@ -96,7 +96,7 @@ function convertToGrayscale(ctx: CanvasRenderingContext2D, size: number, enhance } // Set R, G, B to the same grayscale value - data[i] = final // Red + data[i] = final // Red data[i + 1] = final // Green data[i + 2] = final // Blue // Alpha (data[i + 3]) remains unchanged @@ -104,4 +104,4 @@ function convertToGrayscale(ctx: CanvasRenderingContext2D, size: number, enhance // Put the processed data back ctx.putImageData(imageData, 0, 0) -} \ No newline at end of file +} diff --git a/src/generators/from-photo-generator/types.ts b/src/generators/from-photo-generator/types.ts index 30f7f2c..f464c35 100644 --- a/src/generators/from-photo-generator/types.ts +++ b/src/generators/from-photo-generator/types.ts @@ -9,4 +9,4 @@ export interface PhotoResult { imageData: ImageData originalFile: File config: PhotoProcessingConfig -} \ No newline at end of file +} diff --git a/src/generators/from-photo.ts b/src/generators/from-photo.ts index dae6b2f..5c801f3 100644 --- a/src/generators/from-photo.ts +++ b/src/generators/from-photo.ts @@ -6,7 +6,7 @@ export async function generateFromPhotoImage(file: File, size: number): Promise< const result = await processPhoto(file, { targetSize: size, contrastEnhancement: true, - grayscaleConversion: true + grayscaleConversion: true, }) const image: GeneratedImage = { @@ -21,8 +21,8 @@ export async function generateFromPhotoImage(file: File, size: number): Promise< processedAt: new Date().toISOString(), size, contrastEnhanced: true, - grayscaleConverted: true - } + grayscaleConverted: true, + }, } return image @@ -46,10 +46,10 @@ export async function generateFromPhotoImage(file: File, size: number): Promise< params: { fileName: file.name, error: error instanceof Error ? error.message : 'Unknown error', - size - } + size, + }, } return fallbackImage } -} \ No newline at end of file +} diff --git a/src/generators/geopattern.ts b/src/generators/geopattern.ts index 1d046bd..920dfa9 100644 --- a/src/generators/geopattern.ts +++ b/src/generators/geopattern.ts @@ -27,7 +27,7 @@ const PATTERN_TYPES = [ 'tessellation', 'nestedSquares', 'mosaicSquares', - 'chevrons' + 'chevrons', ] // Grayscale base colors for variety @@ -47,7 +47,7 @@ const GRAYSCALE_COLORS = [ '#cccccc', // Light grey '#dddddd', // Very light grey '#eeeeee', // Almost white - '#ffffff' // Pure white + '#ffffff', // Pure white ] function generateRandomSeed(): string { @@ -60,7 +60,10 @@ function generateRandomSeed(): string { return result } -function svgToCanvas(svgString: string, size: number): Promise<{ canvas: HTMLCanvasElement, imageData: ImageData }> { +function svgToCanvas( + svgString: string, + size: number +): Promise<{ canvas: HTMLCanvasElement; imageData: ImageData }> { return new Promise((resolve, reject) => { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d')! @@ -106,7 +109,7 @@ function svgToCanvas(svgString: string, size: number): Promise<{ canvas: HTMLCan final = Math.max(0, Math.min(255, final)) // Set R, G, B to the same enhanced grayscale value - data[i] = final // Red + data[i] = final // Red data[i + 1] = final // Green data[i + 2] = final // Blue // Alpha (data[i + 3]) remains unchanged @@ -130,7 +133,10 @@ function svgToCanvas(svgString: string, size: number): Promise<{ canvas: HTMLCan }) } -export async function generateGeopatternImages(count: number, size: number): Promise { +export async function generateGeopatternImages( + count: number, + size: number +): Promise { const images: GeneratedImage[] = [] // Load the geopattern library dynamically @@ -150,7 +156,7 @@ export async function generateGeopatternImages(count: number, size: number): Pro // Generate the geopattern const pattern = geopatternLib.generate(seed, { generator: patternType, - baseColor: baseColor + baseColor: baseColor, }) // Get SVG string @@ -169,8 +175,8 @@ export async function generateGeopatternImages(count: number, size: number): Pro seed, baseColor, originalSvg: svgString, - size - } + size, + }, } images.push(image) @@ -196,8 +202,8 @@ export async function generateGeopatternImages(count: number, size: number): Pro seed: 'error', baseColor: '#000000', size, - error: error instanceof Error ? error.message : 'Unknown error' - } + error: error instanceof Error ? error.message : 'Unknown error', + }, } images.push(fallbackImage) @@ -205,4 +211,4 @@ export async function generateGeopatternImages(count: number, size: number): Pro } return images -} \ No newline at end of file +} diff --git a/src/generators/harmonics.ts b/src/generators/harmonics.ts index 29322c9..1f58142 100644 --- a/src/generators/harmonics.ts +++ b/src/generators/harmonics.ts @@ -11,7 +11,7 @@ const greyLevels = [ '#888888', // Very dark grey '#777777', // Darker grey '#666666', // Even darker grey - '#555555' // Much darker grey + '#555555', // Much darker grey ] interface HarmonicSeries { @@ -54,7 +54,12 @@ function calculateHarmonicAmplitude(harmonicNumber: number, timbre: string): num } function generateHarmonicSeries(canvasSize: number): HarmonicSeries { - const timbres: Array<'bright' | 'warm' | 'dark' | 'metallic'> = ['bright', 'warm', 'dark', 'metallic'] + const timbres: Array<'bright' | 'warm' | 'dark' | 'metallic'> = [ + 'bright', + 'warm', + 'dark', + 'metallic', + ] const timbre = timbres[Math.floor(Math.random() * timbres.length)] // Fundamental frequency position (in lower 2/3 of canvas for musical range) @@ -66,7 +71,7 @@ function generateHarmonicSeries(canvasSize: number): HarmonicSeries { for (let i = 1; i <= maxHarmonics; i++) { // Higher harmonics = higher frequencies = lower Y positions - const yPosition = fundamental - (fundamental * (i - 1) / maxHarmonics) + const yPosition = fundamental - (fundamental * (i - 1)) / maxHarmonics // Stop if we go above the canvas if (yPosition <= 0) break @@ -78,14 +83,14 @@ function generateHarmonicSeries(canvasSize: number): HarmonicSeries { frequency: i, yPosition, amplitude, - thickness + thickness, }) } return { fundamental, harmonics, - timbre + timbre, } } @@ -137,10 +142,10 @@ export function generateHarmonicsImages(count: number, size: number): GeneratedI frequency: h.frequency, yPosition: h.yPosition, amplitude: h.amplitude, - thickness: h.thickness + thickness: h.thickness, })), - size - } + size, + }, } images.push(image) @@ -150,4 +155,4 @@ export function generateHarmonicsImages(count: number, size: number): GeneratedI } return images -} \ No newline at end of file +} diff --git a/src/generators/partials.ts b/src/generators/partials.ts index d3ccafa..921203a 100644 --- a/src/generators/partials.ts +++ b/src/generators/partials.ts @@ -8,7 +8,7 @@ const greyLevels = [ '#bbbbbb', // Medium grey '#aaaaaa', // Medium-dark grey '#999999', // Dark grey - '#888888' // Very dark grey (lower amplitude) + '#888888', // Very dark grey (lower amplitude) ] export function generatePartialsImages(count: number, size: number): GeneratedImage[] { @@ -30,7 +30,7 @@ export function generatePartialsImages(count: number, size: number): GeneratedIm ctx.fillRect(0, 0, size, size) // Generate line positions, ensuring they don't overlap - const linePositions: Array<{y: number, thickness: number}> = [] + const linePositions: Array<{ y: number; thickness: number }> = [] const minSpacing = size / (lineCount + 1) for (let j = 0; j < lineCount; j++) { @@ -59,8 +59,8 @@ export function generatePartialsImages(count: number, size: number): GeneratedIm lineCount, linePositions: linePositions.map(l => ({ y: l.y, thickness: l.thickness })), lineColor, - size - } + size, + }, } images.push(image) @@ -70,4 +70,4 @@ export function generatePartialsImages(count: number, size: number): GeneratedIm } return images -} \ No newline at end of file +} diff --git a/src/generators/picsum.ts b/src/generators/picsum.ts index a18cbed..8ba49ad 100644 --- a/src/generators/picsum.ts +++ b/src/generators/picsum.ts @@ -22,56 +22,59 @@ const highQualityIds = [ 1, 2, 3, 5, 6, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 35, 36, 37, 39, 40, 42, 43, 44, 47, 48, 49, 50, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 67, 68, 69, 70, 72, 73, 74, 75, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, - 91, 92, 96, 97, 98, 99, 100, 101, 102, 103, 104, 106, 107, 108, 109, 110, 111, 112, 113, 116, - 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, - 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, - 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, - 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, - 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, - 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, - 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, - 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, - 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, - 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, - 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, - 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, - 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, - 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, - 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, - 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, - 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, - 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, - 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, - 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, - 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, - 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, - 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, - 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, - 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, - 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, - 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, - 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, - 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, - 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, - 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, - 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, - 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, - 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, - 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, - 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, - 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, - 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, - 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, - 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, - 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, - 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, - 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, - 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, - 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, - 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, - 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, - 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, - 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084 + 91, 92, 96, 97, 98, 99, 100, 101, 102, 103, 104, 106, 107, 108, 109, 110, 111, 112, 113, 116, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, + 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, + 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, + 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, + 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, + 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, + 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, + 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, + 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, + 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, + 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, + 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, + 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, + 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, + 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, + 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, + 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, + 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, + 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, + 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, + 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, + 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, + 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, + 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, + 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, + 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, + 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, + 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, + 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, + 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, + 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, + 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, + 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, + 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, + 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, + 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, + 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, + 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, + 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, + 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, + 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, + 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, + 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, + 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, + 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, + 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, + 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, + 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, + 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, + 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, ] // Keep track of recently used IDs to avoid immediate repeats @@ -97,7 +100,7 @@ async function loadPicsumImage(size: number, index: number): Promise { + return new Promise((resolve, _reject) => { img.onload = () => { try { const canvas = document.createElement('canvas') @@ -114,7 +117,7 @@ async function loadPicsumImage(size: number, index: number): Promise s.color), - size - } + size, + }, } images.push(image) @@ -167,4 +193,4 @@ export function generateSlidesImages(count: number, size: number): GeneratedImag } return images -} \ No newline at end of file +} diff --git a/src/generators/tixy-generator/README.md b/src/generators/tixy-generator/README.md index 322a297..05f1e8c 100644 --- a/src/generators/tixy-generator/README.md +++ b/src/generators/tixy-generator/README.md @@ -5,6 +5,7 @@ A standalone module for generating Tixy-like shader patterns in JavaScript/TypeS ## What is Tixy? Tixy is a minimalist programming language designed by Martin Kleppe for creating visual patterns using 4 variables: + - `t` - time - `i` - index (pixel index in the grid) - `x` - x coordinate @@ -24,7 +25,7 @@ const result = renderTixyToCanvas(expression, { height: 64, time: 0, backgroundColor: '#000000', - foregroundColor: '#ffffff' + foregroundColor: '#ffffff', }) // Add to DOM @@ -56,4 +57,4 @@ document.body.appendChild(result.canvas) - `TixyRenderOptions` - Rendering configuration - `TixyResult` - Canvas and ImageData result -Math functions like `sin`, `cos`, `sqrt` etc. are available without the `Math.` prefix. \ No newline at end of file +Math functions like `sin`, `cos`, `sqrt` etc. are available without the `Math.` prefix. diff --git a/src/generators/tixy-generator/core/evaluator.ts b/src/generators/tixy-generator/core/evaluator.ts index 40b2ea2..d2e54d3 100644 --- a/src/generators/tixy-generator/core/evaluator.ts +++ b/src/generators/tixy-generator/core/evaluator.ts @@ -2,9 +2,28 @@ import type { TixyFunction, TixyExpression } from './types' import { TixyFormulaGenerator } from './formula-generator' const MATH_METHODS = [ - 'abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', - 'log', 'max', 'min', 'pow', 'random', 'round', 'sin', 'sqrt', 'tan', - 'sinh', 'cosh', 'tanh', 'sign' + 'abs', + 'acos', + 'asin', + 'atan', + 'atan2', + 'ceil', + 'cos', + 'exp', + 'floor', + 'log', + 'max', + 'min', + 'pow', + 'random', + 'round', + 'sin', + 'sqrt', + 'tan', + 'sinh', + 'cosh', + 'tanh', + 'sign', ] export function compileTixyExpression(code: string): TixyExpression { @@ -22,7 +41,7 @@ export function compileTixyExpression(code: string): TixyExpression { return { code: processedCode, - compiled + compiled, } } catch (error) { throw new Error(`Failed to compile Tixy expression: ${error}`) @@ -48,11 +67,11 @@ export function evaluateTixyExpression( const formulaGenerator = new TixyFormulaGenerator() // Generate expressions dynamically -export function generateTixyExpression(): { expression: string, description: string } { +export function generateTixyExpression(): { expression: string; description: string } { const result = formulaGenerator.generateFormula() return { expression: result.expression, - description: result.description + description: result.description, } } @@ -61,10 +80,19 @@ export function getExampleExpressions(): Record { const expressions: Record = {} // Generate expressions for each theme - const themes = ['organic', 'geometric', 'interference', 'chaotic', 'minimalist', 'psychedelic', 'bitwise'] as const + const themes = [ + 'organic', + 'geometric', + 'interference', + 'chaotic', + 'minimalist', + 'psychedelic', + 'bitwise', + ] as const themes.forEach(theme => { - for (let i = 0; i < 5; i++) { // Generate 5 expressions per theme + for (let i = 0; i < 5; i++) { + // Generate 5 expressions per theme const result = formulaGenerator.generateFormula(theme) expressions[result.expression] = result.description } @@ -74,4 +102,4 @@ export function getExampleExpressions(): Record { } // Export for backward compatibility -export const EXAMPLE_EXPRESSIONS = getExampleExpressions() \ No newline at end of file +export const EXAMPLE_EXPRESSIONS = getExampleExpressions() diff --git a/src/generators/tixy-generator/core/formula-generator.ts b/src/generators/tixy-generator/core/formula-generator.ts index ebcbe8a..8d2d26e 100644 --- a/src/generators/tixy-generator/core/formula-generator.ts +++ b/src/generators/tixy-generator/core/formula-generator.ts @@ -1,5 +1,18 @@ -export type Theme = 'organic' | 'geometric' | 'interference' | 'chaotic' | 'minimalist' | 'psychedelic' | 'bitwise' -export type CoordinateSpace = 'cartesian' | 'polar' | 'log_polar' | 'hyperbolic' | 'wave_distort' | 'spiral' +export type Theme = + | 'organic' + | 'geometric' + | 'interference' + | 'chaotic' + | 'minimalist' + | 'psychedelic' + | 'bitwise' +export type CoordinateSpace = + | 'cartesian' + | 'polar' + | 'log_polar' + | 'hyperbolic' + | 'wave_distort' + | 'spiral' interface PatternConfig { weight: number @@ -45,28 +58,28 @@ const COORDINATE_TRANSFORMS = { polar: (x: string, y: string) => ({ x: `sqrt((${x}-8)**2+(${y}-8)**2)`, // r - y: `atan2(${y}-8,${x}-8)` // θ + y: `atan2(${y}-8,${x}-8)`, // θ }), log_polar: (x: string, y: string) => ({ x: `log(sqrt((${x}-8)**2+(${y}-8)**2)+1)`, // log(r) - y: `atan2(${y}-8,${x}-8)` // θ + y: `atan2(${y}-8,${x}-8)`, // θ }), hyperbolic: (x: string, y: string) => ({ x: `(${x}-8)/(1+((${x}-8)**2+(${y}-8)**2)/64)`, - y: `(${y}-8)/(1+((${x}-8)**2+(${y}-8)**2)/64)` + y: `(${y}-8)/(1+((${x}-8)**2+(${y}-8)**2)/64)`, }), wave_distort: (x: string, y: string, freq = 0.5, amp = 2) => ({ x: `${x}+sin(${y}*${freq})*${amp}`, - y: `${y}+cos(${x}*${freq})*${amp}` + y: `${y}+cos(${x}*${freq})*${amp}`, }), spiral: (x: string, y: string, tightness = 0.3) => ({ x: `sqrt((${x}-8)**2+(${y}-8)**2)*cos(atan2(${y}-8,${x}-8)+sqrt((${x}-8)**2+(${y}-8)**2)*${tightness})`, - y: `sqrt((${x}-8)**2+(${y}-8)**2)*sin(atan2(${y}-8,${x}-8)+sqrt((${x}-8)**2+(${y}-8)**2)*${tightness})` - }) + y: `sqrt((${x}-8)**2+(${y}-8)**2)*sin(atan2(${y}-8,${x}-8)+sqrt((${x}-8)**2+(${y}-8)**2)*${tightness})`, + }), } // Pattern building blocks with parameterization @@ -74,19 +87,27 @@ const PATTERN_GENERATORS = { // Wave patterns sine_wave: (freq: number, phase: number, axis: 'x' | 'y' | 'xy' | 'radial') => { switch (axis) { - case 'x': return `sin(x*${freq.toFixed(2)}+t*${phase.toFixed(2)})` - case 'y': return `sin(y*${freq.toFixed(2)}+t*${phase.toFixed(2)})` - case 'xy': return `sin((x+y)*${freq.toFixed(2)}+t*${phase.toFixed(2)})` - case 'radial': return `sin(sqrt(x*x+y*y)*${freq.toFixed(2)}+t*${phase.toFixed(2)})` + case 'x': + return `sin(x*${freq.toFixed(2)}+t*${phase.toFixed(2)})` + case 'y': + return `sin(y*${freq.toFixed(2)}+t*${phase.toFixed(2)})` + case 'xy': + return `sin((x+y)*${freq.toFixed(2)}+t*${phase.toFixed(2)})` + case 'radial': + return `sin(sqrt(x*x+y*y)*${freq.toFixed(2)}+t*${phase.toFixed(2)})` } }, cos_wave: (freq: number, phase: number, axis: 'x' | 'y' | 'xy' | 'radial') => { switch (axis) { - case 'x': return `cos(x*${freq.toFixed(2)}+t*${phase.toFixed(2)})` - case 'y': return `cos(y*${freq.toFixed(2)}+t*${phase.toFixed(2)})` - case 'xy': return `cos((x+y)*${freq.toFixed(2)}+t*${phase.toFixed(2)})` - case 'radial': return `cos(sqrt(x*x+y*y)*${freq.toFixed(2)}+t*${phase.toFixed(2)})` + case 'x': + return `cos(x*${freq.toFixed(2)}+t*${phase.toFixed(2)})` + case 'y': + return `cos(y*${freq.toFixed(2)}+t*${phase.toFixed(2)})` + case 'xy': + return `cos((x+y)*${freq.toFixed(2)}+t*${phase.toFixed(2)})` + case 'radial': + return `cos(sqrt(x*x+y*y)*${freq.toFixed(2)}+t*${phase.toFixed(2)})` } }, @@ -100,11 +121,11 @@ const PATTERN_GENERATORS = { // Grid patterns grid: (sizeX: number, sizeY: number, phase: number) => - `sin((x%${sizeX.toFixed(1)})*${(2*Math.PI/sizeX).toFixed(2)}+t*${phase.toFixed(2)})*cos((y%${sizeY.toFixed(1)})*${(2*Math.PI/sizeY).toFixed(2)}+t*${phase.toFixed(2)})`, + `sin((x%${sizeX.toFixed(1)})*${((2 * Math.PI) / sizeX).toFixed(2)}+t*${phase.toFixed(2)})*cos((y%${sizeY.toFixed(1)})*${((2 * Math.PI) / sizeY).toFixed(2)}+t*${phase.toFixed(2)})`, // Noise patterns noise: (scaleX: number, scaleY: number, evolution: number) => - `sin(x*${scaleX.toFixed(2)}+y*${scaleY.toFixed(2)}+t*${evolution.toFixed(2)})*cos(x*${(scaleX*1.618).toFixed(2)}+y*${(scaleY*0.618).toFixed(2)}+t*${(evolution*1.414).toFixed(2)})`, + `sin(x*${scaleX.toFixed(2)}+y*${scaleY.toFixed(2)}+t*${evolution.toFixed(2)})*cos(x*${(scaleX * 1.618).toFixed(2)}+y*${(scaleY * 0.618).toFixed(2)}+t*${(evolution * 1.414).toFixed(2)})`, // Geometric shapes checkerboard: (size: number, phase: number) => @@ -148,9 +169,12 @@ const PATTERN_GENERATORS = { modular_arithmetic: (modX: number, modY: number, operation: 'add' | 'mult' | 'xor') => { switch (operation) { - case 'add': return `((floor(x)%${Math.floor(modX)})+(floor(y)%${Math.floor(modY)}))%16/8` - case 'mult': return `((floor(x)%${Math.floor(modX)})*(floor(y)%${Math.floor(modY)}))%16/8` - case 'xor': return `((floor(x)%${Math.floor(modX)})^(floor(y)%${Math.floor(modY)}))%16/8` + case 'add': + return `((floor(x)%${Math.floor(modX)})+(floor(y)%${Math.floor(modY)}))%16/8` + case 'mult': + return `((floor(x)%${Math.floor(modX)})*(floor(y)%${Math.floor(modY)}))%16/8` + case 'xor': + return `((floor(x)%${Math.floor(modX)})^(floor(y)%${Math.floor(modY)}))%16/8` } }, @@ -168,16 +192,16 @@ const PATTERN_GENERATORS = { `sin(${a.toFixed(2)}*x+${b.toFixed(2)}*y+t)*cos(${c.toFixed(2)}*x*y+t*2)`, voronoi: (seed1: number, seed2: number, scale: number) => - `min(sqrt((x-${seed1.toFixed(1)})**2+(y-${seed2.toFixed(1)})**2),sqrt((x-${(16-seed1).toFixed(1)})**2+(y-${(16-seed2).toFixed(1)})**2))*${scale.toFixed(2)}`, + `min(sqrt((x-${seed1.toFixed(1)})**2+(y-${seed2.toFixed(1)})**2),sqrt((x-${(16 - seed1).toFixed(1)})**2+(y-${(16 - seed2).toFixed(1)})**2))*${scale.toFixed(2)}`, reaction_diffusion: (diffusion: number, reaction: number) => `tanh((sin(x*${diffusion.toFixed(2)}+t)*cos(y*${diffusion.toFixed(2)}+t)-${reaction.toFixed(2)})*4)`, fractal_noise: (octaves: number, persistence: number) => - `sin(x*${octaves.toFixed(1)}+t)*${persistence.toFixed(2)}+sin(x*${(octaves*2).toFixed(1)}+t)*${(persistence*0.5).toFixed(2)}+sin(x*${(octaves*4).toFixed(1)}+t)*${(persistence*0.25).toFixed(2)}`, + `sin(x*${octaves.toFixed(1)}+t)*${persistence.toFixed(2)}+sin(x*${(octaves * 2).toFixed(1)}+t)*${(persistence * 0.5).toFixed(2)}+sin(x*${(octaves * 4).toFixed(1)}+t)*${(persistence * 0.25).toFixed(2)}`, musical_harmony: (fundamental: number, overtone: number) => - `sin(x*${fundamental.toFixed(2)}+t)+sin(x*${(fundamental*overtone).toFixed(2)}+t*${PHI.toFixed(3)})*0.5`, + `sin(x*${fundamental.toFixed(2)}+t)+sin(x*${(fundamental * overtone).toFixed(2)}+t*${PHI.toFixed(3)})*0.5`, } // Theme configurations with weighted pattern preferences @@ -188,10 +212,13 @@ const THEME_CONFIGS: Record = { fractal_noise: { weight: 3, params: { octaves: [0.5, 2], persistence: [0.3, 0.8] } }, noise: { weight: 2, params: { scaleX: [0.1, 0.5], scaleY: [0.1, 0.5], evolution: [0.5, 2] } }, sine_wave: { weight: 2, params: { freq: [0.1, 0.4], phase: [0.2, 1], axis: ['x', 'y'] } }, - am_modulation: { weight: 1, params: { carrierFreq: [0.2, 0.6], modFreq: [0.1, 0.4], depth: [0.3, 0.8] } } + am_modulation: { + weight: 1, + params: { carrierFreq: [0.2, 0.6], modFreq: [0.1, 0.4], depth: [0.3, 0.8] }, + }, }, combinators: ['*', '+', 'max', 'min'], - complexity: [2, 3] + complexity: [2, 3], }, geometric: { @@ -199,21 +226,27 @@ const THEME_CONFIGS: Record = { voronoi: { weight: 3, params: { seed1: [2, 14], seed2: [2, 14], scale: [0.3, 1] } }, grid: { weight: 3, params: { sizeX: [2, 8], sizeY: [2, 8], phase: [0.5, 2] } }, checkerboard: { weight: 2, params: { size: [1, 4], phase: [0.1, 1] } }, - diamond: { weight: 1, params: { centerX: [7, 9], centerY: [7, 9], size: [0.5, 2], speed: [0.5, 2] } } + diamond: { + weight: 1, + params: { centerX: [7, 9], centerY: [7, 9], size: [0.5, 2], speed: [0.5, 2] }, + }, }, combinators: ['*', '+', 'floor', 'sign', 'min'], - complexity: [1, 2] + complexity: [1, 2], }, interference: { patterns: { - interference: { weight: 4, params: { freq1: [0.2, 0.8], freq2: [0.2, 0.8], phase1: [0.5, 2], phase2: [0.5, 2] } }, + interference: { + weight: 4, + params: { freq1: [0.2, 0.8], freq2: [0.2, 0.8], phase1: [0.5, 2], phase2: [0.5, 2] }, + }, sine_wave: { weight: 3, params: { freq: [0.3, 1], phase: [0.5, 3], axis: ['x', 'y', 'xy'] } }, cos_wave: { weight: 3, params: { freq: [0.3, 1], phase: [0.5, 3], axis: ['x', 'y', 'xy'] } }, - grid: { weight: 2, params: { sizeX: [3, 8], sizeY: [3, 8], phase: [0.5, 2] } } + grid: { weight: 2, params: { sizeX: [3, 8], sizeY: [3, 8], phase: [0.5, 2] } }, }, combinators: ['+', '-', '*', 'max'], - complexity: [2, 3] + complexity: [2, 3], }, chaotic: { @@ -222,10 +255,13 @@ const THEME_CONFIGS: Record = { mandelbrot_like: { weight: 2, params: { scale: [0.1, 0.5], iterations: [2, 5] } }, fractal_noise: { weight: 2, params: { octaves: [1, 4], persistence: [0.2, 0.8] } }, xor_pattern: { weight: 2, params: { maskX: [7, 31], maskY: [7, 31], timeShift: [1, 4] } }, - interference: { weight: 1, params: { freq1: [0.5, 2], freq2: [0.5, 2], phase1: [2, 6], phase2: [2, 6] } } + interference: { + weight: 1, + params: { freq1: [0.5, 2], freq2: [0.5, 2], phase1: [2, 6], phase2: [2, 6] }, + }, }, combinators: ['*', '+', 'tan', 'pow', '%', '^'], - complexity: [2, 3] + complexity: [2, 3], }, minimalist: { @@ -233,44 +269,62 @@ const THEME_CONFIGS: Record = { checkerboard: { weight: 4, params: { size: [3, 8], phase: [0.1, 0.5] } }, grid: { weight: 3, params: { sizeX: [4, 12], sizeY: [4, 12], phase: [0.2, 0.8] } }, binary_maze: { weight: 2, params: { complexity: [3, 7], timeEvolution: [0.1, 0.5] } }, - sine_wave: { weight: 1, params: { freq: [0.1, 0.3], phase: [0.2, 0.8], axis: ['x', 'y'] } } + sine_wave: { weight: 1, params: { freq: [0.1, 0.3], phase: [0.2, 0.8], axis: ['x', 'y'] } }, }, combinators: ['sign', 'floor', 'abs', '&'], - complexity: [1, 2] + complexity: [1, 2], }, psychedelic: { patterns: { musical_harmony: { weight: 3, params: { fundamental: [0.5, 2], overtone: [1.5, 4] } }, - interference: { weight: 2, params: { freq1: [0.8, 2.5], freq2: [0.8, 2.5], phase1: [2, 8], phase2: [2, 8] } }, + interference: { + weight: 2, + params: { freq1: [0.8, 2.5], freq2: [0.8, 2.5], phase1: [2, 8], phase2: [2, 8] }, + }, bit_rotation: { weight: 2, params: { frequency: [0.8, 2], timeSpeed: [2, 6] } }, strange_attractor: { weight: 2, params: { a: [1, 3], b: [0.5, 2], c: [0.3, 1.5] } }, - spiral: { weight: 1, params: { centerX: [4, 12], centerY: [4, 12], tightness: [3, 12], rotation: [3, 10] } } + spiral: { + weight: 1, + params: { centerX: [4, 12], centerY: [4, 12], tightness: [3, 12], rotation: [3, 10] }, + }, }, combinators: ['*', '+', 'tan', 'cos', 'sin', '^'], - complexity: [3, 4] + complexity: [3, 4], }, bitwise: { patterns: { xor_pattern: { weight: 3, params: { maskX: [3, 31], maskY: [3, 31], timeShift: [0.1, 2] } }, - and_pattern: { weight: 2, params: { maskX: [7, 15], maskY: [7, 15], normalizer: [0.1, 0.5] } }, + and_pattern: { + weight: 2, + params: { maskX: [7, 15], maskY: [7, 15], normalizer: [0.1, 0.5] }, + }, or_pattern: { weight: 2, params: { maskX: [7, 15], maskY: [7, 15], normalizer: [0.1, 0.5] } }, - bit_shift: { weight: 2, params: { direction: ['left', 'right'], amount: [1, 4], modulator: [0.5, 2] } }, + bit_shift: { + weight: 2, + params: { direction: ['left', 'right'], amount: [1, 4], modulator: [0.5, 2] }, + }, binary_cellular: { weight: 2, params: { rule: [7, 31], evolution: [0.2, 1.5] } }, bit_rotation: { weight: 1, params: { frequency: [0.3, 1.5], timeSpeed: [0.5, 3] } }, - modular_arithmetic: { weight: 2, params: { modX: [3, 16], modY: [3, 16], operation: ['add', 'mult', 'xor'] } }, + modular_arithmetic: { + weight: 2, + params: { modX: [3, 16], modY: [3, 16], operation: ['add', 'mult', 'xor'] }, + }, sierpinski: { weight: 1, params: { scale: [0.3, 1.2], timeShift: [0.1, 0.8] } }, bit_noise: { weight: 1, params: { density: [0.5, 2], evolution: [0.3, 2] } }, - binary_maze: { weight: 1, params: { complexity: [3, 15], timeEvolution: [0.1, 1] } } + binary_maze: { weight: 1, params: { complexity: [3, 15], timeEvolution: [0.1, 1] } }, }, combinators: ['^', '&', '|', '+', '*', 'sign', 'floor'], - complexity: [1, 3] - } + complexity: [1, 3], + }, } export class TixyFormulaGenerator { - generateFormula(theme?: Theme, varietyConfig?: VarietyConfig): { expression: string, theme: Theme, description: string } { + generateFormula( + theme?: Theme, + varietyConfig?: VarietyConfig + ): { expression: string; theme: Theme; description: string } { // Enhanced variety system - randomly decide enhancement features const useCoordinateTransform = !varietyConfig || random() < 0.4 const useThemeHybrid = !varietyConfig || random() < 0.3 @@ -304,15 +358,20 @@ export class TixyFormulaGenerator { // Coordinate transformation selection let coordinateSpace: CoordinateSpace = 'cartesian' if (useCoordinateTransform) { - coordinateSpace = choice(['cartesian', 'polar', 'log_polar', 'hyperbolic', 'wave_distort', 'spiral']) + coordinateSpace = choice([ + 'cartesian', + 'polar', + 'log_polar', + 'hyperbolic', + 'wave_distort', + 'spiral', + ]) } // Generate base patterns with enhanced variety for (let i = 0; i < numPatterns; i++) { const patternType = weightedChoice( - Object.fromEntries( - Object.entries(config.patterns).map(([key, cfg]) => [key, cfg.weight]) - ) + Object.fromEntries(Object.entries(config.patterns).map(([key, cfg]) => [key, cfg.weight])) ) const patternConfig = config.patterns[patternType] @@ -334,9 +393,10 @@ export class TixyFormulaGenerator { // Combine patterns with advanced techniques const expression = this.combinePatterns(patterns, config.combinators) - const themeDesc = hybridThemes.length > 0 - ? `${selectedTheme}+${hybridThemes.slice(1).join('+')}` - : selectedTheme + const themeDesc = + hybridThemes.length > 0 + ? `${selectedTheme}+${hybridThemes.slice(1).join('+')}` + : selectedTheme const transformDesc = coordinateSpace !== 'cartesian' ? `[${coordinateSpace}]` : '' const couplingDesc = useParameterCoupling ? '[coupled]' : '' @@ -344,11 +404,13 @@ export class TixyFormulaGenerator { return { expression, theme: selectedTheme, - description: `${themeDesc}${transformDesc}${couplingDesc}: ${descriptions.join(' + ')}` + description: `${themeDesc}${transformDesc}${couplingDesc}: ${descriptions.join(' + ')}`, } } - private generateParams(paramConfig: Record): Record { + private generateParams( + paramConfig: Record + ): Record { const params: Record = {} for (const [key, range] of Object.entries(paramConfig)) { @@ -369,9 +431,19 @@ export class TixyFormulaGenerator { case 'cos_wave': return PATTERN_GENERATORS.cos_wave(params.freq, params.phase, params.axis) case 'ripple': - return PATTERN_GENERATORS.ripple(params.centerX, params.centerY, params.frequency, params.speed) + return PATTERN_GENERATORS.ripple( + params.centerX, + params.centerY, + params.frequency, + params.speed + ) case 'spiral': - return PATTERN_GENERATORS.spiral(params.centerX, params.centerY, params.tightness, params.rotation) + return PATTERN_GENERATORS.spiral( + params.centerX, + params.centerY, + params.tightness, + params.rotation + ) case 'grid': return PATTERN_GENERATORS.grid(params.sizeX, params.sizeY, params.phase) case 'noise': @@ -381,7 +453,12 @@ export class TixyFormulaGenerator { case 'diamond': return PATTERN_GENERATORS.diamond(params.centerX, params.centerY, params.size, params.speed) case 'interference': - return PATTERN_GENERATORS.interference(params.freq1, params.freq2, params.phase1, params.phase2) + return PATTERN_GENERATORS.interference( + params.freq1, + params.freq2, + params.phase1, + params.phase2 + ) case 'mandelbrot_like': return PATTERN_GENERATORS.mandelbrot_like(params.scale, params.iterations) case 'am_modulation': @@ -431,7 +508,7 @@ export class TixyFormulaGenerator { const hybridConfig: ThemeConfig = { patterns: { ...primaryConfig.patterns }, combinators: [...primaryConfig.combinators], - complexity: primaryConfig.complexity + complexity: primaryConfig.complexity, } // Merge patterns from hybrid themes with reduced weights @@ -441,7 +518,7 @@ export class TixyFormulaGenerator { if (!hybridConfig.patterns[patternName]) { hybridConfig.patterns[patternName] = { weight: Math.round(config.weight * 0.5), // Reduced weight for hybrid patterns - params: config.params + params: config.params, } } }) @@ -457,11 +534,14 @@ export class TixyFormulaGenerator { return hybridConfig } - private generateCoupledParams(paramConfig: Record, index: number): Record { + private generateCoupledParams( + paramConfig: Record, + index: number + ): Record { const params: Record = {} // Use mathematical constants and relationships for coupling - const couplingFactors = [1, PHI, PI/4, E/2, Math.sqrt(2), Math.sqrt(3)] + const couplingFactors = [1, PHI, PI / 4, E / 2, Math.sqrt(2), Math.sqrt(3)] const baseFactor = couplingFactors[index % couplingFactors.length] for (const [key, range] of Object.entries(paramConfig)) { @@ -508,9 +588,7 @@ export class TixyFormulaGenerator { } // Replace x and y in the pattern with transformed coordinates - return pattern - .replace(/\bx\b/g, `(${transformedX})`) - .replace(/\by\b/g, `(${transformedY})`) + return pattern.replace(/\bx\b/g, `(${transformedX})`).replace(/\by\b/g, `(${transformedY})`) } private combinePatterns(patterns: string[], combinators: string[]): string { @@ -578,4 +656,4 @@ export class TixyFormulaGenerator { return result } -} \ No newline at end of file +} diff --git a/src/generators/tixy-generator/core/types.ts b/src/generators/tixy-generator/core/types.ts index 16da307..258a6d3 100644 --- a/src/generators/tixy-generator/core/types.ts +++ b/src/generators/tixy-generator/core/types.ts @@ -25,4 +25,4 @@ export interface TixyRenderOptions { export interface TixyResult { imageData: ImageData canvas: HTMLCanvasElement -} \ No newline at end of file +} diff --git a/src/generators/tixy-generator/index.ts b/src/generators/tixy-generator/index.ts index a5c94d8..0503afa 100644 --- a/src/generators/tixy-generator/index.ts +++ b/src/generators/tixy-generator/index.ts @@ -1,3 +1,14 @@ -export { compileTixyExpression, evaluateTixyExpression, EXAMPLE_EXPRESSIONS, generateTixyExpression } from './core/evaluator' +export { + compileTixyExpression, + evaluateTixyExpression, + EXAMPLE_EXPRESSIONS, + generateTixyExpression, +} from './core/evaluator' export { renderTixyToCanvas, renderTixyToImageData } from './renderer/canvas' -export type { TixyParams, TixyFunction, TixyExpression, TixyRenderOptions, TixyResult } from './core/types' \ No newline at end of file +export type { + TixyParams, + TixyFunction, + TixyExpression, + TixyRenderOptions, + TixyResult, +} from './core/types' diff --git a/src/generators/tixy-generator/renderer/canvas.ts b/src/generators/tixy-generator/renderer/canvas.ts index 4b233fc..12c38c6 100644 --- a/src/generators/tixy-generator/renderer/canvas.ts +++ b/src/generators/tixy-generator/renderer/canvas.ts @@ -12,7 +12,7 @@ export function renderTixyToCanvas( backgroundColor = '#000000', foregroundColor = '#ffffff', threshold = 0.3, - pixelSize = 1 + pixelSize = 1, } = options const canvas = document.createElement('canvas') @@ -31,13 +31,14 @@ export function renderTixyToCanvas( const i = y * width + x const value = evaluateTixyExpression(expression, time, i, x, y) - const color = Math.abs(value) > threshold - ? { - r: Math.round(fgColor.r * (Math.sign(value) > 0 ? 1 : 0.8)), - g: Math.round(fgColor.g * (Math.sign(value) > 0 ? 1 : 0.8)), - b: Math.round(fgColor.b * (Math.sign(value) > 0 ? 1 : 0.8)) - } - : bgColor + const color = + Math.abs(value) > threshold + ? { + r: Math.round(fgColor.r * (Math.sign(value) > 0 ? 1 : 0.8)), + g: Math.round(fgColor.g * (Math.sign(value) > 0 ? 1 : 0.8)), + b: Math.round(fgColor.b * (Math.sign(value) > 0 ? 1 : 0.8)), + } + : bgColor for (let py = 0; py < pixelSize; py++) { for (let px = 0; px < pixelSize; px++) { @@ -58,7 +59,7 @@ export function renderTixyToCanvas( return { imageData, - canvas + canvas, } } @@ -71,9 +72,11 @@ export function renderTixyToImageData( function hexToRgb(hex: string): { r: number; g: number; b: number } { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : { r: 0, g: 0, b: 0 } -} \ No newline at end of file + return result + ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + } + : { r: 0, g: 0, b: 0 } +} diff --git a/src/generators/tixy.ts b/src/generators/tixy.ts index 311c543..4b51528 100644 --- a/src/generators/tixy.ts +++ b/src/generators/tixy.ts @@ -9,7 +9,7 @@ const colorPalettes = [ { bg: '#1a1a1a', fg: '#ffffff' }, { bg: '#333333', fg: '#ffffff' }, { bg: '#000000', fg: '#666666' }, - { bg: '#222222', fg: '#dddddd' } + { bg: '#222222', fg: '#dddddd' }, ] export function generateTixyImages(count: number, size: number): GeneratedImage[] { @@ -38,7 +38,7 @@ export function generateTixyImages(count: number, size: number): GeneratedImage[ time, backgroundColor: palette.bg, foregroundColor: palette.fg, - pixelSize: 4 + pixelSize: 4, }) const image = { @@ -46,7 +46,7 @@ export function generateTixyImages(count: number, size: number): GeneratedImage[ canvas: result.canvas, imageData: result.imageData, generator: 'tixy' as const, - params: { expression, time, colors: palette } + params: { expression, time, colors: palette }, } images.push(image) @@ -56,4 +56,4 @@ export function generateTixyImages(count: number, size: number): GeneratedImage[ } return images -} \ No newline at end of file +} diff --git a/src/generators/waveform-generator/README.md b/src/generators/waveform-generator/README.md index f25d17c..543e2e9 100644 --- a/src/generators/waveform-generator/README.md +++ b/src/generators/waveform-generator/README.md @@ -24,7 +24,7 @@ const customWaveform = generateWaveform({ splits: 32, interpolation: 'cubic', randomness: 'smooth', - lineWidth: 3 + lineWidth: 3, }) ``` @@ -33,27 +33,30 @@ const customWaveform = generateWaveform({ ### Main Functions #### `generateWaveform(config?)` + Generate a waveform with optional configuration override. #### `generateRandomWaveform()` + Generate a waveform with randomized parameters for maximum variety. #### `generateWaveformBatch(count)` + Generate multiple random waveforms efficiently. ### Configuration ```typescript interface WaveformConfig { - width: number // Canvas width (default: 256) - height: number // Canvas height (default: 256) - splits: number // Number of control points (8-64) - interpolation: InterpolationType // Curve type - randomness: RandomnessStrategy // Distribution strategy - lineWidth: number // Stroke width (1-4) - backgroundColor: string // Background color - lineColor: string // Line color - smoothness: number // Curve smoothness (0-1) + width: number // Canvas width (default: 256) + height: number // Canvas height (default: 256) + splits: number // Number of control points (8-64) + interpolation: InterpolationType // Curve type + randomness: RandomnessStrategy // Distribution strategy + lineWidth: number // Stroke width (1-4) + backgroundColor: string // Background color + lineColor: string // Line color + smoothness: number // Curve smoothness (0-1) } ``` @@ -95,31 +98,34 @@ waveform-generator/ ## Examples ### Basic Waveform + ```typescript const simple = generateWaveform({ splits: 16, - interpolation: 'linear' + interpolation: 'linear', }) ``` ### Smooth Organic Curves + ```typescript const organic = generateWaveform({ splits: 24, interpolation: 'cubic', randomness: 'smooth', - smoothness: 0.7 + smoothness: 0.7, }) ``` ### Sharp Electronic Waveform + ```typescript const electronic = generateWaveform({ splits: 32, interpolation: 'exponential', randomness: 'uniform', - lineWidth: 1 + lineWidth: 1, }) ``` -The generated waveforms are perfect for audio visualization and spectral synthesis applications. \ No newline at end of file +The generated waveforms are perfect for audio visualization and spectral synthesis applications. diff --git a/src/generators/waveform-generator/core/generator.ts b/src/generators/waveform-generator/core/generator.ts index ec34caf..ee39af3 100644 --- a/src/generators/waveform-generator/core/generator.ts +++ b/src/generators/waveform-generator/core/generator.ts @@ -1,4 +1,4 @@ -import type { ControlPoint, WaveformConfig, RandomnessStrategy } from './types' +import type { ControlPoint, WaveformConfig, RandomnessStrategy, InterpolationType } from './types' import { smoothControlPoints, applyTension } from './interpolation' // Generate random control points based on configuration @@ -81,7 +81,8 @@ function generateRandomY( // Generate gaussian-distributed random numbers using Box-Muller transform function gaussianRandom(mean: number = 0, stdDev: number = 1): number { - let u = 0, v = 0 + let u = 0, + v = 0 while (u === 0) u = Math.random() // Converting [0,1) to (0,1) while (v === 0) v = Math.random() @@ -106,7 +107,7 @@ export function generateWaveformVariation(baseConfig: WaveformConfig): WaveformC // Pure interpolation types (60% of patterns) { type: 'pure', weight: 0.6 }, // Blended/mixed interpolation (40% of patterns) - { type: 'blended', weight: 0.4 } + { type: 'blended', weight: 0.4 }, ] const strategyType = weightedRandomChoice(strategies) @@ -124,7 +125,7 @@ function generatePureInterpolationVariation(baseConfig: WaveformConfig): Wavefor { interpolation: 'linear' as const, weight: 0.3 }, { interpolation: 'exponential' as const, weight: 0.25 }, { interpolation: 'logarithmic' as const, weight: 0.25 }, - { interpolation: 'cubic' as const, weight: 0.2 } + { interpolation: 'cubic' as const, weight: 0.2 }, ] const interpolationType = weightedRandomChoice(pureTypes) @@ -132,10 +133,10 @@ function generatePureInterpolationVariation(baseConfig: WaveformConfig): Wavefor return { ...baseConfig, splits: randomChoice([8, 12, 16, 20, 24, 32]), - interpolation: interpolationType, + interpolation: interpolationType as InterpolationType, randomness: randomChoice(['uniform', 'gaussian', 'smooth'] as const), smoothness: randomChoice([0.2, 0.4, 0.6, 0.8]), - lineWidth: 2 + lineWidth: 2, } } @@ -150,29 +151,29 @@ function generateBlendedInterpolationVariation(baseConfig: WaveformConfig): Wave interpolation: 'cubic' as const, randomness: 'gaussian' as const, smoothness: 0.8, - splits: 16 + splits: 16, }, // Low smoothness + uniform = sharp chaotic lines { interpolation: 'linear' as const, randomness: 'uniform' as const, smoothness: 0.1, - splits: 32 + splits: 32, }, // Exponential + smooth = flowing acceleration curves { interpolation: 'exponential' as const, randomness: 'smooth' as const, smoothness: 0.6, - splits: 20 + splits: 20, }, // Logarithmic + gaussian = natural decay curves { interpolation: 'logarithmic' as const, randomness: 'gaussian' as const, smoothness: 0.5, - splits: 24 - } + splits: 24, + }, ] const config = randomChoice(blendedConfigs) @@ -180,7 +181,7 @@ function generateBlendedInterpolationVariation(baseConfig: WaveformConfig): Wave return { ...baseConfig, ...config, - lineWidth: 2 + lineWidth: 2, } } @@ -190,7 +191,7 @@ function randomChoice(array: readonly T[]): T { } // Weighted random selection utility -function weightedRandomChoice(choices: T[]): T[keyof T] { +function weightedRandomChoice(choices: T[]): any { const totalWeight = choices.reduce((sum, choice) => sum + choice.weight, 0) let random = Math.random() * totalWeight @@ -209,5 +210,7 @@ export function generateMultipleWaveforms( count: number, baseConfig: WaveformConfig ): WaveformConfig[] { - return Array(count).fill(null).map(() => generateWaveformVariation(baseConfig)) -} \ No newline at end of file + return Array(count) + .fill(null) + .map(() => generateWaveformVariation(baseConfig)) +} diff --git a/src/generators/waveform-generator/core/interpolation.ts b/src/generators/waveform-generator/core/interpolation.ts index c1b9618..683a1e0 100644 --- a/src/generators/waveform-generator/core/interpolation.ts +++ b/src/generators/waveform-generator/core/interpolation.ts @@ -8,7 +8,6 @@ export function interpolate( type: InterpolationType, smoothness: number = 0.5 ): number { - const dx = p2.x - p1.x const dy = p2.y - p1.y switch (type) { @@ -93,10 +92,7 @@ export function smoothControlPoints( } // Calculate curve tension for natural-looking waveforms -export function applyTension( - points: ControlPoint[], - tension: number = 0.5 -): ControlPoint[] { +export function applyTension(points: ControlPoint[], tension: number = 0.5): ControlPoint[] { if (points.length <= 2) return points const tensioned = [...points] @@ -114,4 +110,4 @@ export function applyTension( } return tensioned -} \ No newline at end of file +} diff --git a/src/generators/waveform-generator/core/types.ts b/src/generators/waveform-generator/core/types.ts index ff8124d..afd8130 100644 --- a/src/generators/waveform-generator/core/types.ts +++ b/src/generators/waveform-generator/core/types.ts @@ -37,7 +37,7 @@ export const DEFAULT_WAVEFORM_CONFIG: WaveformConfig = { lineWidth: 0.5, // Very thin line for precise frequency definition backgroundColor: '#000000', lineColor: '#ffffff', - smoothness: 0.5 + smoothness: 0.5, } // Common split counts for variety @@ -48,5 +48,5 @@ export const INTERPOLATION_WEIGHTS = { linear: 0.3, exponential: 0.25, logarithmic: 0.25, - cubic: 0.2 -} as const \ No newline at end of file + cubic: 0.2, +} as const diff --git a/src/generators/waveform-generator/index.ts b/src/generators/waveform-generator/index.ts index 391b740..6f1a4bd 100644 --- a/src/generators/waveform-generator/index.ts +++ b/src/generators/waveform-generator/index.ts @@ -6,35 +6,31 @@ export type { RandomnessStrategy, ControlPoint, WaveformConfig, - WaveformResult + WaveformResult, } from './core/types' -export { - DEFAULT_WAVEFORM_CONFIG, - SPLIT_COUNTS, - INTERPOLATION_WEIGHTS -} from './core/types' +export { DEFAULT_WAVEFORM_CONFIG, SPLIT_COUNTS, INTERPOLATION_WEIGHTS } from './core/types' // Interpolation functions export { interpolate, generateCurvePoints, smoothControlPoints, - applyTension + applyTension, } from './core/interpolation' // Generation logic export { generateControlPoints, generateWaveformVariation, - generateMultipleWaveforms + generateMultipleWaveforms, } from './core/generator' // Canvas rendering export { renderWaveformToCanvas, renderWaveformWithBezier, - renderMultipleWaveforms + renderMultipleWaveforms, } from './renderer/canvas' // Main convenience function for generating complete waveforms @@ -56,5 +52,7 @@ export function generateRandomWaveform(): WaveformResult { } export function generateWaveformBatch(count: number): WaveformResult[] { - return Array(count).fill(null).map(() => generateRandomWaveform()) -} \ No newline at end of file + return Array(count) + .fill(null) + .map(() => generateRandomWaveform()) +} diff --git a/src/generators/waveform-generator/renderer/canvas.ts b/src/generators/waveform-generator/renderer/canvas.ts index d85b36f..09f5d7e 100644 --- a/src/generators/waveform-generator/renderer/canvas.ts +++ b/src/generators/waveform-generator/renderer/canvas.ts @@ -44,7 +44,7 @@ export function renderWaveformToCanvas( canvas, imageData, controlPoints, - config + config, } } @@ -114,7 +114,7 @@ export function renderWaveformWithBezier( canvas, imageData, controlPoints, - config + config, } } @@ -178,12 +178,7 @@ export function renderMultipleWaveforms( ctx.lineCap = 'round' ctx.lineJoin = 'round' - const curvePoints = generateCurvePoints( - points, - config.interpolation, - config.smoothness, - 6 - ) + const curvePoints = generateCurvePoints(points, config.interpolation, config.smoothness, 6) drawSmoothCurve(ctx, curvePoints) }) @@ -194,6 +189,6 @@ export function renderMultipleWaveforms( canvas, imageData, controlPoints: waveformData[0]?.points || [], - config: firstConfig || waveformData[0]?.config + config: firstConfig || waveformData[0]?.config, } -} \ No newline at end of file +} diff --git a/src/generators/waveform.ts b/src/generators/waveform.ts index 4e61757..af624e8 100644 --- a/src/generators/waveform.ts +++ b/src/generators/waveform.ts @@ -1,4 +1,6 @@ -import { generateRandomWaveform, generateWaveformVariation, DEFAULT_WAVEFORM_CONFIG } from './waveform-generator' +import { + generateRandomWaveform, +} from './waveform-generator' import type { GeneratedImage } from '../stores' // Generate multiple random waveform images @@ -40,8 +42,8 @@ export function generateWaveformImages(count: number, size: number = 256): Gener randomness: waveform.config.randomness, smoothness: waveform.config.smoothness, lineWidth: waveform.config.lineWidth, - size - } + size, + }, } images.push(image) @@ -91,8 +93,8 @@ export function generateVariedWaveforms(count: number, size: number = 256): Gene smoothness: waveform.config.smoothness, lineWidth: waveform.config.lineWidth, style: 'varied', - size - } + size, + }, } images.push(image) @@ -102,4 +104,4 @@ export function generateVariedWaveforms(count: number, size: number = 256): Gene } return images -} \ No newline at end of file +} diff --git a/src/generators/webcam-generator/index.ts b/src/generators/webcam-generator/index.ts index 1ef194e..b25befc 100644 --- a/src/generators/webcam-generator/index.ts +++ b/src/generators/webcam-generator/index.ts @@ -1,2 +1,2 @@ export * from './types' -export * from './processor' \ No newline at end of file +export * from './processor' diff --git a/src/generators/webcam-generator/processor.ts b/src/generators/webcam-generator/processor.ts index f881a66..084f530 100644 --- a/src/generators/webcam-generator/processor.ts +++ b/src/generators/webcam-generator/processor.ts @@ -10,8 +10,8 @@ export class WebcamProcessor { video: { width: { ideal: 1280 }, height: { ideal: 720 }, - facingMode: 'user' - } + facingMode: 'user', + }, }) this.video = document.createElement('video') @@ -66,7 +66,7 @@ export class WebcamProcessor { canvas, imageData, capturedAt: new Date(), - config + config, } } @@ -89,7 +89,7 @@ export class WebcamProcessor { videoWidth: number, videoHeight: number, targetSize: number - ): { drawWidth: number, drawHeight: number, offsetX: number, offsetY: number } { + ): { drawWidth: number; drawHeight: number; offsetX: number; offsetY: number } { const aspectRatio = videoWidth / videoHeight let drawWidth: number @@ -114,7 +114,11 @@ export class WebcamProcessor { return { drawWidth, drawHeight, offsetX, offsetY } } - private convertToGrayscale(ctx: CanvasRenderingContext2D, size: number, enhanceContrast: boolean) { + private convertToGrayscale( + ctx: CanvasRenderingContext2D, + size: number, + enhanceContrast: boolean + ) { const imageData = ctx.getImageData(0, 0, size, size) const data = imageData.data @@ -133,7 +137,7 @@ export class WebcamProcessor { } // Set R, G, B to the same grayscale value - data[i] = final // Red + data[i] = final // Red data[i + 1] = final // Green data[i + 2] = final // Blue // Alpha (data[i + 3]) remains unchanged @@ -142,4 +146,4 @@ export class WebcamProcessor { // Put the processed data back ctx.putImageData(imageData, 0, 0) } -} \ No newline at end of file +} diff --git a/src/generators/webcam-generator/types.ts b/src/generators/webcam-generator/types.ts index c14e47f..43c3b15 100644 --- a/src/generators/webcam-generator/types.ts +++ b/src/generators/webcam-generator/types.ts @@ -9,4 +9,4 @@ export interface WebcamCaptureResult { imageData: ImageData capturedAt: Date config: WebcamCaptureConfig -} \ No newline at end of file +} diff --git a/src/generators/webcam.ts b/src/generators/webcam.ts index 2ce7c90..2dc3da9 100644 --- a/src/generators/webcam.ts +++ b/src/generators/webcam.ts @@ -9,7 +9,7 @@ export async function generateFromWebcamImage( const result = await processor.capturePhoto({ targetSize: size, contrastEnhancement: true, - grayscaleConversion: true + grayscaleConversion: true, }) const image: GeneratedImage = { @@ -21,8 +21,8 @@ export async function generateFromWebcamImage( capturedAt: result.capturedAt.toISOString(), size, contrastEnhanced: true, - grayscaleConverted: true - } + grayscaleConverted: true, + }, } return image @@ -45,10 +45,10 @@ export async function generateFromWebcamImage( generator: 'webcam', params: { error: error instanceof Error ? error.message : 'Unknown error', - size - } + size, + }, } return fallbackImage } -} \ No newline at end of file +} diff --git a/src/index.css b/src/index.css index a461c50..d4b5078 100644 --- a/src/index.css +++ b/src/index.css @@ -1 +1 @@ -@import "tailwindcss"; \ No newline at end of file +@import 'tailwindcss'; diff --git a/src/main.tsx b/src/main.tsx index bef5202..2caec89 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -6,5 +6,5 @@ import App from './App.tsx' createRoot(document.getElementById('root')!).render( - , + ) diff --git a/src/spectral-synthesis/README.md b/src/spectral-synthesis/README.md index 47ec2ad..b305e6e 100644 --- a/src/spectral-synthesis/README.md +++ b/src/spectral-synthesis/README.md @@ -23,7 +23,7 @@ const audioData = synthesizeFromImage(imageData, { duration: 10, minFreq: 100, maxFreq: 10000, - maxPartials: 200 + maxPartials: 200, }) // Export as WAV @@ -35,6 +35,7 @@ downloadWAV(audioData, 44100, 'my-audio.wav') ### Main Functions #### `synthesizeFromImage(imageData, params?)` + - **imageData**: `ImageData` - Canvas image data - **params**: `Partial` - Optional parameters - **Returns**: `Float32Array` - Audio samples @@ -42,16 +43,17 @@ downloadWAV(audioData, 44100, 'my-audio.wav') ### Types #### `SynthesisParams` + ```typescript interface SynthesisParams { - duration: number // Audio duration in seconds - minFreq: number // Minimum frequency in Hz - maxFreq: number // Maximum frequency in Hz - sampleRate: number // Sample rate in Hz + duration: number // Audio duration in seconds + minFreq: number // Minimum frequency in Hz + maxFreq: number // Maximum frequency in Hz + sampleRate: number // Sample rate in Hz frequencyResolution: number // Frequency bin downsampling - timeResolution: number // Time slice downsampling + timeResolution: number // Time slice downsampling amplitudeThreshold: number // Minimum amplitude threshold - maxPartials: number // Maximum simultaneous partials + maxPartials: number // Maximum simultaneous partials } ``` @@ -80,6 +82,7 @@ spectral-synthesis/ ## Usage Examples ### Basic Synthesis + ```typescript const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') @@ -89,14 +92,15 @@ const audio = synthesizeFromImage(imageData) ``` ### Advanced Usage + ```typescript import { ImageToAudioSynthesizer } from './spectral-synthesis' const synthesizer = new ImageToAudioSynthesizer({ duration: 5, - maxPartials: 150 + maxPartials: 150, }) const result = synthesizer.synthesize(imageData) console.log(`Generated ${result.duration}s of audio`) -``` \ No newline at end of file +``` diff --git a/src/spectral-synthesis/audio/export.ts b/src/spectral-synthesis/audio/export.ts index 11503c4..18e86e0 100644 --- a/src/spectral-synthesis/audio/export.ts +++ b/src/spectral-synthesis/audio/export.ts @@ -25,7 +25,7 @@ export function createWAVBuffer(audioData: Float32Array, sampleRate: number): Ar let offset = 44 for (let i = 0; i < length; i++) { const sample = Math.max(-1, Math.min(1, audioData[i])) - view.setInt16(offset, sample * 0x7FFF, true) + view.setInt16(offset, sample * 0x7fff, true) offset += 2 } @@ -83,7 +83,9 @@ export function createAudioPlayer(audioData: Float32Array, sampleRate: number): gainNode.connect(audioContext.destination) if (audioContext.sampleRate !== sampleRate) { - console.warn(`Audio context sample rate (${audioContext.sampleRate}) differs from data sample rate (${sampleRate})`) + console.warn( + `Audio context sample rate (${audioContext.sampleRate}) differs from data sample rate (${sampleRate})` + ) } } } @@ -102,7 +104,8 @@ export function createAudioPlayer(audioData: Float32Array, sampleRate: number): if (isPaused) { // Resume from pause is not supported with AudioBufferSource - // We need to restart from the beginning + // We need to restart from the beginning (pausedAt was ${pausedAt}s) + void pausedAt // Track for future resume feature isPaused = false pausedAt = 0 } @@ -163,7 +166,7 @@ export function createAudioPlayer(audioData: Float32Array, sampleRate: number): onStateChange(callback: (isPlaying: boolean) => void) { stateCallback = callback - } + }, } } @@ -174,11 +177,11 @@ export async function playAudio(audioData: Float32Array, sampleRate: number): Pr const player = createAudioPlayer(audioData, sampleRate) return new Promise(resolve => { - player.onStateChange((isPlaying) => { + player.onStateChange(isPlaying => { if (!isPlaying) { resolve() } }) player.play() }) -} \ No newline at end of file +} diff --git a/src/spectral-synthesis/core/synthesizer.ts b/src/spectral-synthesis/core/synthesizer.ts index 8b18d9f..dcaa89f 100644 --- a/src/spectral-synthesis/core/synthesizer.ts +++ b/src/spectral-synthesis/core/synthesizer.ts @@ -9,7 +9,7 @@ import { generateSpectralDensity, mapFrequency, mapFrequencyLinear, - normalizeAudioGlobal + normalizeAudioGlobal, } from './utils' /** @@ -78,7 +78,7 @@ export class ImageToAudioSynthesizer { disableNormalization: false, disableContrast: false, exactBinMapping: false, - ...params + ...params, } } @@ -93,7 +93,6 @@ export class ImageToAudioSynthesizer { } } - /** * Custom synthesis mode - sophisticated audio processing */ @@ -112,7 +111,7 @@ export class ImageToAudioSynthesizer { spectralDensity, usePerceptualWeighting, frequencyMapping, - invert = false + invert = false, } = this.params // Calculate synthesis parameters @@ -136,7 +135,6 @@ export class ImageToAudioSynthesizer { const previousAmplitudes = new Float32Array(effectiveHeight) const smoothingFactor = 0.2 // Reduced for sharper transients - // Process each time slice for (let col = 0; col < effectiveWidth; col++) { const sourceCol = col @@ -160,9 +158,12 @@ export class ImageToAudioSynthesizer { return normalizedDb }) - // Detect spectral peaks - const peaks = detectSpectralPeaks(processedSpectrum, Math.min(amplitudeThreshold, 0.01), false) + const peaks = detectSpectralPeaks( + processedSpectrum, + Math.min(amplitudeThreshold, 0.01), + false + ) // Generate partials from peaks with spectral density const partials: SpectralPeak[] = [] @@ -176,14 +177,21 @@ export class ImageToAudioSynthesizer { } else if (frequencyMapping === 'linear') { frequency = mapFrequencyLinear(peakRow, effectiveHeight, minFreq, maxFreq) } else { - frequency = mapFrequency(peakRow, effectiveHeight, minFreq, maxFreq, frequencyMapping || 'mel') + frequency = mapFrequency( + peakRow, + effectiveHeight, + minFreq, + maxFreq, + frequencyMapping || 'mel' + ) } let amplitude = processedSpectrum[peakRow] // Apply temporal smoothing if (col > 0) { - amplitude = smoothingFactor * previousAmplitudes[peakRow] + (1 - smoothingFactor) * amplitude + amplitude = + smoothingFactor * previousAmplitudes[peakRow] + (1 - smoothingFactor) * amplitude } previousAmplitudes[peakRow] = amplitude @@ -236,7 +244,7 @@ export class ImageToAudioSynthesizer { return { audio: normalizedAudio, sampleRate, - duration + duration, } } @@ -256,7 +264,7 @@ export class ImageToAudioSynthesizer { disableNormalization = false, disableContrast = false, exactBinMapping = true, - invert = false + invert = false, } = this.params const totalSamples = Math.floor(duration * sampleRate) @@ -282,7 +290,6 @@ export class ImageToAudioSynthesizer { } } // Map image rows to these exact bins - console.log(`Ultra-precise mode: Using ${freqBins.length} exact FFT bins from ${minFreq}Hz to ${maxFreq}Hz`) } else { // Linear frequency mapping freqBins = [] @@ -339,16 +346,16 @@ export class ImageToAudioSynthesizer { const contrast = this.params.contrast || 1.0 // Fast power optimization for common cases if (contrast === 1.0) { - amplitude = intensity // No contrast + amplitude = intensity // No contrast } else if (contrast === 2.0) { - amplitude = intensity * intensity // Square is much faster than Math.pow + amplitude = intensity * intensity // Square is much faster than Math.pow } else if (contrast === 0.5) { - amplitude = Math.sqrt(intensity) // Square root is faster than Math.pow + amplitude = Math.sqrt(intensity) // Square root is faster than Math.pow } else if (contrast === 3.0) { - amplitude = intensity * intensity * intensity // Cube + amplitude = intensity * intensity * intensity // Cube } else if (contrast === 4.0) { const sq = intensity * intensity - amplitude = sq * sq // Fourth power + amplitude = sq * sq // Fourth power } else { // Fast power approximation for arbitrary values // Uses bit manipulation + lookup for ~10x speedup over Math.pow @@ -380,8 +387,8 @@ export class ImageToAudioSynthesizer { // Phase increment method - mathematically identical but much faster // Eliminates array lookups and multiplications in tight loop - let phase = freqCoeff * startSample / sampleRate // Initial phase - const phaseIncrement = freqCoeff / sampleRate // Phase per sample + let phase = (freqCoeff * startSample) / sampleRate // Initial phase + const phaseIncrement = freqCoeff / sampleRate // Phase per sample for (let i = 0; i < frameLength; i++) { columnSpectrum[i] += amplitude * Math.sin(phase) phase += phaseIncrement @@ -415,7 +422,7 @@ export class ImageToAudioSynthesizer { return { audio, sampleRate, - duration + duration, } } @@ -470,7 +477,7 @@ export function createDirectParams(overrides: Partial = {}): Sy disableNormalization: false, disableContrast: false, exactBinMapping: false, - ...overrides + ...overrides, } } @@ -498,7 +505,7 @@ export function createCustomParams(overrides: Partial = {}): Sy disableNormalization: false, disableContrast: false, exactBinMapping: false, - ...overrides + ...overrides, } } @@ -524,4 +531,4 @@ export function synthesizeCustom( const customParams = createCustomParams(params) const synthesizer = new ImageToAudioSynthesizer(customParams) return synthesizer.synthesize(imageData) -} \ No newline at end of file +} diff --git a/src/spectral-synthesis/core/types.ts b/src/spectral-synthesis/core/types.ts index d171a9d..0bd9cd2 100644 --- a/src/spectral-synthesis/core/types.ts +++ b/src/spectral-synthesis/core/types.ts @@ -32,4 +32,4 @@ export interface SynthesisResult { audio: Float32Array sampleRate: number duration: number -} \ No newline at end of file +} diff --git a/src/spectral-synthesis/core/utils.ts b/src/spectral-synthesis/core/utils.ts index 5e5cff1..ef3c378 100644 --- a/src/spectral-synthesis/core/utils.ts +++ b/src/spectral-synthesis/core/utils.ts @@ -27,8 +27,9 @@ export function barkToHz(bark: number): number { let freq = 1000 // Initial guess for (let i = 0; i < 10; i++) { const barkEst = hzToBark(freq) - const derivative = 13 * 0.00076 / (1 + Math.pow(0.00076 * freq, 2)) + - 3.5 * 2 * (freq / 7500) * (1 / 7500) / (1 + Math.pow(freq / 7500, 4)) + const derivative = + (13 * 0.00076) / (1 + Math.pow(0.00076 * freq, 2)) + + (3.5 * 2 * (freq / 7500) * (1 / 7500)) / (1 + Math.pow(freq / 7500, 4)) freq = freq - (barkEst - bark) / derivative if (Math.abs(hzToBark(freq) - bark) < 0.001) break } @@ -58,7 +59,11 @@ export function applyAmplitudeCurve(amplitude: number, curve: string, gamma: num /** * Apply soft thresholding using tanh function */ -export function applySoftThreshold(amplitude: number, threshold: number, softness: number = 0.1): number { +export function applySoftThreshold( + amplitude: number, + threshold: number, + softness: number = 0.1 +): number { if (threshold <= 0) return amplitude const ratio = amplitude / threshold @@ -76,7 +81,13 @@ export function applySoftThreshold(amplitude: number, threshold: number, softnes /** * Map frequency using specified scale */ -export function mapFrequency(row: number, totalRows: number, minFreq: number, maxFreq: number, scale: string): number { +export function mapFrequency( + row: number, + totalRows: number, + minFreq: number, + maxFreq: number, + scale: string +): number { const normalizedRow = row / (totalRows - 1) switch (scale) { @@ -109,7 +120,11 @@ export function mapFrequency(row: number, totalRows: number, minFreq: number, ma /** * Detect spectral peaks in amplitude spectrum with optional smoothing */ -export function detectSpectralPeaks(spectrum: number[], threshold: number = 0.01, useSmoothing: boolean = false): number[] { +export function detectSpectralPeaks( + spectrum: number[], + threshold: number = 0.01, + useSmoothing: boolean = false +): number[] { if (useSmoothing) { return detectSmoothSpectralPeaks(spectrum, threshold) } @@ -126,9 +141,7 @@ export function detectSpectralPeaks(spectrum: number[], threshold: number = 0.01 // Fallback: use local maxima with lower threshold if no peaks found if (peaks.length === 0) { for (let i = 1; i < spectrum.length - 1; i++) { - if (spectrum[i] > spectrum[i - 1] && - spectrum[i] > spectrum[i + 1] && - spectrum[i] > 0.001) { + if (spectrum[i] > spectrum[i - 1] && spectrum[i] > spectrum[i + 1] && spectrum[i] > 0.001) { peaks.push(i) } } @@ -148,12 +161,13 @@ export function detectSmoothSpectralPeaks(spectrum: number[], threshold: number for (let i = 2; i < smoothedSpectrum.length - 2; i++) { const current = smoothedSpectrum[i] - if (current > threshold && - current > smoothedSpectrum[i - 1] && - current > smoothedSpectrum[i + 1] && - current > smoothedSpectrum[i - 2] && - current > smoothedSpectrum[i + 2]) { - + if ( + current > threshold && + current > smoothedSpectrum[i - 1] && + current > smoothedSpectrum[i + 1] && + current > smoothedSpectrum[i - 2] && + current > smoothedSpectrum[i + 2] + ) { // Find the exact peak position with sub-bin accuracy using parabolic interpolation const y1 = smoothedSpectrum[i - 1] const y2 = smoothedSpectrum[i] @@ -199,7 +213,11 @@ function smoothSpectrum(spectrum: number[], windowSize: number): number[] { let sum = 0 let count = 0 - for (let j = Math.max(0, i - halfWindow); j <= Math.min(spectrum.length - 1, i + halfWindow); j++) { + for ( + let j = Math.max(0, i - halfWindow); + j <= Math.min(spectrum.length - 1, i + halfWindow); + j++ + ) { sum += spectrum[j] count++ } @@ -213,7 +231,11 @@ function smoothSpectrum(spectrum: number[], windowSize: number): number[] { /** * Apply perceptual amplitude weighting with contrast control */ -export function perceptualAmplitudeWeighting(freq: number, amplitude: number, contrast: number = 2.2): number { +export function perceptualAmplitudeWeighting( + freq: number, + amplitude: number, + contrast: number = 2.2 +): number { // Apply contrast curve first (like LeviBorodenko's approach) const contrastedAmplitude = Math.pow(amplitude, contrast) @@ -223,8 +245,6 @@ export function perceptualAmplitudeWeighting(freq: number, amplitude: number, co return contrastedAmplitude * weight } - - /** * Generate spectral density by creating multiple tones per frequency bin * Inspired by LeviBorodenko's multi-tone approach @@ -239,13 +259,13 @@ export function generateSpectralDensity( const toneSpacing = bandwidth / numTones for (let i = 0; i < numTones; i++) { - const freq = centerFreq + (i - numTones/2) * toneSpacing - const toneAmplitude = amplitude * (1 - Math.abs(i - numTones/2) / numTones * 0.3) // Slight amplitude variation + const freq = centerFreq + (i - numTones / 2) * toneSpacing + const toneAmplitude = amplitude * (1 - (Math.abs(i - numTones / 2) / numTones) * 0.3) // Slight amplitude variation peaks.push({ frequency: freq, amplitude: toneAmplitude, - phase: 0 + phase: 0, }) } @@ -328,7 +348,8 @@ export function analyzeImageBrightness(imageData: ImageData): { const avgEdgeBrightness = edgePixels > 0 ? edgeBrightness / edgePixels : meanBrightness // Calculate contrast (standard deviation) - const variance = brightnesses.reduce((sum, b) => sum + Math.pow(b - meanBrightness, 2), 0) / brightnesses.length + const variance = + brightnesses.reduce((sum, b) => sum + Math.pow(b - meanBrightness, 2), 0) / brightnesses.length const contrast = Math.sqrt(variance) // Make recommendation @@ -346,7 +367,7 @@ export function analyzeImageBrightness(imageData: ImageData): { medianBrightness, edgeBrightness: avgEdgeBrightness, contrast, - recommendation + recommendation, } } @@ -366,19 +387,19 @@ export function generateWindow(length: number, windowType: string): Float32Array switch (windowType) { case 'hann': for (let i = 0; i < length; i++) { - window[i] = 0.5 * (1 - Math.cos(2 * Math.PI * i / (length - 1))) + window[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / (length - 1))) } break case 'hamming': for (let i = 0; i < length; i++) { - window[i] = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (length - 1)) + window[i] = 0.54 - 0.46 * Math.cos((2 * Math.PI * i) / (length - 1)) } break case 'blackman': for (let i = 0; i < length; i++) { - const factor = 2 * Math.PI * i / (length - 1) + const factor = (2 * Math.PI * i) / (length - 1) window[i] = 0.42 - 0.5 * Math.cos(factor) + 0.08 * Math.cos(2 * factor) } break @@ -450,7 +471,12 @@ export function extractSpectrum( /** * Alternative linear frequency mapping inspired by alexadam's approach */ -export function mapFrequencyLinear(row: number, totalRows: number, minFreq: number, maxFreq: number): number { +export function mapFrequencyLinear( + row: number, + totalRows: number, + minFreq: number, + maxFreq: number +): number { // Direct linear mapping from top to bottom (high freq at top) const normalizedRow = row / (totalRows - 1) return maxFreq - normalizedRow * (maxFreq - minFreq) @@ -481,4 +507,4 @@ export function normalizeAudioGlobal(audio: Float32Array, targetLevel: number = } return normalized -} \ No newline at end of file +} diff --git a/src/spectral-synthesis/index.ts b/src/spectral-synthesis/index.ts index 5bba1c4..71054e7 100644 --- a/src/spectral-synthesis/index.ts +++ b/src/spectral-synthesis/index.ts @@ -5,7 +5,7 @@ export { createDirectParams, createCustomParams, synthesizeDirect, - synthesizeCustom + synthesizeCustom, } from './core/synthesizer' export type { SynthesisParams, SpectralPeak, SynthesisResult, WindowType } from './core/types' @@ -25,14 +25,9 @@ export { mapFrequency, mapFrequencyLinear, normalizeAudioGlobal, - generateSpectralDensity + generateSpectralDensity, } from './core/utils' // Audio export -export { - createWAVBuffer, - downloadWAV, - playAudio, - createAudioPlayer -} from './audio/export' -export type { AudioPlayer } from './audio/export' \ No newline at end of file +export { createWAVBuffer, downloadWAV, playAudio, createAudioPlayer } from './audio/export' +export type { AudioPlayer } from './audio/export' diff --git a/src/stores/index.ts b/src/stores/index.ts index a134957..f9b882f 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -1,7 +1,20 @@ import { atom } from 'nanostores' import type { SynthesisParams } from '../spectral-synthesis' -export type GeneratorType = 'tixy' | 'picsum' | 'art-institute' | 'waveform' | 'partials' | 'slides' | 'shapes' | 'bands' | 'dust' | 'geopattern' | 'from-photo' | 'webcam' | 'harmonics' +export type GeneratorType = + | 'tixy' + | 'picsum' + | 'art-institute' + | 'waveform' + | 'partials' + | 'slides' + | 'shapes' + | 'bands' + | 'dust' + | 'geopattern' + | 'from-photo' + | 'webcam' + | 'harmonics' export interface GeneratedImage { id: string @@ -22,7 +35,7 @@ export const appSettings = atom({ selectedGenerator: 'tixy', gridSize: 25, backgroundColor: '#000000', - foregroundColor: '#ffffff' + foregroundColor: '#ffffff', }) export const generatedImages = atom([]) @@ -54,5 +67,5 @@ export const synthesisParams = atom({ frameOverlap: 0.75, disableNormalization: false, disableContrast: false, - exactBinMapping: false -}) \ No newline at end of file + exactBinMapping: false, +}) diff --git a/src/types/geopattern.d.ts b/src/types/geopattern.d.ts new file mode 100644 index 0000000..0acb569 --- /dev/null +++ b/src/types/geopattern.d.ts @@ -0,0 +1,17 @@ +declare module 'geopattern' { + interface GeoPattern { + toDataUri(): string + toString(): string + toSvg(): string + } + + interface GeoPatternOptions { + color?: string + baseColor?: string + generator?: string + } + + function generate(input: string, options?: GeoPatternOptions): GeoPattern + + export = generate +} \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 89a305e..7141e45 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,11 +1,8 @@ /** @type {import('tailwindcss').Config} */ export default { - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", - ], + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], theme: { extend: {}, }, plugins: [], -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 1ffef60..d32ff68 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,4 @@ { "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } diff --git a/vite.config.ts b/vite.config.ts index 8b0f57b..d0d52ae 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,4 +4,41 @@ import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + build: { + // Generate source maps for production debugging + sourcemap: false, + // Optimize chunk size threshold + chunkSizeWarningLimit: 1000, + // Enable minification (use default for rolldown) + minify: true, + // Rollup options for advanced bundling + rollupOptions: { + output: { + // Manual chunk splitting for better caching + manualChunks: (id) => { + if (id.includes('react') || id.includes('react-dom')) { + return 'react' + } + if (id.includes('nanostores')) { + return 'stores' + } + if (id.includes('jszip') || id.includes('geopattern')) { + return 'vendor' + } + }, + // Optimize chunk file names + chunkFileNames: 'assets/[name]-[hash].js', + entryFileNames: 'assets/[name]-[hash].js', + assetFileNames: 'assets/[name]-[hash].[ext]', + }, + }, + // Target modern browsers for better optimization + target: 'esnext', + // Enable CSS code splitting + cssCodeSplit: true, + }, + // Optimize dependency pre-bundling + optimizeDeps: { + include: ['react', 'react-dom', 'nanostores', '@nanostores/react'], + }, })