feat: init
a414062c
37 file(s) · +2020 −0
| 1 | + | # Logs |
|
| 2 | + | logs |
|
| 3 | + | *.log |
|
| 4 | + | npm-debug.log* |
|
| 5 | + | yarn-debug.log* |
|
| 6 | + | yarn-error.log* |
|
| 7 | + | pnpm-debug.log* |
|
| 8 | + | lerna-debug.log* |
|
| 9 | + | ||
| 10 | + | node_modules |
|
| 11 | + | dist |
|
| 12 | + | dist-ssr |
|
| 13 | + | *.local |
|
| 14 | + | ||
| 15 | + | # Editor directories and files |
|
| 16 | + | .vscode/* |
|
| 17 | + | !.vscode/extensions.json |
|
| 18 | + | .idea |
|
| 19 | + | .DS_Store |
|
| 20 | + | *.suo |
|
| 21 | + | *.ntvs* |
|
| 22 | + | *.njsproj |
|
| 23 | + | *.sln |
|
| 24 | + | *.sw? |
| 1 | + | # React + TypeScript + Vite |
|
| 2 | + | ||
| 3 | + | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. |
|
| 4 | + | ||
| 5 | + | Currently, two official plugins are available: |
|
| 6 | + | ||
| 7 | + | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) |
|
| 8 | + | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) |
|
| 9 | + | ||
| 10 | + | ## React Compiler |
|
| 11 | + | ||
| 12 | + | The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). |
|
| 13 | + | ||
| 14 | + | ## Expanding the ESLint configuration |
|
| 15 | + | ||
| 16 | + | If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: |
|
| 17 | + | ||
| 18 | + | ```js |
|
| 19 | + | export default defineConfig([ |
|
| 20 | + | globalIgnores(['dist']), |
|
| 21 | + | { |
|
| 22 | + | files: ['**/*.{ts,tsx}'], |
|
| 23 | + | extends: [ |
|
| 24 | + | // Other configs... |
|
| 25 | + | ||
| 26 | + | // Remove tseslint.configs.recommended and replace with this |
|
| 27 | + | tseslint.configs.recommendedTypeChecked, |
|
| 28 | + | // Alternatively, use this for stricter rules |
|
| 29 | + | tseslint.configs.strictTypeChecked, |
|
| 30 | + | // Optionally, add this for stylistic rules |
|
| 31 | + | tseslint.configs.stylisticTypeChecked, |
|
| 32 | + | ||
| 33 | + | // Other configs... |
|
| 34 | + | ], |
|
| 35 | + | languageOptions: { |
|
| 36 | + | parserOptions: { |
|
| 37 | + | project: ['./tsconfig.node.json', './tsconfig.app.json'], |
|
| 38 | + | tsconfigRootDir: import.meta.dirname, |
|
| 39 | + | }, |
|
| 40 | + | // other options... |
|
| 41 | + | }, |
|
| 42 | + | }, |
|
| 43 | + | ]) |
|
| 44 | + | ``` |
|
| 45 | + | ||
| 46 | + | You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: |
|
| 47 | + | ||
| 48 | + | ```js |
|
| 49 | + | // eslint.config.js |
|
| 50 | + | import reactX from 'eslint-plugin-react-x' |
|
| 51 | + | import reactDom from 'eslint-plugin-react-dom' |
|
| 52 | + | ||
| 53 | + | export default defineConfig([ |
|
| 54 | + | globalIgnores(['dist']), |
|
| 55 | + | { |
|
| 56 | + | files: ['**/*.{ts,tsx}'], |
|
| 57 | + | extends: [ |
|
| 58 | + | // Other configs... |
|
| 59 | + | // Enable lint rules for React |
|
| 60 | + | reactX.configs['recommended-typescript'], |
|
| 61 | + | // Enable lint rules for React DOM |
|
| 62 | + | reactDom.configs.recommended, |
|
| 63 | + | ], |
|
| 64 | + | languageOptions: { |
|
| 65 | + | parserOptions: { |
|
| 66 | + | project: ['./tsconfig.node.json', './tsconfig.app.json'], |
|
| 67 | + | tsconfigRootDir: import.meta.dirname, |
|
| 68 | + | }, |
|
| 69 | + | // other options... |
|
| 70 | + | }, |
|
| 71 | + | }, |
|
| 72 | + | ]) |
|
| 73 | + | ``` |
| 1 | + | { |
|
| 2 | + | "lockfileVersion": 1, |
|
| 3 | + | "configVersion": 1, |
|
| 4 | + | "workspaces": { |
|
| 5 | + | "": { |
|
| 6 | + | "name": "scope", |
|
| 7 | + | "dependencies": { |
|
| 8 | + | "react": "^19.2.4", |
|
| 9 | + | "react-dom": "^19.2.4", |
|
| 10 | + | }, |
|
| 11 | + | "devDependencies": { |
|
| 12 | + | "@eslint/js": "^9.39.4", |
|
| 13 | + | "@types/node": "^24.12.0", |
|
| 14 | + | "@types/react": "^19.2.14", |
|
| 15 | + | "@types/react-dom": "^19.2.3", |
|
| 16 | + | "@vitejs/plugin-react": "^6.0.1", |
|
| 17 | + | "eslint": "^9.39.4", |
|
| 18 | + | "eslint-plugin-react-hooks": "^7.0.1", |
|
| 19 | + | "eslint-plugin-react-refresh": "^0.5.2", |
|
| 20 | + | "globals": "^17.4.0", |
|
| 21 | + | "typescript": "~5.9.3", |
|
| 22 | + | "typescript-eslint": "^8.57.0", |
|
| 23 | + | "vite": "^8.0.1", |
|
| 24 | + | }, |
|
| 25 | + | }, |
|
| 26 | + | }, |
|
| 27 | + | "packages": { |
|
| 28 | + | "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], |
|
| 29 | + | ||
| 30 | + | "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], |
|
| 31 | + | ||
| 32 | + | "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@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" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], |
|
| 33 | + | ||
| 34 | + | "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], |
|
| 35 | + | ||
| 36 | + | "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], |
|
| 37 | + | ||
| 38 | + | "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], |
|
| 39 | + | ||
| 40 | + | "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], |
|
| 41 | + | ||
| 42 | + | "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], |
|
| 43 | + | ||
| 44 | + | "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], |
|
| 45 | + | ||
| 46 | + | "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], |
|
| 47 | + | ||
| 48 | + | "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], |
|
| 49 | + | ||
| 50 | + | "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], |
|
| 51 | + | ||
| 52 | + | "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], |
|
| 53 | + | ||
| 54 | + | "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], |
|
| 55 | + | ||
| 56 | + | "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], |
|
| 57 | + | ||
| 58 | + | "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], |
|
| 59 | + | ||
| 60 | + | "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], |
|
| 61 | + | ||
| 62 | + | "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], |
|
| 63 | + | ||
| 64 | + | "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], |
|
| 65 | + | ||
| 66 | + | "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], |
|
| 67 | + | ||
| 68 | + | "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], |
|
| 69 | + | ||
| 70 | + | "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], |
|
| 71 | + | ||
| 72 | + | "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], |
|
| 73 | + | ||
| 74 | + | "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], |
|
| 75 | + | ||
| 76 | + | "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "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.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], |
|
| 77 | + | ||
| 78 | + | "@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="], |
|
| 79 | + | ||
| 80 | + | "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], |
|
| 81 | + | ||
| 82 | + | "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], |
|
| 83 | + | ||
| 84 | + | "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], |
|
| 85 | + | ||
| 86 | + | "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], |
|
| 87 | + | ||
| 88 | + | "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], |
|
| 89 | + | ||
| 90 | + | "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], |
|
| 91 | + | ||
| 92 | + | "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], |
|
| 93 | + | ||
| 94 | + | "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], |
|
| 95 | + | ||
| 96 | + | "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], |
|
| 97 | + | ||
| 98 | + | "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], |
|
| 99 | + | ||
| 100 | + | "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], |
|
| 101 | + | ||
| 102 | + | "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], |
|
| 103 | + | ||
| 104 | + | "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], |
|
| 105 | + | ||
| 106 | + | "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.12", "", { "os": "android", "cpu": "arm64" }, "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA=="], |
|
| 107 | + | ||
| 108 | + | "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg=="], |
|
| 109 | + | ||
| 110 | + | "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw=="], |
|
| 111 | + | ||
| 112 | + | "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q=="], |
|
| 113 | + | ||
| 114 | + | "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm" }, "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q=="], |
|
| 115 | + | ||
| 116 | + | "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg=="], |
|
| 117 | + | ||
| 118 | + | "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw=="], |
|
| 119 | + | ||
| 120 | + | "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g=="], |
|
| 121 | + | ||
| 122 | + | "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og=="], |
|
| 123 | + | ||
| 124 | + | "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg=="], |
|
| 125 | + | ||
| 126 | + | "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig=="], |
|
| 127 | + | ||
| 128 | + | "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.12", "", { "os": "none", "cpu": "arm64" }, "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA=="], |
|
| 129 | + | ||
| 130 | + | "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.12", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg=="], |
|
| 131 | + | ||
| 132 | + | "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q=="], |
|
| 133 | + | ||
| 134 | + | "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "x64" }, "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw=="], |
|
| 135 | + | ||
| 136 | + | "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], |
|
| 137 | + | ||
| 138 | + | "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], |
|
| 139 | + | ||
| 140 | + | "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], |
|
| 141 | + | ||
| 142 | + | "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], |
|
| 143 | + | ||
| 144 | + | "@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], |
|
| 145 | + | ||
| 146 | + | "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], |
|
| 147 | + | ||
| 148 | + | "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], |
|
| 149 | + | ||
| 150 | + | "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="], |
|
| 151 | + | ||
| 152 | + | "@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="], |
|
| 153 | + | ||
| 154 | + | "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="], |
|
| 155 | + | ||
| 156 | + | "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], |
|
| 157 | + | ||
| 158 | + | "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="], |
|
| 159 | + | ||
| 160 | + | "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="], |
|
| 161 | + | ||
| 162 | + | "@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], |
|
| 163 | + | ||
| 164 | + | "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="], |
|
| 165 | + | ||
| 166 | + | "@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="], |
|
| 167 | + | ||
| 168 | + | "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], |
|
| 169 | + | ||
| 170 | + | "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], |
|
| 171 | + | ||
| 172 | + | "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], |
|
| 173 | + | ||
| 174 | + | "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], |
|
| 175 | + | ||
| 176 | + | "ajv": ["ajv@6.14.0", "", { "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" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], |
|
| 177 | + | ||
| 178 | + | "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], |
|
| 179 | + | ||
| 180 | + | "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], |
|
| 181 | + | ||
| 182 | + | "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], |
|
| 183 | + | ||
| 184 | + | "baseline-browser-mapping": ["baseline-browser-mapping@2.10.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA=="], |
|
| 185 | + | ||
| 186 | + | "brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], |
|
| 187 | + | ||
| 188 | + | "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], |
|
| 189 | + | ||
| 190 | + | "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], |
|
| 191 | + | ||
| 192 | + | "caniuse-lite": ["caniuse-lite@1.0.30001785", "", {}, "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ=="], |
|
| 193 | + | ||
| 194 | + | "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], |
|
| 195 | + | ||
| 196 | + | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], |
|
| 197 | + | ||
| 198 | + | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], |
|
| 199 | + | ||
| 200 | + | "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], |
|
| 201 | + | ||
| 202 | + | "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], |
|
| 203 | + | ||
| 204 | + | "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], |
|
| 205 | + | ||
| 206 | + | "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], |
|
| 207 | + | ||
| 208 | + | "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], |
|
| 209 | + | ||
| 210 | + | "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], |
|
| 211 | + | ||
| 212 | + | "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], |
|
| 213 | + | ||
| 214 | + | "electron-to-chromium": ["electron-to-chromium@1.5.331", "", {}, "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q=="], |
|
| 215 | + | ||
| 216 | + | "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], |
|
| 217 | + | ||
| 218 | + | "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], |
|
| 219 | + | ||
| 220 | + | "eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "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.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], |
|
| 221 | + | ||
| 222 | + | "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], |
|
| 223 | + | ||
| 224 | + | "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.5.2", "", { "peerDependencies": { "eslint": "^9 || ^10" } }, "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA=="], |
|
| 225 | + | ||
| 226 | + | "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], |
|
| 227 | + | ||
| 228 | + | "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], |
|
| 229 | + | ||
| 230 | + | "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], |
|
| 231 | + | ||
| 232 | + | "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], |
|
| 233 | + | ||
| 234 | + | "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], |
|
| 235 | + | ||
| 236 | + | "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], |
|
| 237 | + | ||
| 238 | + | "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], |
|
| 239 | + | ||
| 240 | + | "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], |
|
| 241 | + | ||
| 242 | + | "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], |
|
| 243 | + | ||
| 244 | + | "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], |
|
| 245 | + | ||
| 246 | + | "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], |
|
| 247 | + | ||
| 248 | + | "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], |
|
| 249 | + | ||
| 250 | + | "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], |
|
| 251 | + | ||
| 252 | + | "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], |
|
| 253 | + | ||
| 254 | + | "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], |
|
| 255 | + | ||
| 256 | + | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], |
|
| 257 | + | ||
| 258 | + | "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], |
|
| 259 | + | ||
| 260 | + | "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], |
|
| 261 | + | ||
| 262 | + | "globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], |
|
| 263 | + | ||
| 264 | + | "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], |
|
| 265 | + | ||
| 266 | + | "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], |
|
| 267 | + | ||
| 268 | + | "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], |
|
| 269 | + | ||
| 270 | + | "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], |
|
| 271 | + | ||
| 272 | + | "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], |
|
| 273 | + | ||
| 274 | + | "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], |
|
| 275 | + | ||
| 276 | + | "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], |
|
| 277 | + | ||
| 278 | + | "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], |
|
| 279 | + | ||
| 280 | + | "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], |
|
| 281 | + | ||
| 282 | + | "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], |
|
| 283 | + | ||
| 284 | + | "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], |
|
| 285 | + | ||
| 286 | + | "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], |
|
| 287 | + | ||
| 288 | + | "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], |
|
| 289 | + | ||
| 290 | + | "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], |
|
| 291 | + | ||
| 292 | + | "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], |
|
| 293 | + | ||
| 294 | + | "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], |
|
| 295 | + | ||
| 296 | + | "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], |
|
| 297 | + | ||
| 298 | + | "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], |
|
| 299 | + | ||
| 300 | + | "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], |
|
| 301 | + | ||
| 302 | + | "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], |
|
| 303 | + | ||
| 304 | + | "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], |
|
| 305 | + | ||
| 306 | + | "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], |
|
| 307 | + | ||
| 308 | + | "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], |
|
| 309 | + | ||
| 310 | + | "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], |
|
| 311 | + | ||
| 312 | + | "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], |
|
| 313 | + | ||
| 314 | + | "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], |
|
| 315 | + | ||
| 316 | + | "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], |
|
| 317 | + | ||
| 318 | + | "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], |
|
| 319 | + | ||
| 320 | + | "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], |
|
| 321 | + | ||
| 322 | + | "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], |
|
| 323 | + | ||
| 324 | + | "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], |
|
| 325 | + | ||
| 326 | + | "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], |
|
| 327 | + | ||
| 328 | + | "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], |
|
| 329 | + | ||
| 330 | + | "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], |
|
| 331 | + | ||
| 332 | + | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], |
|
| 333 | + | ||
| 334 | + | "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], |
|
| 335 | + | ||
| 336 | + | "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], |
|
| 337 | + | ||
| 338 | + | "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], |
|
| 339 | + | ||
| 340 | + | "optionator": ["optionator@0.9.4", "", { "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" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], |
|
| 341 | + | ||
| 342 | + | "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], |
|
| 343 | + | ||
| 344 | + | "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], |
|
| 345 | + | ||
| 346 | + | "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], |
|
| 347 | + | ||
| 348 | + | "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], |
|
| 349 | + | ||
| 350 | + | "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], |
|
| 351 | + | ||
| 352 | + | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], |
|
| 353 | + | ||
| 354 | + | "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], |
|
| 355 | + | ||
| 356 | + | "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], |
|
| 357 | + | ||
| 358 | + | "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], |
|
| 359 | + | ||
| 360 | + | "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], |
|
| 361 | + | ||
| 362 | + | "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], |
|
| 363 | + | ||
| 364 | + | "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], |
|
| 365 | + | ||
| 366 | + | "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], |
|
| 367 | + | ||
| 368 | + | "rolldown": ["rolldown@1.0.0-rc.12", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.12" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-x64": "1.0.0-rc.12", "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A=="], |
|
| 369 | + | ||
| 370 | + | "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], |
|
| 371 | + | ||
| 372 | + | "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], |
|
| 373 | + | ||
| 374 | + | "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], |
|
| 375 | + | ||
| 376 | + | "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], |
|
| 377 | + | ||
| 378 | + | "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], |
|
| 379 | + | ||
| 380 | + | "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], |
|
| 381 | + | ||
| 382 | + | "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], |
|
| 383 | + | ||
| 384 | + | "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], |
|
| 385 | + | ||
| 386 | + | "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], |
|
| 387 | + | ||
| 388 | + | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], |
|
| 389 | + | ||
| 390 | + | "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], |
|
| 391 | + | ||
| 392 | + | "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], |
|
| 393 | + | ||
| 394 | + | "typescript-eslint": ["typescript-eslint@8.58.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA=="], |
|
| 395 | + | ||
| 396 | + | "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], |
|
| 397 | + | ||
| 398 | + | "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], |
|
| 399 | + | ||
| 400 | + | "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], |
|
| 401 | + | ||
| 402 | + | "vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.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" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="], |
|
| 403 | + | ||
| 404 | + | "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], |
|
| 405 | + | ||
| 406 | + | "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], |
|
| 407 | + | ||
| 408 | + | "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], |
|
| 409 | + | ||
| 410 | + | "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], |
|
| 411 | + | ||
| 412 | + | "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], |
|
| 413 | + | ||
| 414 | + | "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], |
|
| 415 | + | ||
| 416 | + | "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], |
|
| 417 | + | ||
| 418 | + | "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], |
|
| 419 | + | ||
| 420 | + | "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], |
|
| 421 | + | ||
| 422 | + | "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], |
|
| 423 | + | ||
| 424 | + | "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], |
|
| 425 | + | ||
| 426 | + | "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], |
|
| 427 | + | ||
| 428 | + | "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.12", "", {}, "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw=="], |
|
| 429 | + | ||
| 430 | + | "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], |
|
| 431 | + | ||
| 432 | + | "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], |
|
| 433 | + | } |
|
| 434 | + | } |
| 1 | + | import js from '@eslint/js' |
|
| 2 | + | import globals from 'globals' |
|
| 3 | + | import reactHooks from 'eslint-plugin-react-hooks' |
|
| 4 | + | import reactRefresh from 'eslint-plugin-react-refresh' |
|
| 5 | + | import tseslint from 'typescript-eslint' |
|
| 6 | + | import { defineConfig, globalIgnores } from 'eslint/config' |
|
| 7 | + | ||
| 8 | + | export default defineConfig([ |
|
| 9 | + | globalIgnores(['dist']), |
|
| 10 | + | { |
|
| 11 | + | files: ['**/*.{ts,tsx}'], |
|
| 12 | + | extends: [ |
|
| 13 | + | js.configs.recommended, |
|
| 14 | + | tseslint.configs.recommended, |
|
| 15 | + | reactHooks.configs.flat.recommended, |
|
| 16 | + | reactRefresh.configs.vite, |
|
| 17 | + | ], |
|
| 18 | + | languageOptions: { |
|
| 19 | + | ecmaVersion: 2020, |
|
| 20 | + | globals: globals.browser, |
|
| 21 | + | }, |
|
| 22 | + | }, |
|
| 23 | + | ]) |
| 1 | + | <!doctype html> |
|
| 2 | + | <html lang="en"> |
|
| 3 | + | <head> |
|
| 4 | + | <meta charset="UTF-8" /> |
|
| 5 | + | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|
| 6 | + | <meta name="theme-color" content="#121113" /> |
|
| 7 | + | <title>Scope</title> |
|
| 8 | + | <meta name="description" content="Minimal video scope" /> |
|
| 9 | + | ||
| 10 | + | <!-- Open Graph --> |
|
| 11 | + | <meta property="og:type" content="website" /> |
|
| 12 | + | <meta property="og:url" content="https://scope.darkmatter.build" /> |
|
| 13 | + | <meta property="og:title" content="Scope" /> |
|
| 14 | + | <meta property="og:description" content="Minimal video scope" /> |
|
| 15 | + | <meta property="og:image" content="https://scope.darkmatter.build/og.png" /> |
|
| 16 | + | <meta property="og:site_name" content="Scope" /> |
|
| 17 | + | ||
| 18 | + | <!-- Twitter --> |
|
| 19 | + | <meta name="twitter:card" content="summary_large_image" /> |
|
| 20 | + | <meta name="twitter:url" content="https://scope.darkmatter.build" /> |
|
| 21 | + | <meta name="twitter:title" content="Scope" /> |
|
| 22 | + | <meta name="twitter:description" content="Minimal video scope" /> |
|
| 23 | + | <meta name="twitter:image" content="https://scope.darkmatter.build/og.png" /> |
|
| 24 | + | ||
| 25 | + | <!-- Favicons --> |
|
| 26 | + | <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> |
|
| 27 | + | <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> |
|
| 28 | + | <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> |
|
| 29 | + | <link rel="manifest" href="/site.webmanifest"> |
|
| 30 | + | </head> |
|
| 31 | + | <body> |
|
| 32 | + | <div id="root"></div> |
|
| 33 | + | <script type="module" src="/src/main.tsx"></script> |
|
| 34 | + | </body> |
|
| 35 | + | </html> |
| 1 | + | { |
|
| 2 | + | "name": "scope", |
|
| 3 | + | "private": true, |
|
| 4 | + | "version": "0.0.0", |
|
| 5 | + | "type": "module", |
|
| 6 | + | "scripts": { |
|
| 7 | + | "dev": "vite", |
|
| 8 | + | "build": "tsc -b && vite build", |
|
| 9 | + | "lint": "eslint .", |
|
| 10 | + | "preview": "vite preview" |
|
| 11 | + | }, |
|
| 12 | + | "dependencies": { |
|
| 13 | + | "react": "^19.2.4", |
|
| 14 | + | "react-dom": "^19.2.4" |
|
| 15 | + | }, |
|
| 16 | + | "devDependencies": { |
|
| 17 | + | "@eslint/js": "^9.39.4", |
|
| 18 | + | "@types/node": "^24.12.0", |
|
| 19 | + | "@types/react": "^19.2.14", |
|
| 20 | + | "@types/react-dom": "^19.2.3", |
|
| 21 | + | "@vitejs/plugin-react": "^6.0.1", |
|
| 22 | + | "eslint": "^9.39.4", |
|
| 23 | + | "eslint-plugin-react-hooks": "^7.0.1", |
|
| 24 | + | "eslint-plugin-react-refresh": "^0.5.2", |
|
| 25 | + | "globals": "^17.4.0", |
|
| 26 | + | "typescript": "~5.9.3", |
|
| 27 | + | "typescript-eslint": "^8.57.0", |
|
| 28 | + | "vite": "^8.0.1" |
|
| 29 | + | } |
|
| 30 | + | } |
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
Binary file — no preview.
| 1 | + | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} |
| 1 | + | /* Layout */ |
|
| 2 | + | ||
| 3 | + | .app { |
|
| 4 | + | width: 100vw; |
|
| 5 | + | height: 100vh; |
|
| 6 | + | position: relative; |
|
| 7 | + | overflow: hidden; |
|
| 8 | + | background: #121113; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /* Camera */ |
|
| 12 | + | ||
| 13 | + | .camera-view { |
|
| 14 | + | width: 100%; |
|
| 15 | + | height: 100%; |
|
| 16 | + | display: flex; |
|
| 17 | + | align-items: center; |
|
| 18 | + | justify-content: center; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | .camera-canvas { |
|
| 22 | + | width: 100%; |
|
| 23 | + | height: 100%; |
|
| 24 | + | object-fit: contain; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | .camera-error { |
|
| 28 | + | width: 100%; |
|
| 29 | + | height: 100%; |
|
| 30 | + | display: flex; |
|
| 31 | + | align-items: center; |
|
| 32 | + | justify-content: center; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | .camera-error p { |
|
| 36 | + | color: #ffffff; |
|
| 37 | + | border-left: 2px solid #ffffff; |
|
| 38 | + | padding-left: 0.5rem; |
|
| 39 | + | font-size: 13px; |
|
| 40 | + | opacity: 0.8; |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | /* Control Panel */ |
|
| 44 | + | ||
| 45 | + | .control-panel { |
|
| 46 | + | position: fixed; |
|
| 47 | + | top: 0; |
|
| 48 | + | right: 0; |
|
| 49 | + | height: 100vh; |
|
| 50 | + | width: 320px; |
|
| 51 | + | transform: translateX(100%); |
|
| 52 | + | transition: transform 0.3s ease; |
|
| 53 | + | z-index: 10; |
|
| 54 | + | display: flex; |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | .control-panel.open { |
|
| 58 | + | transform: translateX(0); |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | .panel-toggle { |
|
| 62 | + | position: absolute; |
|
| 63 | + | left: -32px; |
|
| 64 | + | top: 50%; |
|
| 65 | + | transform: translateY(-50%); |
|
| 66 | + | width: 32px; |
|
| 67 | + | height: 64px; |
|
| 68 | + | background: #121113; |
|
| 69 | + | color: #ffffff; |
|
| 70 | + | border: 1px solid #333; |
|
| 71 | + | border-right: none; |
|
| 72 | + | cursor: pointer; |
|
| 73 | + | font-size: 20px; |
|
| 74 | + | display: flex; |
|
| 75 | + | align-items: center; |
|
| 76 | + | justify-content: center; |
|
| 77 | + | border-radius: 0; |
|
| 78 | + | padding: 0; |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | .panel-toggle:hover { |
|
| 82 | + | opacity: 0.7; |
|
| 83 | + | } |
|
| 84 | + | ||
| 85 | + | .panel-content { |
|
| 86 | + | flex: 1; |
|
| 87 | + | background: #121113; |
|
| 88 | + | border-left: 1px solid #333; |
|
| 89 | + | overflow-y: auto; |
|
| 90 | + | padding: 1.5rem 1rem; |
|
| 91 | + | display: flex; |
|
| 92 | + | flex-direction: column; |
|
| 93 | + | gap: 1.5rem; |
|
| 94 | + | scrollbar-width: none; |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | .panel-content::-webkit-scrollbar { |
|
| 98 | + | display: none; |
|
| 99 | + | } |
|
| 100 | + | ||
| 101 | + | .panel-header { |
|
| 102 | + | display: flex; |
|
| 103 | + | justify-content: space-between; |
|
| 104 | + | align-items: center; |
|
| 105 | + | padding-bottom: 0.75rem; |
|
| 106 | + | border-bottom: 1px solid #333; |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | .panel-title { |
|
| 110 | + | font-size: 16px; |
|
| 111 | + | font-weight: 700; |
|
| 112 | + | text-transform: uppercase; |
|
| 113 | + | } |
|
| 114 | + | ||
| 115 | + | .reset-btn { |
|
| 116 | + | background: #121113; |
|
| 117 | + | color: #ffffff; |
|
| 118 | + | border: 1px solid white; |
|
| 119 | + | padding: 0.25rem 0.5rem; |
|
| 120 | + | font-size: 12px; |
|
| 121 | + | cursor: pointer; |
|
| 122 | + | border-radius: 0; |
|
| 123 | + | } |
|
| 124 | + | ||
| 125 | + | .reset-btn:hover { |
|
| 126 | + | opacity: 0.7; |
|
| 127 | + | } |
|
| 128 | + | ||
| 129 | + | /* Randomize */ |
|
| 130 | + | ||
| 131 | + | .randomize-section { |
|
| 132 | + | display: flex; |
|
| 133 | + | gap: 0.5rem; |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | .randomize-btn { |
|
| 137 | + | flex: 1; |
|
| 138 | + | background: #121113; |
|
| 139 | + | color: #ffffff; |
|
| 140 | + | border: 1px solid #555; |
|
| 141 | + | padding: 0.4rem 0; |
|
| 142 | + | font-size: 12px; |
|
| 143 | + | font-weight: 700; |
|
| 144 | + | cursor: pointer; |
|
| 145 | + | border-radius: 0; |
|
| 146 | + | } |
|
| 147 | + | ||
| 148 | + | .randomize-btn:hover { |
|
| 149 | + | border-color: #ffffff; |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | .fluid-toggle { |
|
| 153 | + | background: #121113; |
|
| 154 | + | color: #ffffff; |
|
| 155 | + | border: 1px solid #555; |
|
| 156 | + | padding: 0.4rem 0.75rem; |
|
| 157 | + | font-size: 12px; |
|
| 158 | + | font-weight: 700; |
|
| 159 | + | cursor: pointer; |
|
| 160 | + | border-radius: 0; |
|
| 161 | + | opacity: 0.5; |
|
| 162 | + | transition: opacity 0.15s, border-color 0.15s; |
|
| 163 | + | } |
|
| 164 | + | ||
| 165 | + | .fluid-toggle:hover { |
|
| 166 | + | opacity: 0.7; |
|
| 167 | + | } |
|
| 168 | + | ||
| 169 | + | .fluid-toggle.active { |
|
| 170 | + | opacity: 1; |
|
| 171 | + | border-color: #ffffff; |
|
| 172 | + | } |
|
| 173 | + | ||
| 174 | + | .panel-header-actions { |
|
| 175 | + | display: flex; |
|
| 176 | + | gap: 0.5rem; |
|
| 177 | + | align-items: center; |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | /* Sections */ |
|
| 181 | + | ||
| 182 | + | .panel-section { |
|
| 183 | + | display: flex; |
|
| 184 | + | flex-direction: column; |
|
| 185 | + | gap: 0.75rem; |
|
| 186 | + | } |
|
| 187 | + | ||
| 188 | + | .section-label { |
|
| 189 | + | font-size: 12px; |
|
| 190 | + | opacity: 0.5; |
|
| 191 | + | text-transform: uppercase; |
|
| 192 | + | font-weight: 700; |
|
| 193 | + | } |
|
| 194 | + | ||
| 195 | + | /* Slider */ |
|
| 196 | + | ||
| 197 | + | .slider-group { |
|
| 198 | + | display: flex; |
|
| 199 | + | flex-direction: column; |
|
| 200 | + | gap: 0.5rem; |
|
| 201 | + | } |
|
| 202 | + | ||
| 203 | + | .slider-row { |
|
| 204 | + | display: flex; |
|
| 205 | + | flex-direction: column; |
|
| 206 | + | gap: 0.2rem; |
|
| 207 | + | } |
|
| 208 | + | ||
| 209 | + | .slider-header { |
|
| 210 | + | display: flex; |
|
| 211 | + | justify-content: space-between; |
|
| 212 | + | align-items: center; |
|
| 213 | + | } |
|
| 214 | + | ||
| 215 | + | .slider-header label { |
|
| 216 | + | font-size: 12px; |
|
| 217 | + | opacity: 0.7; |
|
| 218 | + | } |
|
| 219 | + | ||
| 220 | + | .slider-value { |
|
| 221 | + | font-size: 12px; |
|
| 222 | + | opacity: 0.5; |
|
| 223 | + | } |
|
| 224 | + | ||
| 225 | + | /* Range Input Styling */ |
|
| 226 | + | ||
| 227 | + | input[type="range"] { |
|
| 228 | + | -webkit-appearance: none; |
|
| 229 | + | appearance: none; |
|
| 230 | + | width: 100%; |
|
| 231 | + | height: 2px; |
|
| 232 | + | background: #555; |
|
| 233 | + | outline: none; |
|
| 234 | + | border: none; |
|
| 235 | + | border-radius: 0; |
|
| 236 | + | } |
|
| 237 | + | ||
| 238 | + | input[type="range"]::-webkit-slider-thumb { |
|
| 239 | + | -webkit-appearance: none; |
|
| 240 | + | appearance: none; |
|
| 241 | + | width: 12px; |
|
| 242 | + | height: 12px; |
|
| 243 | + | background: #ffffff; |
|
| 244 | + | border: none; |
|
| 245 | + | border-radius: 0; |
|
| 246 | + | cursor: pointer; |
|
| 247 | + | } |
|
| 248 | + | ||
| 249 | + | input[type="range"]::-moz-range-thumb { |
|
| 250 | + | width: 12px; |
|
| 251 | + | height: 12px; |
|
| 252 | + | background: #ffffff; |
|
| 253 | + | border: none; |
|
| 254 | + | border-radius: 0; |
|
| 255 | + | cursor: pointer; |
|
| 256 | + | } |
|
| 257 | + | ||
| 258 | + | input[type="range"]::-webkit-slider-thumb:hover { |
|
| 259 | + | opacity: 0.7; |
|
| 260 | + | } |
|
| 261 | + | ||
| 262 | + | input[type="range"]::-moz-range-thumb:hover { |
|
| 263 | + | opacity: 0.7; |
|
| 264 | + | } |
|
| 265 | + | ||
| 266 | + | /* Curves Editor */ |
|
| 267 | + | ||
| 268 | + | .curves-editor { |
|
| 269 | + | display: flex; |
|
| 270 | + | flex-direction: column; |
|
| 271 | + | gap: 0.5rem; |
|
| 272 | + | } |
|
| 273 | + | ||
| 274 | + | .curves-tabs { |
|
| 275 | + | display: flex; |
|
| 276 | + | gap: 0; |
|
| 277 | + | } |
|
| 278 | + | ||
| 279 | + | .curves-tab { |
|
| 280 | + | flex: 1; |
|
| 281 | + | background: #121113; |
|
| 282 | + | color: #ffffff; |
|
| 283 | + | border: 1px solid #333; |
|
| 284 | + | padding: 0.3rem 0; |
|
| 285 | + | font-size: 12px; |
|
| 286 | + | cursor: pointer; |
|
| 287 | + | border-radius: 0; |
|
| 288 | + | opacity: 0.5; |
|
| 289 | + | } |
|
| 290 | + | ||
| 291 | + | .curves-tab + .curves-tab { |
|
| 292 | + | border-left: none; |
|
| 293 | + | } |
|
| 294 | + | ||
| 295 | + | .curves-tab.active { |
|
| 296 | + | opacity: 1; |
|
| 297 | + | border-color: #ffffff; |
|
| 298 | + | } |
|
| 299 | + | ||
| 300 | + | .curves-tab:hover { |
|
| 301 | + | opacity: 0.7; |
|
| 302 | + | } |
|
| 303 | + | ||
| 304 | + | .curves-canvas { |
|
| 305 | + | cursor: crosshair; |
|
| 306 | + | border: 1px solid #333; |
|
| 307 | + | display: block; |
|
| 308 | + | width: 100% !important; |
|
| 309 | + | height: auto !important; |
|
| 310 | + | aspect-ratio: 1; |
|
| 311 | + | } |
| 1 | + | import { useReducer } from "react"; |
|
| 2 | + | import type { BasicFilters, CurveChannel, CurvePoint, FilterState } from "./lib/types"; |
|
| 3 | + | import { DEFAULT_FILTER_STATE } from "./lib/types"; |
|
| 4 | + | import { CameraView } from "./components/CameraView"; |
|
| 5 | + | import { ControlPanel } from "./components/ControlPanel"; |
|
| 6 | + | import "./App.css"; |
|
| 7 | + | ||
| 8 | + | type Action = |
|
| 9 | + | | { type: "SET_BASIC"; key: keyof BasicFilters; value: number } |
|
| 10 | + | | { type: "SET_CURVE"; channel: CurveChannel; points: CurvePoint[] } |
|
| 11 | + | | { type: "SET_ALL"; state: FilterState } |
|
| 12 | + | | { type: "RESET" }; |
|
| 13 | + | ||
| 14 | + | function reducer(state: FilterState, action: Action): FilterState { |
|
| 15 | + | switch (action.type) { |
|
| 16 | + | case "SET_BASIC": |
|
| 17 | + | return { ...state, basic: { ...state.basic, [action.key]: action.value } }; |
|
| 18 | + | case "SET_CURVE": |
|
| 19 | + | return { ...state, curves: { ...state.curves, [action.channel]: action.points } }; |
|
| 20 | + | case "SET_ALL": |
|
| 21 | + | return action.state; |
|
| 22 | + | case "RESET": |
|
| 23 | + | return { ...DEFAULT_FILTER_STATE }; |
|
| 24 | + | default: |
|
| 25 | + | return state; |
|
| 26 | + | } |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | function App() { |
|
| 30 | + | const [filterState, dispatch] = useReducer(reducer, DEFAULT_FILTER_STATE); |
|
| 31 | + | ||
| 32 | + | return ( |
|
| 33 | + | <div className="app"> |
|
| 34 | + | <CameraView filterState={filterState} /> |
|
| 35 | + | <ControlPanel |
|
| 36 | + | filterState={filterState} |
|
| 37 | + | onBasicChange={(key, value) => dispatch({ type: "SET_BASIC", key, value })} |
|
| 38 | + | onCurveChange={(channel, points) => dispatch({ type: "SET_CURVE", channel, points })} |
|
| 39 | + | onSetAll={(state: FilterState) => dispatch({ type: "SET_ALL", state })} |
|
| 40 | + | onReset={() => dispatch({ type: "RESET" })} |
|
| 41 | + | /> |
|
| 42 | + | </div> |
|
| 43 | + | ); |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | export default App; |
| 1 | + | import { useRef, useMemo, useEffect, useState } from "react"; |
|
| 2 | + | import { useCamera } from "../hooks/useCamera"; |
|
| 3 | + | import { useAnimationLoop } from "../hooks/useAnimationLoop"; |
|
| 4 | + | import { buildCSSFilter, buildLUT } from "../lib/filters"; |
|
| 5 | + | import type { FilterState } from "../lib/types"; |
|
| 6 | + | ||
| 7 | + | interface CameraViewProps { |
|
| 8 | + | filterState: FilterState; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | export function CameraView({ filterState }: CameraViewProps) { |
|
| 12 | + | const { videoRef, error, isReady } = useCamera(); |
|
| 13 | + | const canvasRef = useRef<HTMLCanvasElement>(null); |
|
| 14 | + | const [videoDimensions, setVideoDimensions] = useState({ w: 1280, h: 720 }); |
|
| 15 | + | ||
| 16 | + | useEffect(() => { |
|
| 17 | + | const video = videoRef.current; |
|
| 18 | + | if (!video) return; |
|
| 19 | + | ||
| 20 | + | function onMeta() { |
|
| 21 | + | setVideoDimensions({ w: video!.videoWidth, h: video!.videoHeight }); |
|
| 22 | + | } |
|
| 23 | + | video.addEventListener("loadedmetadata", onMeta); |
|
| 24 | + | return () => video.removeEventListener("loadedmetadata", onMeta); |
|
| 25 | + | }, [videoRef]); |
|
| 26 | + | ||
| 27 | + | const cssFilter = useMemo(() => buildCSSFilter(filterState.basic), [filterState.basic]); |
|
| 28 | + | const lut = useMemo( |
|
| 29 | + | () => buildLUT(filterState.basic, filterState.curves), |
|
| 30 | + | [filterState.basic, filterState.curves], |
|
| 31 | + | ); |
|
| 32 | + | ||
| 33 | + | useAnimationLoop(() => { |
|
| 34 | + | const video = videoRef.current; |
|
| 35 | + | const canvas = canvasRef.current; |
|
| 36 | + | if (!video || !canvas || !isReady || video.readyState < 2) return; |
|
| 37 | + | ||
| 38 | + | const ctx = canvas.getContext("2d", { willReadFrequently: !lut.isIdentity }); |
|
| 39 | + | if (!ctx) return; |
|
| 40 | + | ||
| 41 | + | canvas.width = videoDimensions.w; |
|
| 42 | + | canvas.height = videoDimensions.h; |
|
| 43 | + | ||
| 44 | + | ctx.filter = cssFilter; |
|
| 45 | + | ctx.drawImage(video, 0, 0, videoDimensions.w, videoDimensions.h); |
|
| 46 | + | ||
| 47 | + | if (!lut.isIdentity) { |
|
| 48 | + | ctx.filter = "none"; |
|
| 49 | + | const imageData = ctx.getImageData(0, 0, videoDimensions.w, videoDimensions.h); |
|
| 50 | + | const d = imageData.data; |
|
| 51 | + | const lr = lut.r; |
|
| 52 | + | const lg = lut.g; |
|
| 53 | + | const lb = lut.b; |
|
| 54 | + | for (let i = 0; i < d.length; i += 4) { |
|
| 55 | + | d[i] = lr[d[i]]; |
|
| 56 | + | d[i + 1] = lg[d[i + 1]]; |
|
| 57 | + | d[i + 2] = lb[d[i + 2]]; |
|
| 58 | + | } |
|
| 59 | + | ctx.putImageData(imageData, 0, 0); |
|
| 60 | + | } |
|
| 61 | + | }); |
|
| 62 | + | ||
| 63 | + | if (error) { |
|
| 64 | + | return ( |
|
| 65 | + | <div className="camera-error"> |
|
| 66 | + | <p>{error}</p> |
|
| 67 | + | </div> |
|
| 68 | + | ); |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | return ( |
|
| 72 | + | <div className="camera-view"> |
|
| 73 | + | <video |
|
| 74 | + | ref={videoRef} |
|
| 75 | + | autoPlay |
|
| 76 | + | playsInline |
|
| 77 | + | muted |
|
| 78 | + | style={{ position: "absolute", width: 1, height: 1, opacity: 0, pointerEvents: "none" }} |
|
| 79 | + | /> |
|
| 80 | + | <canvas ref={canvasRef} className="camera-canvas" /> |
|
| 81 | + | </div> |
|
| 82 | + | ); |
|
| 83 | + | } |
| 1 | + | import { useState, useEffect, useRef, useCallback } from "react"; |
|
| 2 | + | import type { BasicFilters, CurveChannel, CurvePoint, FilterState } from "../lib/types"; |
|
| 3 | + | import { DEFAULT_BASIC, DEFAULT_CURVES } from "../lib/types"; |
|
| 4 | + | import { SliderGroup } from "./SliderGroup"; |
|
| 5 | + | import { CurvesEditor } from "./CurvesEditor"; |
|
| 6 | + | ||
| 7 | + | const SLIDER_RANGES: Record<keyof BasicFilters, [number, number]> = { |
|
| 8 | + | brightness: [0, 200], |
|
| 9 | + | contrast: [0, 200], |
|
| 10 | + | exposure: [-100, 100], |
|
| 11 | + | saturation: [0, 200], |
|
| 12 | + | temperature: [-100, 100], |
|
| 13 | + | tint: [-100, 100], |
|
| 14 | + | highlights: [-100, 100], |
|
| 15 | + | shadows: [-100, 100], |
|
| 16 | + | }; |
|
| 17 | + | ||
| 18 | + | function randBetween(min: number, max: number) { |
|
| 19 | + | return Math.round(min + Math.random() * (max - min)); |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | function randomCurve(): CurvePoint[] { |
|
| 23 | + | const numMid = 1 + Math.floor(Math.random() * 3); |
|
| 24 | + | const points: CurvePoint[] = [{ x: 0, y: randBetween(0, 60) }]; |
|
| 25 | + | for (let i = 0; i < numMid; i++) { |
|
| 26 | + | const x = Math.round(((i + 1) / (numMid + 1)) * 255); |
|
| 27 | + | points.push({ x, y: randBetween(30, 225) }); |
|
| 28 | + | } |
|
| 29 | + | points.push({ x: 255, y: randBetween(195, 255) }); |
|
| 30 | + | return points; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | function generateRandomState(): FilterState { |
|
| 34 | + | const basic = {} as BasicFilters; |
|
| 35 | + | for (const key of Object.keys(SLIDER_RANGES) as (keyof BasicFilters)[]) { |
|
| 36 | + | const [min, max] = SLIDER_RANGES[key]; |
|
| 37 | + | basic[key] = randBetween(min, max); |
|
| 38 | + | } |
|
| 39 | + | return { |
|
| 40 | + | basic, |
|
| 41 | + | curves: { |
|
| 42 | + | rgb: randomCurve(), |
|
| 43 | + | r: randomCurve(), |
|
| 44 | + | g: randomCurve(), |
|
| 45 | + | b: randomCurve(), |
|
| 46 | + | }, |
|
| 47 | + | }; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | function lerpNum(a: number, b: number, t: number) { |
|
| 51 | + | return a + (b - a) * t; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | function lerpBasic(a: BasicFilters, b: BasicFilters, t: number): BasicFilters { |
|
| 55 | + | const result = {} as BasicFilters; |
|
| 56 | + | for (const key of Object.keys(a) as (keyof BasicFilters)[]) { |
|
| 57 | + | result[key] = Math.round(lerpNum(a[key], b[key], t)); |
|
| 58 | + | } |
|
| 59 | + | return result; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | function lerpCurve(a: CurvePoint[], b: CurvePoint[], t: number): CurvePoint[] { |
|
| 63 | + | // Resample both curves to a common set of x points for smooth interpolation |
|
| 64 | + | const xs = new Set<number>(); |
|
| 65 | + | a.forEach((p) => xs.add(p.x)); |
|
| 66 | + | b.forEach((p) => xs.add(p.x)); |
|
| 67 | + | const sortedX = Array.from(xs).sort((a, b) => a - b); |
|
| 68 | + | ||
| 69 | + | return sortedX.map((x) => ({ |
|
| 70 | + | x, |
|
| 71 | + | y: Math.round(lerpNum(sampleCurve(a, x), sampleCurve(b, x), t)), |
|
| 72 | + | })); |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | function sampleCurve(points: CurvePoint[], x: number): number { |
|
| 76 | + | if (x <= points[0].x) return points[0].y; |
|
| 77 | + | if (x >= points[points.length - 1].x) return points[points.length - 1].y; |
|
| 78 | + | for (let i = 0; i < points.length - 1; i++) { |
|
| 79 | + | if (x >= points[i].x && x <= points[i + 1].x) { |
|
| 80 | + | const t = (x - points[i].x) / (points[i + 1].x - points[i].x); |
|
| 81 | + | return lerpNum(points[i].y, points[i + 1].y, t); |
|
| 82 | + | } |
|
| 83 | + | } |
|
| 84 | + | return points[points.length - 1].y; |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | function lerpState(a: FilterState, b: FilterState, t: number): FilterState { |
|
| 88 | + | return { |
|
| 89 | + | basic: lerpBasic(a.basic, b.basic, t), |
|
| 90 | + | curves: { |
|
| 91 | + | rgb: lerpCurve(a.curves.rgb, b.curves.rgb, t), |
|
| 92 | + | r: lerpCurve(a.curves.r, b.curves.r, t), |
|
| 93 | + | g: lerpCurve(a.curves.g, b.curves.g, t), |
|
| 94 | + | b: lerpCurve(a.curves.b, b.curves.b, t), |
|
| 95 | + | }, |
|
| 96 | + | }; |
|
| 97 | + | } |
|
| 98 | + | ||
| 99 | + | interface ControlPanelProps { |
|
| 100 | + | filterState: FilterState; |
|
| 101 | + | onBasicChange: (key: keyof BasicFilters, value: number) => void; |
|
| 102 | + | onCurveChange: (channel: CurveChannel, points: CurvePoint[]) => void; |
|
| 103 | + | onSetAll: (state: FilterState) => void; |
|
| 104 | + | onReset: () => void; |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | export function ControlPanel({ filterState, onBasicChange, onCurveChange, onSetAll, onReset }: ControlPanelProps) { |
|
| 108 | + | const [open, setOpen] = useState(false); |
|
| 109 | + | const [fluid, setFluid] = useState(false); |
|
| 110 | + | ||
| 111 | + | const fluidRef = useRef(false); |
|
| 112 | + | const rafRef = useRef<number>(0); |
|
| 113 | + | const fromRef = useRef<FilterState>(filterState); |
|
| 114 | + | const toRef = useRef<FilterState>(generateRandomState()); |
|
| 115 | + | const progressRef = useRef(0); |
|
| 116 | + | const onSetAllRef = useRef(onSetAll); |
|
| 117 | + | onSetAllRef.current = onSetAll; |
|
| 118 | + | ||
| 119 | + | const startFluid = useCallback(() => { |
|
| 120 | + | fluidRef.current = true; |
|
| 121 | + | fromRef.current = filterState; |
|
| 122 | + | toRef.current = generateRandomState(); |
|
| 123 | + | progressRef.current = 0; |
|
| 124 | + | ||
| 125 | + | const LERP_SPEED = 0.008; |
|
| 126 | + | ||
| 127 | + | const tick = () => { |
|
| 128 | + | if (!fluidRef.current) return; |
|
| 129 | + | progressRef.current += LERP_SPEED; |
|
| 130 | + | if (progressRef.current >= 1) { |
|
| 131 | + | fromRef.current = toRef.current; |
|
| 132 | + | toRef.current = generateRandomState(); |
|
| 133 | + | progressRef.current = 0; |
|
| 134 | + | } |
|
| 135 | + | // Smooth easing |
|
| 136 | + | const t = progressRef.current * progressRef.current * (3 - 2 * progressRef.current); |
|
| 137 | + | const interpolated = lerpState(fromRef.current, toRef.current, t); |
|
| 138 | + | onSetAllRef.current(interpolated); |
|
| 139 | + | rafRef.current = requestAnimationFrame(tick); |
|
| 140 | + | }; |
|
| 141 | + | rafRef.current = requestAnimationFrame(tick); |
|
| 142 | + | }, [filterState]); |
|
| 143 | + | ||
| 144 | + | const stopFluid = useCallback(() => { |
|
| 145 | + | fluidRef.current = false; |
|
| 146 | + | if (rafRef.current) cancelAnimationFrame(rafRef.current); |
|
| 147 | + | }, []); |
|
| 148 | + | ||
| 149 | + | useEffect(() => { |
|
| 150 | + | return () => { |
|
| 151 | + | if (rafRef.current) cancelAnimationFrame(rafRef.current); |
|
| 152 | + | }; |
|
| 153 | + | }, []); |
|
| 154 | + | ||
| 155 | + | const handleFluidToggle = () => { |
|
| 156 | + | if (fluid) { |
|
| 157 | + | stopFluid(); |
|
| 158 | + | setFluid(false); |
|
| 159 | + | } else { |
|
| 160 | + | setFluid(true); |
|
| 161 | + | startFluid(); |
|
| 162 | + | } |
|
| 163 | + | }; |
|
| 164 | + | ||
| 165 | + | const handleRandomize = () => { |
|
| 166 | + | if (fluid) { |
|
| 167 | + | stopFluid(); |
|
| 168 | + | setFluid(false); |
|
| 169 | + | } |
|
| 170 | + | onSetAll(generateRandomState()); |
|
| 171 | + | }; |
|
| 172 | + | ||
| 173 | + | const hasChanges = |
|
| 174 | + | JSON.stringify(filterState.basic) !== JSON.stringify(DEFAULT_BASIC) || |
|
| 175 | + | JSON.stringify(filterState.curves) !== JSON.stringify(DEFAULT_CURVES); |
|
| 176 | + | ||
| 177 | + | return ( |
|
| 178 | + | <div className={`control-panel ${open ? "open" : ""}`}> |
|
| 179 | + | <button className="panel-toggle" onClick={() => setOpen(!open)}> |
|
| 180 | + | {open ? "\u203A" : "\u2039"} |
|
| 181 | + | </button> |
|
| 182 | + | <div className="panel-content"> |
|
| 183 | + | <div className="panel-header"> |
|
| 184 | + | <span className="panel-title">CONTROLS</span> |
|
| 185 | + | <div className="panel-header-actions"> |
|
| 186 | + | {hasChanges && ( |
|
| 187 | + | <button className="reset-btn" onClick={onReset}> |
|
| 188 | + | RESET |
|
| 189 | + | </button> |
|
| 190 | + | )} |
|
| 191 | + | </div> |
|
| 192 | + | </div> |
|
| 193 | + | ||
| 194 | + | <div className="randomize-section"> |
|
| 195 | + | <button className="randomize-btn" onClick={handleRandomize}> |
|
| 196 | + | RANDOMIZE |
|
| 197 | + | </button> |
|
| 198 | + | <button |
|
| 199 | + | className={`fluid-toggle ${fluid ? "active" : ""}`} |
|
| 200 | + | onClick={handleFluidToggle} |
|
| 201 | + | > |
|
| 202 | + | FLUID |
|
| 203 | + | </button> |
|
| 204 | + | </div> |
|
| 205 | + | ||
| 206 | + | <div className="panel-section"> |
|
| 207 | + | <span className="section-label">ADJUSTMENTS</span> |
|
| 208 | + | <SliderGroup filters={filterState.basic} onChange={onBasicChange} /> |
|
| 209 | + | </div> |
|
| 210 | + | ||
| 211 | + | <div className="panel-section"> |
|
| 212 | + | <span className="section-label">CURVES</span> |
|
| 213 | + | <CurvesEditor curves={filterState.curves} onChange={onCurveChange} /> |
|
| 214 | + | </div> |
|
| 215 | + | </div> |
|
| 216 | + | </div> |
|
| 217 | + | ); |
|
| 218 | + | } |
| 1 | + | import { useRef, useEffect, useCallback, useState } from "react"; |
|
| 2 | + | import type { CurvePoint, CurveChannel, CurvesState } from "../lib/types"; |
|
| 3 | + | import { interpolateSpline } from "../lib/curves"; |
|
| 4 | + | ||
| 5 | + | interface CurvesEditorProps { |
|
| 6 | + | curves: CurvesState; |
|
| 7 | + | onChange: (channel: CurveChannel, points: CurvePoint[]) => void; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | const SIZE = 256; |
|
| 11 | + | const HANDLE_RADIUS = 6; |
|
| 12 | + | const CHANNELS: CurveChannel[] = ["rgb", "r", "g", "b"]; |
|
| 13 | + | const CHANNEL_LABELS: Record<CurveChannel, string> = { |
|
| 14 | + | rgb: "RGB", |
|
| 15 | + | r: "R", |
|
| 16 | + | g: "G", |
|
| 17 | + | b: "B", |
|
| 18 | + | }; |
|
| 19 | + | ||
| 20 | + | export function CurvesEditor({ curves, onChange }: CurvesEditorProps) { |
|
| 21 | + | const canvasRef = useRef<HTMLCanvasElement>(null); |
|
| 22 | + | const [activeChannel, setActiveChannel] = useState<CurveChannel>("rgb"); |
|
| 23 | + | const draggingRef = useRef<number | null>(null); |
|
| 24 | + | ||
| 25 | + | const points = curves[activeChannel]; |
|
| 26 | + | ||
| 27 | + | const draw = useCallback(() => { |
|
| 28 | + | const canvas = canvasRef.current; |
|
| 29 | + | if (!canvas) return; |
|
| 30 | + | const ctx = canvas.getContext("2d"); |
|
| 31 | + | if (!ctx) return; |
|
| 32 | + | ||
| 33 | + | const dpr = window.devicePixelRatio || 1; |
|
| 34 | + | const w = SIZE; |
|
| 35 | + | const h = SIZE; |
|
| 36 | + | ||
| 37 | + | canvas.width = w * dpr; |
|
| 38 | + | canvas.height = h * dpr; |
|
| 39 | + | ctx.scale(dpr, dpr); |
|
| 40 | + | ||
| 41 | + | // Background |
|
| 42 | + | ctx.fillStyle = "#1e1c1f"; |
|
| 43 | + | ctx.fillRect(0, 0, w, h); |
|
| 44 | + | ||
| 45 | + | // Grid |
|
| 46 | + | ctx.strokeStyle = "#333"; |
|
| 47 | + | ctx.lineWidth = 0.5; |
|
| 48 | + | for (let i = 1; i < 4; i++) { |
|
| 49 | + | const pos = (i / 4) * w; |
|
| 50 | + | ctx.beginPath(); |
|
| 51 | + | ctx.moveTo(pos, 0); |
|
| 52 | + | ctx.lineTo(pos, h); |
|
| 53 | + | ctx.stroke(); |
|
| 54 | + | ctx.beginPath(); |
|
| 55 | + | ctx.moveTo(0, pos); |
|
| 56 | + | ctx.lineTo(w, pos); |
|
| 57 | + | ctx.stroke(); |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | // Diagonal reference |
|
| 61 | + | ctx.strokeStyle = "#555"; |
|
| 62 | + | ctx.lineWidth = 1; |
|
| 63 | + | ctx.setLineDash([4, 4]); |
|
| 64 | + | ctx.beginPath(); |
|
| 65 | + | ctx.moveTo(0, h); |
|
| 66 | + | ctx.lineTo(w, 0); |
|
| 67 | + | ctx.stroke(); |
|
| 68 | + | ctx.setLineDash([]); |
|
| 69 | + | ||
| 70 | + | // Curve |
|
| 71 | + | const lut = interpolateSpline(points); |
|
| 72 | + | ctx.strokeStyle = "#ffffff"; |
|
| 73 | + | ctx.lineWidth = 1.5; |
|
| 74 | + | ctx.beginPath(); |
|
| 75 | + | for (let x = 0; x < 256; x++) { |
|
| 76 | + | const px = (x / 255) * w; |
|
| 77 | + | const py = h - (lut[x] / 255) * h; |
|
| 78 | + | if (x === 0) ctx.moveTo(px, py); |
|
| 79 | + | else ctx.lineTo(px, py); |
|
| 80 | + | } |
|
| 81 | + | ctx.stroke(); |
|
| 82 | + | ||
| 83 | + | // Control points |
|
| 84 | + | for (const point of points) { |
|
| 85 | + | const px = (point.x / 255) * w; |
|
| 86 | + | const py = h - (point.y / 255) * h; |
|
| 87 | + | ctx.fillStyle = "#121113"; |
|
| 88 | + | ctx.strokeStyle = "#ffffff"; |
|
| 89 | + | ctx.lineWidth = 1.5; |
|
| 90 | + | ctx.beginPath(); |
|
| 91 | + | ctx.arc(px, py, HANDLE_RADIUS, 0, Math.PI * 2); |
|
| 92 | + | ctx.fill(); |
|
| 93 | + | ctx.stroke(); |
|
| 94 | + | } |
|
| 95 | + | }, [points]); |
|
| 96 | + | ||
| 97 | + | useEffect(() => { |
|
| 98 | + | draw(); |
|
| 99 | + | }, [draw]); |
|
| 100 | + | ||
| 101 | + | function canvasToPoint(e: React.PointerEvent): { x: number; y: number } { |
|
| 102 | + | const canvas = canvasRef.current!; |
|
| 103 | + | const rect = canvas.getBoundingClientRect(); |
|
| 104 | + | const x = ((e.clientX - rect.left) / rect.width) * 255; |
|
| 105 | + | const y = (1 - (e.clientY - rect.top) / rect.height) * 255; |
|
| 106 | + | return { x: Math.round(x), y: Math.round(Math.max(0, Math.min(255, y))) }; |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | function findNearestPoint(cx: number, cy: number): number | null { |
|
| 110 | + | const threshold = 15; |
|
| 111 | + | let bestIdx: number | null = null; |
|
| 112 | + | let bestDist = Infinity; |
|
| 113 | + | for (let i = 0; i < points.length; i++) { |
|
| 114 | + | const dx = points[i].x - cx; |
|
| 115 | + | const dy = points[i].y - cy; |
|
| 116 | + | const dist = Math.sqrt(dx * dx + dy * dy); |
|
| 117 | + | if (dist < threshold && dist < bestDist) { |
|
| 118 | + | bestDist = dist; |
|
| 119 | + | bestIdx = i; |
|
| 120 | + | } |
|
| 121 | + | } |
|
| 122 | + | return bestIdx; |
|
| 123 | + | } |
|
| 124 | + | ||
| 125 | + | function handlePointerDown(e: React.PointerEvent) { |
|
| 126 | + | const { x, y } = canvasToPoint(e); |
|
| 127 | + | const idx = findNearestPoint(x, y); |
|
| 128 | + | ||
| 129 | + | if (idx !== null) { |
|
| 130 | + | draggingRef.current = idx; |
|
| 131 | + | } else { |
|
| 132 | + | // Add new point |
|
| 133 | + | const newPoints = [...points, { x, y }].sort((a, b) => a.x - b.x); |
|
| 134 | + | onChange(activeChannel, newPoints); |
|
| 135 | + | const newIdx = newPoints.findIndex((p) => p.x === x && p.y === y); |
|
| 136 | + | draggingRef.current = newIdx; |
|
| 137 | + | } |
|
| 138 | + | ||
| 139 | + | (e.target as Element).setPointerCapture(e.pointerId); |
|
| 140 | + | } |
|
| 141 | + | ||
| 142 | + | function handlePointerMove(e: React.PointerEvent) { |
|
| 143 | + | if (draggingRef.current === null) return; |
|
| 144 | + | const { y } = canvasToPoint(e); |
|
| 145 | + | const idx = draggingRef.current; |
|
| 146 | + | ||
| 147 | + | const updated = [...points]; |
|
| 148 | + | // Endpoints can only move vertically |
|
| 149 | + | if (idx === 0 || idx === points.length - 1) { |
|
| 150 | + | updated[idx] = { ...updated[idx], y }; |
|
| 151 | + | } else { |
|
| 152 | + | const rect = canvasRef.current!.getBoundingClientRect(); |
|
| 153 | + | const rawX = ((e.clientX - rect.left) / rect.width) * 255; |
|
| 154 | + | const x = Math.round(Math.max(updated[idx - 1].x + 1, Math.min(updated[idx + 1].x - 1, rawX))); |
|
| 155 | + | updated[idx] = { x, y }; |
|
| 156 | + | } |
|
| 157 | + | onChange(activeChannel, updated); |
|
| 158 | + | } |
|
| 159 | + | ||
| 160 | + | function handlePointerUp() { |
|
| 161 | + | draggingRef.current = null; |
|
| 162 | + | } |
|
| 163 | + | ||
| 164 | + | function handleDoubleClick(e: React.MouseEvent) { |
|
| 165 | + | const canvas = canvasRef.current!; |
|
| 166 | + | const rect = canvas.getBoundingClientRect(); |
|
| 167 | + | const cx = ((e.clientX - rect.left) / rect.width) * 255; |
|
| 168 | + | const cy = (1 - (e.clientY - rect.top) / rect.height) * 255; |
|
| 169 | + | const idx = findNearestPoint(cx, cy); |
|
| 170 | + | if (idx !== null && idx !== 0 && idx !== points.length - 1) { |
|
| 171 | + | const updated = points.filter((_, i) => i !== idx); |
|
| 172 | + | onChange(activeChannel, updated); |
|
| 173 | + | } |
|
| 174 | + | } |
|
| 175 | + | ||
| 176 | + | return ( |
|
| 177 | + | <div className="curves-editor"> |
|
| 178 | + | <div className="curves-tabs"> |
|
| 179 | + | {CHANNELS.map((ch) => ( |
|
| 180 | + | <button |
|
| 181 | + | key={ch} |
|
| 182 | + | className={`curves-tab ${activeChannel === ch ? "active" : ""}`} |
|
| 183 | + | onClick={() => setActiveChannel(ch)} |
|
| 184 | + | > |
|
| 185 | + | {CHANNEL_LABELS[ch]} |
|
| 186 | + | </button> |
|
| 187 | + | ))} |
|
| 188 | + | </div> |
|
| 189 | + | <canvas |
|
| 190 | + | ref={canvasRef} |
|
| 191 | + | className="curves-canvas" |
|
| 192 | + | style={{ width: SIZE, height: SIZE }} |
|
| 193 | + | onPointerDown={handlePointerDown} |
|
| 194 | + | onPointerMove={handlePointerMove} |
|
| 195 | + | onPointerUp={handlePointerUp} |
|
| 196 | + | onDoubleClick={handleDoubleClick} |
|
| 197 | + | /> |
|
| 198 | + | </div> |
|
| 199 | + | ); |
|
| 200 | + | } |
| 1 | + | interface SliderProps { |
|
| 2 | + | label: string; |
|
| 3 | + | value: number; |
|
| 4 | + | min: number; |
|
| 5 | + | max: number; |
|
| 6 | + | step?: number; |
|
| 7 | + | onChange: (value: number) => void; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | export function Slider({ label, value, min, max, step = 1, onChange }: SliderProps) { |
|
| 11 | + | return ( |
|
| 12 | + | <div className="slider-row"> |
|
| 13 | + | <div className="slider-header"> |
|
| 14 | + | <label>{label}</label> |
|
| 15 | + | <span className="slider-value">{value}</span> |
|
| 16 | + | </div> |
|
| 17 | + | <input |
|
| 18 | + | type="range" |
|
| 19 | + | min={min} |
|
| 20 | + | max={max} |
|
| 21 | + | step={step} |
|
| 22 | + | value={value} |
|
| 23 | + | onChange={(e) => onChange(Number(e.target.value))} |
|
| 24 | + | /> |
|
| 25 | + | </div> |
|
| 26 | + | ); |
|
| 27 | + | } |
| 1 | + | import type { BasicFilters } from "../lib/types"; |
|
| 2 | + | import { Slider } from "./Slider"; |
|
| 3 | + | ||
| 4 | + | interface SliderGroupProps { |
|
| 5 | + | filters: BasicFilters; |
|
| 6 | + | onChange: (key: keyof BasicFilters, value: number) => void; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | const SLIDER_CONFIG: { |
|
| 10 | + | key: keyof BasicFilters; |
|
| 11 | + | label: string; |
|
| 12 | + | min: number; |
|
| 13 | + | max: number; |
|
| 14 | + | }[] = [ |
|
| 15 | + | { key: "brightness", label: "BRIGHTNESS", min: 0, max: 200 }, |
|
| 16 | + | { key: "contrast", label: "CONTRAST", min: 0, max: 200 }, |
|
| 17 | + | { key: "exposure", label: "EXPOSURE", min: -100, max: 100 }, |
|
| 18 | + | { key: "saturation", label: "SATURATION", min: 0, max: 200 }, |
|
| 19 | + | { key: "temperature", label: "TEMPERATURE", min: -100, max: 100 }, |
|
| 20 | + | { key: "tint", label: "TINT", min: -100, max: 100 }, |
|
| 21 | + | { key: "highlights", label: "HIGHLIGHTS", min: -100, max: 100 }, |
|
| 22 | + | { key: "shadows", label: "SHADOWS", min: -100, max: 100 }, |
|
| 23 | + | ]; |
|
| 24 | + | ||
| 25 | + | export function SliderGroup({ filters, onChange }: SliderGroupProps) { |
|
| 26 | + | return ( |
|
| 27 | + | <div className="slider-group"> |
|
| 28 | + | {SLIDER_CONFIG.map(({ key, label, min, max }) => ( |
|
| 29 | + | <Slider |
|
| 30 | + | key={key} |
|
| 31 | + | label={label} |
|
| 32 | + | value={filters[key]} |
|
| 33 | + | min={min} |
|
| 34 | + | max={max} |
|
| 35 | + | onChange={(v) => onChange(key, v)} |
|
| 36 | + | /> |
|
| 37 | + | ))} |
|
| 38 | + | </div> |
|
| 39 | + | ); |
|
| 40 | + | } |
| 1 | + | import { useEffect, useRef, useCallback } from "react"; |
|
| 2 | + | ||
| 3 | + | export function useAnimationLoop(callback: () => void) { |
|
| 4 | + | const callbackRef = useRef(callback); |
|
| 5 | + | const rafRef = useRef<number>(0); |
|
| 6 | + | ||
| 7 | + | callbackRef.current = callback; |
|
| 8 | + | ||
| 9 | + | const loop = useCallback(() => { |
|
| 10 | + | callbackRef.current(); |
|
| 11 | + | rafRef.current = requestAnimationFrame(loop); |
|
| 12 | + | }, []); |
|
| 13 | + | ||
| 14 | + | useEffect(() => { |
|
| 15 | + | rafRef.current = requestAnimationFrame(loop); |
|
| 16 | + | return () => cancelAnimationFrame(rafRef.current); |
|
| 17 | + | }, [loop]); |
|
| 18 | + | } |
| 1 | + | import { useEffect, useRef, useState } from "react"; |
|
| 2 | + | ||
| 3 | + | export function useCamera() { |
|
| 4 | + | const videoRef = useRef<HTMLVideoElement>(null); |
|
| 5 | + | const [error, setError] = useState<string | null>(null); |
|
| 6 | + | const [isReady, setIsReady] = useState(false); |
|
| 7 | + | ||
| 8 | + | useEffect(() => { |
|
| 9 | + | let stream: MediaStream | null = null; |
|
| 10 | + | ||
| 11 | + | async function init() { |
|
| 12 | + | try { |
|
| 13 | + | stream = await navigator.mediaDevices.getUserMedia({ |
|
| 14 | + | video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } }, |
|
| 15 | + | audio: false, |
|
| 16 | + | }); |
|
| 17 | + | if (videoRef.current) { |
|
| 18 | + | videoRef.current.srcObject = stream; |
|
| 19 | + | videoRef.current.onloadedmetadata = () => { |
|
| 20 | + | videoRef.current!.play(); |
|
| 21 | + | setIsReady(true); |
|
| 22 | + | }; |
|
| 23 | + | } |
|
| 24 | + | } catch (err) { |
|
| 25 | + | if (err instanceof DOMException) { |
|
| 26 | + | if (err.name === "NotAllowedError") { |
|
| 27 | + | setError("Camera access denied. Please allow camera permissions."); |
|
| 28 | + | } else if (err.name === "NotFoundError") { |
|
| 29 | + | setError("No camera found. Please connect a camera."); |
|
| 30 | + | } else { |
|
| 31 | + | setError(`Camera error: ${err.message}`); |
|
| 32 | + | } |
|
| 33 | + | } else { |
|
| 34 | + | setError("Failed to access camera."); |
|
| 35 | + | } |
|
| 36 | + | } |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | init(); |
|
| 40 | + | ||
| 41 | + | return () => { |
|
| 42 | + | if (stream) { |
|
| 43 | + | stream.getTracks().forEach((t) => t.stop()); |
|
| 44 | + | } |
|
| 45 | + | }; |
|
| 46 | + | }, []); |
|
| 47 | + | ||
| 48 | + | return { videoRef, error, isReady }; |
|
| 49 | + | } |
| 1 | + | @font-face { |
|
| 2 | + | font-family: "Commit Mono"; |
|
| 3 | + | src: url("/CommitMono-400-Regular.otf") format("opentype"); |
|
| 4 | + | font-weight: 400; |
|
| 5 | + | font-style: normal; |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | @font-face { |
|
| 9 | + | font-family: "Commit Mono"; |
|
| 10 | + | src: url("/CommitMono-700-Regular.otf") format("opentype"); |
|
| 11 | + | font-weight: 700; |
|
| 12 | + | font-style: normal; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | * { |
|
| 16 | + | padding: 0; |
|
| 17 | + | margin: 0; |
|
| 18 | + | box-sizing: border-box; |
|
| 19 | + | font-family: "Commit Mono", monospace, sans-serif; |
|
| 20 | + | scrollbar-width: none; |
|
| 21 | + | -ms-overflow-style: none; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | html { |
|
| 25 | + | background: #121113; |
|
| 26 | + | color: #ffffff; |
|
| 27 | + | font-size: 14px; |
|
| 28 | + | line-height: 1.6; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | html::-webkit-scrollbar { |
|
| 32 | + | display: none; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | body { |
|
| 36 | + | margin: 0; |
|
| 37 | + | overflow: hidden; |
|
| 38 | + | width: 100vw; |
|
| 39 | + | height: 100vh; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | #root { |
|
| 43 | + | width: 100%; |
|
| 44 | + | height: 100%; |
|
| 45 | + | position: relative; |
|
| 46 | + | } |
| 1 | + | import type { CurvePoint } from "./types"; |
|
| 2 | + | ||
| 3 | + | export function interpolateSpline(points: CurvePoint[]): Uint8Array { |
|
| 4 | + | const lut = new Uint8Array(256); |
|
| 5 | + | ||
| 6 | + | if (points.length < 2) { |
|
| 7 | + | for (let i = 0; i < 256; i++) lut[i] = i; |
|
| 8 | + | return lut; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | const sorted = [...points].sort((a, b) => a.x - b.x); |
|
| 12 | + | const n = sorted.length; |
|
| 13 | + | ||
| 14 | + | if (n === 2) { |
|
| 15 | + | const [p0, p1] = sorted; |
|
| 16 | + | for (let i = 0; i < 256; i++) { |
|
| 17 | + | if (i <= p0.x) { |
|
| 18 | + | lut[i] = clamp(p0.y); |
|
| 19 | + | } else if (i >= p1.x) { |
|
| 20 | + | lut[i] = clamp(p1.y); |
|
| 21 | + | } else { |
|
| 22 | + | const t = (i - p0.x) / (p1.x - p0.x); |
|
| 23 | + | lut[i] = clamp(p0.y + t * (p1.y - p0.y)); |
|
| 24 | + | } |
|
| 25 | + | } |
|
| 26 | + | return lut; |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | // Monotone cubic Hermite interpolation (Fritsch-Carlson) |
|
| 30 | + | const xs = sorted.map((p) => p.x); |
|
| 31 | + | const ys = sorted.map((p) => p.y); |
|
| 32 | + | const dx: number[] = []; |
|
| 33 | + | const dy: number[] = []; |
|
| 34 | + | const m: number[] = []; |
|
| 35 | + | const ms: number[] = []; |
|
| 36 | + | ||
| 37 | + | for (let i = 0; i < n - 1; i++) { |
|
| 38 | + | dx[i] = xs[i + 1] - xs[i]; |
|
| 39 | + | dy[i] = ys[i + 1] - ys[i]; |
|
| 40 | + | ms[i] = dx[i] === 0 ? 0 : dy[i] / dx[i]; |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | m[0] = ms[0]; |
|
| 44 | + | for (let i = 1; i < n - 1; i++) { |
|
| 45 | + | if (ms[i - 1] * ms[i] <= 0) { |
|
| 46 | + | m[i] = 0; |
|
| 47 | + | } else { |
|
| 48 | + | m[i] = (ms[i - 1] + ms[i]) / 2; |
|
| 49 | + | } |
|
| 50 | + | } |
|
| 51 | + | m[n - 1] = ms[n - 2]; |
|
| 52 | + | ||
| 53 | + | // Fritsch-Carlson monotonicity |
|
| 54 | + | for (let i = 0; i < n - 1; i++) { |
|
| 55 | + | if (ms[i] === 0) { |
|
| 56 | + | m[i] = 0; |
|
| 57 | + | m[i + 1] = 0; |
|
| 58 | + | } else { |
|
| 59 | + | const alpha = m[i] / ms[i]; |
|
| 60 | + | const beta = m[i + 1] / ms[i]; |
|
| 61 | + | const tau = alpha * alpha + beta * beta; |
|
| 62 | + | if (tau > 9) { |
|
| 63 | + | const s = 3 / Math.sqrt(tau); |
|
| 64 | + | m[i] = s * alpha * ms[i]; |
|
| 65 | + | m[i + 1] = s * beta * ms[i]; |
|
| 66 | + | } |
|
| 67 | + | } |
|
| 68 | + | } |
|
| 69 | + | ||
| 70 | + | for (let x = 0; x < 256; x++) { |
|
| 71 | + | if (x <= xs[0]) { |
|
| 72 | + | lut[x] = clamp(ys[0]); |
|
| 73 | + | continue; |
|
| 74 | + | } |
|
| 75 | + | if (x >= xs[n - 1]) { |
|
| 76 | + | lut[x] = clamp(ys[n - 1]); |
|
| 77 | + | continue; |
|
| 78 | + | } |
|
| 79 | + | ||
| 80 | + | let seg = 0; |
|
| 81 | + | for (let i = 0; i < n - 1; i++) { |
|
| 82 | + | if (x >= xs[i] && x < xs[i + 1]) { |
|
| 83 | + | seg = i; |
|
| 84 | + | break; |
|
| 85 | + | } |
|
| 86 | + | } |
|
| 87 | + | ||
| 88 | + | const h = dx[seg]; |
|
| 89 | + | const t = (x - xs[seg]) / h; |
|
| 90 | + | const t2 = t * t; |
|
| 91 | + | const t3 = t2 * t; |
|
| 92 | + | ||
| 93 | + | const h00 = 2 * t3 - 3 * t2 + 1; |
|
| 94 | + | const h10 = t3 - 2 * t2 + t; |
|
| 95 | + | const h01 = -2 * t3 + 3 * t2; |
|
| 96 | + | const h11 = t3 - t2; |
|
| 97 | + | ||
| 98 | + | const val = h00 * ys[seg] + h10 * h * m[seg] + h01 * ys[seg + 1] + h11 * h * m[seg + 1]; |
|
| 99 | + | lut[x] = clamp(val); |
|
| 100 | + | } |
|
| 101 | + | ||
| 102 | + | return lut; |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | function clamp(v: number): number { |
|
| 106 | + | return Math.max(0, Math.min(255, Math.round(v))); |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | export function isIdentityCurve(points: CurvePoint[]): boolean { |
|
| 110 | + | if (points.length !== 2) return false; |
|
| 111 | + | return points[0].x === 0 && points[0].y === 0 && points[1].x === 255 && points[1].y === 255; |
|
| 112 | + | } |
| 1 | + | import type { BasicFilters, CurvesState } from "./types"; |
|
| 2 | + | import { interpolateSpline, isIdentityCurve } from "./curves"; |
|
| 3 | + | ||
| 4 | + | export function buildCSSFilter(basic: BasicFilters): string { |
|
| 5 | + | const parts: string[] = []; |
|
| 6 | + | ||
| 7 | + | // Brightness: slider 0-200, CSS brightness(0-2) |
|
| 8 | + | parts.push(`brightness(${basic.brightness / 100})`); |
|
| 9 | + | ||
| 10 | + | // Contrast: slider 0-200, CSS contrast(0-2) |
|
| 11 | + | parts.push(`contrast(${basic.contrast / 100})`); |
|
| 12 | + | ||
| 13 | + | // Saturation: slider 0-200, CSS saturate(0-2) |
|
| 14 | + | parts.push(`saturate(${basic.saturation / 100})`); |
|
| 15 | + | ||
| 16 | + | // Exposure: -100 to 100, maps to exponential brightness multiplier |
|
| 17 | + | if (basic.exposure !== 0) { |
|
| 18 | + | const exposureMult = Math.pow(2, basic.exposure / 100); |
|
| 19 | + | parts.push(`brightness(${exposureMult})`); |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | // Temperature: -100 (cool) to 100 (warm) |
|
| 23 | + | if (basic.temperature !== 0) { |
|
| 24 | + | const absTemp = Math.abs(basic.temperature) / 100; |
|
| 25 | + | const sepia = absTemp * 0.3; |
|
| 26 | + | parts.push(`sepia(${sepia})`); |
|
| 27 | + | if (basic.temperature < 0) { |
|
| 28 | + | parts.push(`hue-rotate(180deg)`); |
|
| 29 | + | } |
|
| 30 | + | parts.push(`saturate(${1 + absTemp * 0.2})`); |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | // Tint: -100 to 100, maps to hue-rotate -30 to 30 degrees |
|
| 34 | + | if (basic.tint !== 0) { |
|
| 35 | + | const deg = (basic.tint / 100) * 30; |
|
| 36 | + | parts.push(`hue-rotate(${deg}deg)`); |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | return parts.join(" "); |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | export interface LUTResult { |
|
| 43 | + | r: Uint8Array; |
|
| 44 | + | g: Uint8Array; |
|
| 45 | + | b: Uint8Array; |
|
| 46 | + | isIdentity: boolean; |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | export function buildLUT(basic: BasicFilters, curves: CurvesState): LUTResult { |
|
| 50 | + | const curvesIdentity = |
|
| 51 | + | isIdentityCurve(curves.rgb) && |
|
| 52 | + | isIdentityCurve(curves.r) && |
|
| 53 | + | isIdentityCurve(curves.g) && |
|
| 54 | + | isIdentityCurve(curves.b); |
|
| 55 | + | ||
| 56 | + | const noHighlightsShadows = basic.highlights === 0 && basic.shadows === 0; |
|
| 57 | + | ||
| 58 | + | if (curvesIdentity && noHighlightsShadows) { |
|
| 59 | + | const identity = new Uint8Array(256); |
|
| 60 | + | for (let i = 0; i < 256; i++) identity[i] = i; |
|
| 61 | + | return { r: identity, g: identity, b: identity, isIdentity: true }; |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | // Start with highlight/shadow adjustments |
|
| 65 | + | const baseLut = new Uint8Array(256); |
|
| 66 | + | for (let i = 0; i < 256; i++) { |
|
| 67 | + | let val = i; |
|
| 68 | + | ||
| 69 | + | // Shadows: gamma on lower half (values < 128) |
|
| 70 | + | if (basic.shadows !== 0) { |
|
| 71 | + | const shadowGamma = 1 - basic.shadows / 200; |
|
| 72 | + | if (i < 128) { |
|
| 73 | + | const t = i / 128; |
|
| 74 | + | const adjusted = Math.pow(t, shadowGamma) * 128; |
|
| 75 | + | val = adjusted + (val - i); |
|
| 76 | + | } |
|
| 77 | + | } |
|
| 78 | + | ||
| 79 | + | // Highlights: gamma on upper half (values > 128) |
|
| 80 | + | if (basic.highlights !== 0) { |
|
| 81 | + | const highlightGamma = 1 - basic.highlights / 200; |
|
| 82 | + | if (val > 128) { |
|
| 83 | + | const t = (val - 128) / 127; |
|
| 84 | + | val = 128 + Math.pow(t, highlightGamma) * 127; |
|
| 85 | + | } |
|
| 86 | + | } |
|
| 87 | + | ||
| 88 | + | baseLut[i] = Math.max(0, Math.min(255, Math.round(val))); |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | // Apply curves on top |
|
| 92 | + | const rgbLut = interpolateSpline(curves.rgb); |
|
| 93 | + | const rCurve = interpolateSpline(curves.r); |
|
| 94 | + | const gCurve = interpolateSpline(curves.g); |
|
| 95 | + | const bCurve = interpolateSpline(curves.b); |
|
| 96 | + | ||
| 97 | + | const r = new Uint8Array(256); |
|
| 98 | + | const g = new Uint8Array(256); |
|
| 99 | + | const b = new Uint8Array(256); |
|
| 100 | + | ||
| 101 | + | for (let i = 0; i < 256; i++) { |
|
| 102 | + | const base = baseLut[i]; |
|
| 103 | + | const afterRgb = rgbLut[base]; |
|
| 104 | + | r[i] = rCurve[afterRgb]; |
|
| 105 | + | g[i] = gCurve[afterRgb]; |
|
| 106 | + | b[i] = bCurve[afterRgb]; |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | return { r, g, b, isIdentity: false }; |
|
| 110 | + | } |
| 1 | + | export interface BasicFilters { |
|
| 2 | + | brightness: number; |
|
| 3 | + | contrast: number; |
|
| 4 | + | exposure: number; |
|
| 5 | + | saturation: number; |
|
| 6 | + | temperature: number; |
|
| 7 | + | tint: number; |
|
| 8 | + | highlights: number; |
|
| 9 | + | shadows: number; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | export interface CurvePoint { |
|
| 13 | + | x: number; |
|
| 14 | + | y: number; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | export type CurveChannel = "rgb" | "r" | "g" | "b"; |
|
| 18 | + | ||
| 19 | + | export interface CurvesState { |
|
| 20 | + | rgb: CurvePoint[]; |
|
| 21 | + | r: CurvePoint[]; |
|
| 22 | + | g: CurvePoint[]; |
|
| 23 | + | b: CurvePoint[]; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | export interface FilterState { |
|
| 27 | + | basic: BasicFilters; |
|
| 28 | + | curves: CurvesState; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | export const DEFAULT_BASIC: BasicFilters = { |
|
| 32 | + | brightness: 100, |
|
| 33 | + | contrast: 100, |
|
| 34 | + | exposure: 0, |
|
| 35 | + | saturation: 100, |
|
| 36 | + | temperature: 0, |
|
| 37 | + | tint: 0, |
|
| 38 | + | highlights: 0, |
|
| 39 | + | shadows: 0, |
|
| 40 | + | }; |
|
| 41 | + | ||
| 42 | + | export const DEFAULT_CURVE: CurvePoint[] = [ |
|
| 43 | + | { x: 0, y: 0 }, |
|
| 44 | + | { x: 255, y: 255 }, |
|
| 45 | + | ]; |
|
| 46 | + | ||
| 47 | + | export const DEFAULT_CURVES: CurvesState = { |
|
| 48 | + | rgb: [...DEFAULT_CURVE], |
|
| 49 | + | r: [...DEFAULT_CURVE], |
|
| 50 | + | g: [...DEFAULT_CURVE], |
|
| 51 | + | b: [...DEFAULT_CURVE], |
|
| 52 | + | }; |
|
| 53 | + | ||
| 54 | + | export const DEFAULT_FILTER_STATE: FilterState = { |
|
| 55 | + | basic: { ...DEFAULT_BASIC }, |
|
| 56 | + | curves: { |
|
| 57 | + | rgb: [...DEFAULT_CURVE], |
|
| 58 | + | r: [...DEFAULT_CURVE], |
|
| 59 | + | g: [...DEFAULT_CURVE], |
|
| 60 | + | b: [...DEFAULT_CURVE], |
|
| 61 | + | }, |
|
| 62 | + | }; |
| 1 | + | import { StrictMode } from 'react' |
|
| 2 | + | import { createRoot } from 'react-dom/client' |
|
| 3 | + | import './index.css' |
|
| 4 | + | import App from './App.tsx' |
|
| 5 | + | ||
| 6 | + | createRoot(document.getElementById('root')!).render( |
|
| 7 | + | <StrictMode> |
|
| 8 | + | <App /> |
|
| 9 | + | </StrictMode>, |
|
| 10 | + | ) |
| 1 | + | { |
|
| 2 | + | "compilerOptions": { |
|
| 3 | + | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
|
| 4 | + | "target": "ES2023", |
|
| 5 | + | "useDefineForClassFields": true, |
|
| 6 | + | "lib": ["ES2023", "DOM", "DOM.Iterable"], |
|
| 7 | + | "module": "ESNext", |
|
| 8 | + | "types": ["vite/client"], |
|
| 9 | + | "skipLibCheck": true, |
|
| 10 | + | ||
| 11 | + | /* Bundler mode */ |
|
| 12 | + | "moduleResolution": "bundler", |
|
| 13 | + | "allowImportingTsExtensions": true, |
|
| 14 | + | "verbatimModuleSyntax": true, |
|
| 15 | + | "moduleDetection": "force", |
|
| 16 | + | "noEmit": true, |
|
| 17 | + | "jsx": "react-jsx", |
|
| 18 | + | ||
| 19 | + | /* Linting */ |
|
| 20 | + | "strict": true, |
|
| 21 | + | "noUnusedLocals": true, |
|
| 22 | + | "noUnusedParameters": true, |
|
| 23 | + | "erasableSyntaxOnly": true, |
|
| 24 | + | "noFallthroughCasesInSwitch": true, |
|
| 25 | + | "noUncheckedSideEffectImports": true |
|
| 26 | + | }, |
|
| 27 | + | "include": ["src"] |
|
| 28 | + | } |
| 1 | + | { |
|
| 2 | + | "files": [], |
|
| 3 | + | "references": [ |
|
| 4 | + | { "path": "./tsconfig.app.json" }, |
|
| 5 | + | { "path": "./tsconfig.node.json" } |
|
| 6 | + | ] |
|
| 7 | + | } |
| 1 | + | { |
|
| 2 | + | "compilerOptions": { |
|
| 3 | + | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
|
| 4 | + | "target": "ES2023", |
|
| 5 | + | "lib": ["ES2023"], |
|
| 6 | + | "module": "ESNext", |
|
| 7 | + | "types": ["node"], |
|
| 8 | + | "skipLibCheck": true, |
|
| 9 | + | ||
| 10 | + | /* Bundler mode */ |
|
| 11 | + | "moduleResolution": "bundler", |
|
| 12 | + | "allowImportingTsExtensions": true, |
|
| 13 | + | "verbatimModuleSyntax": true, |
|
| 14 | + | "moduleDetection": "force", |
|
| 15 | + | "noEmit": true, |
|
| 16 | + | ||
| 17 | + | /* Linting */ |
|
| 18 | + | "strict": true, |
|
| 19 | + | "noUnusedLocals": true, |
|
| 20 | + | "noUnusedParameters": true, |
|
| 21 | + | "erasableSyntaxOnly": true, |
|
| 22 | + | "noFallthroughCasesInSwitch": true, |
|
| 23 | + | "noUncheckedSideEffectImports": true |
|
| 24 | + | }, |
|
| 25 | + | "include": ["vite.config.ts"] |
|
| 26 | + | } |
| 1 | + | import { defineConfig } from 'vite' |
|
| 2 | + | import react from '@vitejs/plugin-react' |
|
| 3 | + | ||
| 4 | + | // https://vite.dev/config/ |
|
| 5 | + | export default defineConfig({ |
|
| 6 | + | plugins: [react()], |
|
| 7 | + | }) |