diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f1fd7a2..79c0a66 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,65 +1,65 @@ -name: Deploy SvelteKit - -on: - push: - branches: [main, master] - paths: - - "src/**" - - "package.json" - - "tsconfig.json" - - "svelte.config.js" - - "vite.config.js" - - ".github/workflows/**" - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Clean _app directory - run: | - rm -rf build/_app - touch build/.nojekyll - - - name: Setup Pages - uses: actions/configure-pages@v4 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./build - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 +name: Deploy SvelteKit + +on: + push: + branches: [main, master] + paths: + - "src/**" + - "package.json" + - "tsconfig.json" + - "svelte.config.js" + - "vite.config.js" + - ".github/workflows/**" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Clean _app directory + run: | + rm -rf build/_app + touch build/.nojekyll + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./build + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f95947a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": false, + "singleQuote": false, + "trailingComma": "all", + "endOfLine": "lf", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/.stylelintrc.json b/.stylelintrc.json index 5a66e5c..4cdc552 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,20 +1,20 @@ -{ - "extends": ["stylelint-config-recommended", "stylelint-config-html/svelte"], - "rules": { - "selector-pseudo-class-no-unknown": [ - true, - { - "ignorePseudoClasses": ["global"] - } - ] - }, - "ignoreFiles": [ - ".github/**/*", - ".svelte-kit/**/*", - "build/**/*", - "coverage/**/*", - "dist/**/*", - "node_modules/**/*", - "reference/**/*" - ] -} +{ + "extends": ["stylelint-config-recommended", "stylelint-config-html/svelte"], + "rules": { + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": ["global"] + } + ] + }, + "ignoreFiles": [ + ".github/**/*", + ".svelte-kit/**/*", + "build/**/*", + "coverage/**/*", + "dist/**/*", + "node_modules/**/*", + "reference/**/*" + ] +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..c42333a --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,40 @@ +import { includeIgnoreFile } from "@eslint/compat"; +import js from "@eslint/js"; +import prettier from "eslint-config-prettier"; +import svelte from "eslint-plugin-svelte"; +import { defineConfig } from "eslint/config"; +import globals from "globals"; +import path from "node:path"; +import ts from "typescript-eslint"; +import svelteConfig from "./svelte.config.js"; + +const gitignorePath = path.resolve(import.meta.dirname, ".gitignore"); + +export default defineConfig( + { + ignores: ["reference/"], + }, + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + prettier, + ...svelte.configs.prettier, + { + languageOptions: { globals: { ...globals.browser, ...globals.node } }, + rules: { + "no-undef": "off", + }, + }, + { + files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: [".svelte"], + parser: ts.parser, + svelteConfig, + }, + }, + }, +); diff --git a/package-lock.json b/package-lock.json index 60ad11a..ee1a9e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5723 +1,7189 @@ -{ - "name": "twitch-panels", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "twitch-panels", - "version": "0.0.1", - "dependencies": { - "@types/cropperjs": "^1.1.5", - "@types/uuid": "^10.0.0", - "cropperjs": "^2.1.0", - "file-saver": "^2.0.5", - "jszip": "^3.10.1", - "svelte-konva": "^1.0.1", - "uuid": "^13.0.0" - }, - "devDependencies": { - "@iconify-json/lucide": "^1.2.89", - "@playwright/test": "^1.58.1", - "@sveltejs/adapter-auto": "^7.0.0", - "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.50.1", - "@sveltejs/vite-plugin-svelte": "^6.2.4", - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/svelte": "^5.3.1", - "@testing-library/user-event": "^14.6.1", - "@types/file-saver": "^2.0.7", - "@types/node": "^25.1.0", - "@vitest/coverage-istanbul": "^4.0.18", - "@vitest/ui": "^4.0.18", - "glob": "^13.0.2", - "jsdom": "^28.0.0", - "konva": "^10.2.0", - "stylelint": "^17.2.0", - "stylelint-config-html": "^1.1.0", - "stylelint-config-recommended": "^18.0.0", - "svelte": "^5.48.2", - "svelte-check": "^4.3.5", - "typescript": "^5.9.3", - "unplugin-icons": "^23.0.1", - "vite": "^7.3.1", - "vitest": "^4.0.18", - "vitest-canvas-mock": "^1.1.3", - "web-animations-js": "^2.3.2" - } - }, - "node_modules/@acemir/cssom": { - "version": "0.9.31", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", - "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@antfu/install-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", - "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "package-manager-detector": "^1.3.0", - "tinyexec": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@asamuzakjp/css-color": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", - "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.4" - } - }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.7.tgz", - "integrity": "sha512-8CO/UQ4tzDd7ula+/CVimJIVWez99UJlbMyIgk8xOnhAVPKLnBZmUFYVgugS441v2ZqUq5EnSh6B0Ua0liSFAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/nwsapi": "^2.3.9", - "bidi-js": "^1.0.3", - "css-tree": "^3.1.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.5" - } - }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@cacheable/memory": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.7.tgz", - "integrity": "sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cacheable/utils": "^2.3.3", - "@keyv/bigmap": "^1.3.0", - "hookified": "^1.14.0", - "keyv": "^5.5.5" - } - }, - "node_modules/@cacheable/utils": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.3.4.tgz", - "integrity": "sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hashery": "^1.3.0", - "keyv": "^5.6.0" - } - }, - "node_modules/@cropper/element": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/element/-/element-2.1.0.tgz", - "integrity": "sha512-2zELddqHQNmlvkPoiYzE5nxEjPE+C8nXoTPuvV3FvLp3YjBinc7qb73Icg9UXP0o9qC4+h9q96JgGo0AyMO/Ng==", - "license": "MIT", - "dependencies": { - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/@cropper/element-canvas": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/element-canvas/-/element-canvas-2.1.0.tgz", - "integrity": "sha512-el+rfJpZxsD2q5XxDBA4fRczcrOqB65Lb7roqXOq8LKufwf4bPWA9C6DjNJJahh/TP94dsLIEy3tSkgRMDv3Aw==", - "license": "MIT", - "dependencies": { - "@cropper/element": "^2.1.0", - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/@cropper/element-crosshair": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/element-crosshair/-/element-crosshair-2.1.0.tgz", - "integrity": "sha512-0V589dAx8uZAfvJwdINLn76gfPQEafPH94ukjJ76uX0FCUovLaAVX+VRD/MDSYn0Mza/xejzmL9Dhd1DfemvmA==", - "license": "MIT", - "dependencies": { - "@cropper/element": "^2.1.0", - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/@cropper/element-grid": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/element-grid/-/element-grid-2.1.0.tgz", - "integrity": "sha512-dEnk0rO+vp553LMvsPYgfrqVFcYXeVFrgFeavBYYEhAXtO40p7kN4rmLYLMMjaN+T/Mx2BATv6kUQpALKy2HLw==", - "license": "MIT", - "dependencies": { - "@cropper/element": "^2.1.0", - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/@cropper/element-handle": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/element-handle/-/element-handle-2.1.0.tgz", - "integrity": "sha512-8BklWA4C/2GGAULupIWleSnGutECvYt3vx9flodqDfZpDEozws4LgLqmmzVuQmVkRVUdLnXdtx28kjgWLtzkHg==", - "license": "MIT", - "dependencies": { - "@cropper/element": "^2.1.0", - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/@cropper/element-image": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/element-image/-/element-image-2.1.0.tgz", - "integrity": "sha512-mXOV8ixJvG0XtTxLebYAKDjEkFbFOQnsF02hXPZk1yQSV0J+LLhN7a2NePrtKnoTsEV19fhhX3UorMoyGGxvzg==", - "license": "MIT", - "dependencies": { - "@cropper/element": "^2.1.0", - "@cropper/element-canvas": "^2.1.0", - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/@cropper/element-selection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/element-selection/-/element-selection-2.1.0.tgz", - "integrity": "sha512-mtFtBl6HIa/s9TWohXw+Z5eJoeYTqylrIcHvS7oVv0uM7IyeRwBW65Q7z+KtLfq/LW+2Sw/XDyvR+VN/DawBPw==", - "license": "MIT", - "dependencies": { - "@cropper/element": "^2.1.0", - "@cropper/element-canvas": "^2.1.0", - "@cropper/element-image": "^2.1.0", - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/@cropper/element-shade": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/element-shade/-/element-shade-2.1.0.tgz", - "integrity": "sha512-zMdyqbb0lc0Vd1oj2Z1miIZvhyZG41OXMHvrNt0hNwblh0dVdrvtw48lnFDgRv+672vt2CNx7Q04GuvCQfPlgg==", - "license": "MIT", - "dependencies": { - "@cropper/element": "^2.1.0", - "@cropper/element-canvas": "^2.1.0", - "@cropper/element-selection": "^2.1.0", - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/@cropper/element-viewer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/element-viewer/-/element-viewer-2.1.0.tgz", - "integrity": "sha512-XnxlQuqHitd1FOFZ6E0yXAF5NYd/LyIvONLLHI9p1rJw747WYKUPxQaSYtFKF7IOizJu/8mMj++Zc1dZ5ZP3YQ==", - "license": "MIT", - "dependencies": { - "@cropper/element": "^2.1.0", - "@cropper/element-canvas": "^2.1.0", - "@cropper/element-image": "^2.1.0", - "@cropper/element-selection": "^2.1.0", - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/@cropper/elements": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/elements/-/elements-2.1.0.tgz", - "integrity": "sha512-qvzlYDn3VQgPPpsCu6Gi1XUO0v3vpXQFSjjxcVijbXeNsl/eiKrN7H9/CEiRgi5vr8kXfd7ZvgYxBjUBbH+y+w==", - "license": "MIT", - "dependencies": { - "@cropper/element": "^2.1.0", - "@cropper/element-canvas": "^2.1.0", - "@cropper/element-crosshair": "^2.1.0", - "@cropper/element-grid": "^2.1.0", - "@cropper/element-handle": "^2.1.0", - "@cropper/element-image": "^2.1.0", - "@cropper/element-selection": "^2.1.0", - "@cropper/element-shade": "^2.1.0", - "@cropper/element-viewer": "^2.1.0" - } - }, - "node_modules/@cropper/utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cropper/utils/-/utils-2.1.0.tgz", - "integrity": "sha512-wLtpZ4/UWgo+fGmG8NBWge8x5ehjfDe9ovleDfLy8kpwFaw43XXOEXQtRL1UNr0u4JZxaeO8FcXcolRWUUrlRQ==", - "license": "MIT" - }, - "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", - "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0" - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/selector-resolve-nested": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.0.tgz", - "integrity": "sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.1.1" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz", - "integrity": "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.1.1" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@exodus/bytes": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.11.0.tgz", - "integrity": "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } - } - }, - "node_modules/@iconify-json/lucide": { - "version": "1.2.89", - "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.89.tgz", - "integrity": "sha512-9rZaJZn8VBls1KZnGaFTnqqZrUkd++XB3vy9WYIMgmHHgLxQMEZXg3V+oJSEeit0kCNr/OfDBmrDwuGl/LZulA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@iconify/types": "*" - } - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@iconify/utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", - "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@iconify/types": "^2.0.0", - "mlly": "^1.8.0" - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@keyv/bigmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", - "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "hashery": "^1.4.0", - "hookified": "^1.15.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "keyv": "^5.6.0" - } - }, - "node_modules/@keyv/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@playwright/test": { - "version": "1.58.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz", - "integrity": "sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.58.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz", - "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, - "node_modules/@sveltejs/adapter-auto": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.0.tgz", - "integrity": "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" - } - }, - "node_modules/@sveltejs/adapter-static": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", - "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" - } - }, - "node_modules/@sveltejs/kit": { - "version": "2.50.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.1.tgz", - "integrity": "sha512-XRHD2i3zC4ukhz2iCQzO4mbsts081PAZnnMAQ7LNpWeYgeBmwMsalf0FGSwhFXBbtr2XViPKnFJBDCckWqrsLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/cookie": "^0.6.0", - "acorn": "^8.14.1", - "cookie": "^0.6.0", - "devalue": "^5.6.2", - "esm-env": "^1.2.2", - "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "mrmime": "^2.0.0", - "sade": "^1.8.1", - "set-cookie-parser": "^2.6.0", - "sirv": "^3.0.0" - }, - "bin": { - "svelte-kit": "svelte-kit.js" - }, - "engines": { - "node": ">=18.13" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.3.3", - "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", - "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "deepmerge": "^4.3.1", - "magic-string": "^0.30.21", - "obug": "^2.1.0", - "vitefu": "^1.1.1" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", - "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "obug": "^2.1.0" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@testing-library/jest-dom": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", - "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/svelte": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.3.1.tgz", - "integrity": "sha512-8Ez7ZOqW5geRf9PF5rkuopODe5RGy3I9XR+kc7zHh26gBiktLaxTfKmhlGaSHYUOTQE7wFsLMN9xCJVCszw47w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@testing-library/dom": "9.x.x || 10.x.x", - "@testing-library/svelte-core": "1.0.0" - }, - "engines": { - "node": ">= 10" - }, - "peerDependencies": { - "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", - "vite": "*", - "vitest": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - }, - "vitest": { - "optional": true - } - } - }, - "node_modules/@testing-library/svelte-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/svelte-core/-/svelte-core-1.0.0.tgz", - "integrity": "sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0" - } - }, - "node_modules/@testing-library/user-event": { - "version": "14.6.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", - "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" - } - }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/cropperjs": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@types/cropperjs/-/cropperjs-1.1.5.tgz", - "integrity": "sha512-ofxLqZgZK6qtTjPedXfnph2nodHueejdNLtH/ljaNbM8rGRZg17YdBORJ/xYtKBdsonc7bIYKLGBfTGu/IQtcg==", - "license": "MIT" - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/file-saver": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", - "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", - "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "license": "MIT" - }, - "node_modules/@vitest/coverage-istanbul": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-4.0.18.tgz", - "integrity": "sha512-0OhjP30owEDihYTZGWuq20rNtV1RjjJs1Mv4MaZIKcFBmiLUXX7HJLX4fU7wE+Mrc3lQxI2HKq6WrSXi5FGuCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@istanbuljs/schema": "^0.1.3", - "@jridgewell/gen-mapping": "^0.3.13", - "@jridgewell/trace-mapping": "0.3.31", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-instrument": "^6.0.3", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.1", - "obug": "^2.1.1", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "4.0.18" - } - }, - "node_modules/@vitest/expect": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", - "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", - "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.0.18", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", - "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", - "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.0.18", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", - "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.18", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", - "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/ui": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.18.tgz", - "integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.0.18", - "fflate": "^0.8.2", - "flatted": "^3.3.3", - "pathe": "^2.0.3", - "sirv": "^3.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "4.0.18" - } - }, - "node_modules/@vitest/utils": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", - "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.18", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/balanced-match": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz", - "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/cacheable": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.2.tgz", - "integrity": "sha512-w+ZuRNmex9c1TR9RcsxbfTKCjSL0rh1WA5SABbrWprIHeNBdmyQLSYonlDy9gpD+63XT8DgZ/wNh1Smvc9WnJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cacheable/memory": "^2.0.7", - "@cacheable/utils": "^2.3.3", - "hookified": "^1.15.0", - "keyv": "^5.5.5", - "qified": "^0.6.0" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001767", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz", - "integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cropperjs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-2.1.0.tgz", - "integrity": "sha512-SsSDqdVRl+mjbIBkGWlk1gCGcc+HzBqCbH5EQ+1tkAFUdxq2KUGukXF1RqhmvXrrdrX7PDwSUkWgXS7E36KvGQ==", - "license": "MIT", - "dependencies": { - "@cropper/elements": "^2.1.0", - "@cropper/utils": "^2.1.0" - } - }, - "node_modules/css-functions-list": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz", - "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssfontparser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz", - "integrity": "sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cssstyle": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", - "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^4.1.1", - "@csstools/css-syntax-patches-for-csstree": "^1.0.21", - "css-tree": "^3.1.0", - "lru-cache": "^11.2.4" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/devalue": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", - "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", - "license": "MIT" - }, - "node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause", - "peer": true - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "license": "MIT" - }, - "node_modules/esrap": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.2.tgz", - "integrity": "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "dev": true, - "license": "MIT" - }, - "node_modules/file-entry-cache": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.2.tgz", - "integrity": "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^6.1.20" - } - }, - "node_modules/file-saver": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "6.1.20", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.20.tgz", - "integrity": "sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cacheable": "^2.3.2", - "flatted": "^3.3.3", - "hookified": "^1.15.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.2.tgz", - "integrity": "sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.1.2", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/globby": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.0.tgz", - "integrity": "sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.5", - "is-path-inside": "^4.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globjoin": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", - "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hashery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.4.0.tgz", - "integrity": "sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "hookified": "^1.14.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/hookified": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", - "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.6.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/html-tags": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-5.1.0.tgz", - "integrity": "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "28.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.0.0.tgz", - "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@acemir/cssom": "^0.9.31", - "@asamuzakjp/dom-selector": "^6.7.6", - "@exodus/bytes": "^1.11.0", - "cssstyle": "^5.3.7", - "data-urls": "^7.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "is-potential-custom-element-name": "^1.0.1", - "parse5": "^8.0.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.0", - "undici": "^7.20.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/keyv": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", - "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/known-css-properties": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", - "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/konva": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/konva/-/konva-10.2.0.tgz", - "integrity": "sha512-JBoz0Xjbf49UPxCZegZ4WseqOzJ+C4AUDOtJ9eBve5RS5Fcq/u8TdBD5fDl/uPFInpC3a9uycm0sRyZpF4hheg==", - "funding": [ - { - "type": "patreon", - "url": "https://www.patreon.com/lavrton" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/konva" - }, - { - "type": "github", - "url": "https://github.com/sponsors/lavrton" - } - ], - "license": "MIT" - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", - "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "source-map-js": "^1.2.1" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mathml-tag-names": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz", - "integrity": "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/meow": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-14.0.0.tgz", - "integrity": "sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/moo-color": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", - "integrity": "sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "^1.1.4" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/package-manager-detector": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", - "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", - "dev": true, - "license": "MIT" - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", - "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/playwright": { - "version": "1.58.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", - "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.58.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.58.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", - "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-html": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.8.1.tgz", - "integrity": "sha512-OLF6P7qctfAWayOhLpcVnTGqVeJzu2W3WpIYelfz2+JV5oGxfkcEvweN9U4XpeqE0P98dcD9ssusGwlF0TK0uQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "htmlparser2": "^8.0.0", - "js-tokens": "^9.0.0", - "postcss": "^8.5.0", - "postcss-safe-parser": "^6.0.0" - }, - "engines": { - "node": "^12 || >=14" - } - }, - "node_modules/postcss-html/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/postcss-html/node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.3.3" - } - }, - "node_modules/postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qified": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/qified/-/qified-0.6.0.tgz", - "integrity": "sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hookified": "^1.14.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "dev": true, - "license": "MIT" - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-width": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", - "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/stylelint": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.2.0.tgz", - "integrity": "sha512-602jhMkRt6P1dSh9kEzbFIaOKY//h4D0E7u/w2WHKxmi5VAjjMqe6P8rQPJuCWdbB3apOkjOFN5kcg6qWPIZWQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^3.0.0", - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-syntax-patches-for-csstree": "^1.0.26", - "@csstools/css-tokenizer": "^4.0.0", - "@csstools/media-query-list-parser": "^5.0.0", - "@csstools/selector-resolve-nested": "^4.0.0", - "@csstools/selector-specificity": "^6.0.0", - "balanced-match": "^3.0.1", - "colord": "^2.9.3", - "cosmiconfig": "^9.0.0", - "css-functions-list": "^3.2.3", - "css-tree": "^3.1.0", - "debug": "^4.4.3", - "fast-glob": "^3.3.3", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^11.1.2", - "global-modules": "^2.0.0", - "globby": "^16.1.0", - "globjoin": "^0.1.4", - "html-tags": "^5.1.0", - "ignore": "^7.0.5", - "import-meta-resolve": "^4.2.0", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.37.0", - "mathml-tag-names": "^4.0.0", - "meow": "^14.0.0", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.5.6", - "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.1", - "postcss-value-parser": "^4.2.0", - "string-width": "^8.1.0", - "supports-hyperlinks": "^4.4.0", - "svg-tags": "^1.0.0", - "table": "^6.9.0", - "write-file-atomic": "^7.0.0" - }, - "bin": { - "stylelint": "bin/stylelint.mjs" - }, - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/stylelint-config-html": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-html/-/stylelint-config-html-1.1.0.tgz", - "integrity": "sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12 || >=14" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "postcss-html": "^1.0.0", - "stylelint": ">=14.0.0" - } - }, - "node_modules/stylelint-config-recommended": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz", - "integrity": "sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "stylelint": "^17.0.0" - } - }, - "node_modules/stylelint/node_modules/@csstools/css-calc": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.0.1.tgz", - "integrity": "sha512-bsDKIP6f4ta2DO9t+rAbSSwv4EMESXy5ZIvzQl1afmD6Z1XHkVu9ijcG9QR/qSgQS1dVa+RaQ/MfQ7FIB/Dn1Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/stylelint/node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", - "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/stylelint/node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.0.tgz", - "integrity": "sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.4.0.tgz", - "integrity": "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^5.0.1", - "supports-color": "^10.2.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", - "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/svelte": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.49.1.tgz", - "integrity": "sha512-jj95WnbKbXsXXngYj28a4zx8jeZx50CN/J4r0CEeax2pbfdsETv/J1K8V9Hbu3DCXnpHz5qAikICuxEooi7eNQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "devalue": "^5.6.2", - "esm-env": "^1.2.1", - "esrap": "^2.2.2", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/svelte-check": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.6.tgz", - "integrity": "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": ">=5.0.0" - } - }, - "node_modules/svelte-konva": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/svelte-konva/-/svelte-konva-1.0.1.tgz", - "integrity": "sha512-XfiSF9+J9phRupYGzMfbp46dnqsgZfN3Fwqei4YVAy0/9dr7k/qGtEucOqEcwgL1GEI2rxwSFXH0S5zbZqyMJQ==", - "license": "MIT", - "peerDependencies": { - "konva": "8 - 10", - "svelte": "^5.0.0" - } - }, - "node_modules/svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/table/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tldts": { - "version": "7.0.22", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.22.tgz", - "integrity": "sha512-nqpKFC53CgopKPjT6Wfb6tpIcZXHcI6G37hesvikhx0EmUGPkZrujRyAjgnmp1SHNgpQfKVanZ+KfpANFt2Hxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^7.0.22" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "7.0.22", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.22.tgz", - "integrity": "sha512-KgbTDC5wzlL6j/x6np6wCnDSMUq4kucHNm00KXPbfNzmllCmtmvtykJHfmgdHntwIeupW04y8s1N/43S1PkQDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", - "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^7.0.5" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", - "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicorn-magic": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", - "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin-icons": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-23.0.1.tgz", - "integrity": "sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@iconify/utils": "^3.1.0", - "local-pkg": "^1.1.2", - "obug": "^2.1.1", - "unplugin": "^2.3.11" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@svgr/core": ">=7.0.0", - "@svgx/core": "^1.0.1", - "@vue/compiler-sfc": "^3.0.2", - "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "@svgr/core": { - "optional": true - }, - "@svgx/core": { - "optional": true - }, - "@vue/compiler-sfc": { - "optional": true - }, - "svelte": { - "optional": true - } - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "dev": true, - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", - "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.0.18", - "@vitest/mocker": "4.0.18", - "@vitest/pretty-format": "4.0.18", - "@vitest/runner": "4.0.18", - "@vitest/snapshot": "4.0.18", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^3.10.0", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.18", - "@vitest/browser-preview": "4.0.18", - "@vitest/browser-webdriverio": "4.0.18", - "@vitest/ui": "4.0.18", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/vitest-canvas-mock": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/vitest-canvas-mock/-/vitest-canvas-mock-1.1.3.tgz", - "integrity": "sha512-zlKJR776Qgd+bcACPh0Pq5MG3xWq+CdkACKY/wX4Jyija0BSz8LH3aCCgwFKYFwtm565+050YFEGG9Ki0gE/Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssfontparser": "^1.2.1", - "moo-color": "^1.0.3" - }, - "peerDependencies": { - "vitest": "^3.0.0 || ^4.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/web-animations-js": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.2.tgz", - "integrity": "sha512-TOMFWtQdxzjWp8qx4DAraTWTsdhxVSiWa6NkPFSaPtZ1diKUxTn4yTix73A1euG1WbSOMMPcY51cnjTIHrGtDA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/webidl-conversions": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", - "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/whatwg-mimetype": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", - "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/whatwg-url": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz", - "integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.11.0", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/write-file-atomic": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.0.tgz", - "integrity": "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/zimmerframe": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "license": "MIT" - } - } -} +{ + "name": "twitch-panels", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "twitch-panels", + "version": "0.0.1", + "dependencies": { + "@types/cropperjs": "^1.1.5", + "@types/uuid": "^10.0.0", + "cropperjs": "^2.1.0", + "file-saver": "^2.0.5", + "jszip": "^3.10.1", + "svelte-konva": "^1.0.1", + "uuid": "^13.0.0" + }, + "devDependencies": { + "@eslint/compat": "^2.0.2", + "@eslint/js": "^9.39.2", + "@iconify-json/lucide": "^1.2.89", + "@playwright/test": "^1.58.1", + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.50.1", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/svelte": "^5.3.1", + "@testing-library/user-event": "^14.6.1", + "@types/file-saver": "^2.0.7", + "@types/node": "^20", + "@vitest/coverage-istanbul": "^4.0.18", + "@vitest/ui": "^4.0.18", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.14.0", + "glob": "^13.0.2", + "globals": "^17.3.0", + "jsdom": "^28.0.0", + "konva": "^10.2.0", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.4.1", + "stylelint": "^17.2.0", + "stylelint-config-html": "^1.1.0", + "stylelint-config-recommended": "^18.0.0", + "svelte": "^5.48.2", + "svelte-check": "^4.3.5", + "typescript": "^5.9.3", + "typescript-eslint": "^8.54.0", + "unplugin-icons": "^23.0.1", + "vite": "^7.3.1", + "vitest": "^4.0.18", + "vitest-canvas-mock": "^1.1.3", + "web-animations-js": "^2.3.2" + } + }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", + "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.4" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.7.tgz", + "integrity": "sha512-8CO/UQ4tzDd7ula+/CVimJIVWez99UJlbMyIgk8xOnhAVPKLnBZmUFYVgugS441v2ZqUq5EnSh6B0Ua0liSFAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.7.tgz", + "integrity": "sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.3.3", + "@keyv/bigmap": "^1.3.0", + "hookified": "^1.14.0", + "keyv": "^5.5.5" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.3.4.tgz", + "integrity": "sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.3.0", + "keyv": "^5.6.0" + } + }, + "node_modules/@cropper/element": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/element/-/element-2.1.0.tgz", + "integrity": "sha512-2zELddqHQNmlvkPoiYzE5nxEjPE+C8nXoTPuvV3FvLp3YjBinc7qb73Icg9UXP0o9qC4+h9q96JgGo0AyMO/Ng==", + "license": "MIT", + "dependencies": { + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/@cropper/element-canvas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/element-canvas/-/element-canvas-2.1.0.tgz", + "integrity": "sha512-el+rfJpZxsD2q5XxDBA4fRczcrOqB65Lb7roqXOq8LKufwf4bPWA9C6DjNJJahh/TP94dsLIEy3tSkgRMDv3Aw==", + "license": "MIT", + "dependencies": { + "@cropper/element": "^2.1.0", + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/@cropper/element-crosshair": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/element-crosshair/-/element-crosshair-2.1.0.tgz", + "integrity": "sha512-0V589dAx8uZAfvJwdINLn76gfPQEafPH94ukjJ76uX0FCUovLaAVX+VRD/MDSYn0Mza/xejzmL9Dhd1DfemvmA==", + "license": "MIT", + "dependencies": { + "@cropper/element": "^2.1.0", + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/@cropper/element-grid": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/element-grid/-/element-grid-2.1.0.tgz", + "integrity": "sha512-dEnk0rO+vp553LMvsPYgfrqVFcYXeVFrgFeavBYYEhAXtO40p7kN4rmLYLMMjaN+T/Mx2BATv6kUQpALKy2HLw==", + "license": "MIT", + "dependencies": { + "@cropper/element": "^2.1.0", + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/@cropper/element-handle": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/element-handle/-/element-handle-2.1.0.tgz", + "integrity": "sha512-8BklWA4C/2GGAULupIWleSnGutECvYt3vx9flodqDfZpDEozws4LgLqmmzVuQmVkRVUdLnXdtx28kjgWLtzkHg==", + "license": "MIT", + "dependencies": { + "@cropper/element": "^2.1.0", + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/@cropper/element-image": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/element-image/-/element-image-2.1.0.tgz", + "integrity": "sha512-mXOV8ixJvG0XtTxLebYAKDjEkFbFOQnsF02hXPZk1yQSV0J+LLhN7a2NePrtKnoTsEV19fhhX3UorMoyGGxvzg==", + "license": "MIT", + "dependencies": { + "@cropper/element": "^2.1.0", + "@cropper/element-canvas": "^2.1.0", + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/@cropper/element-selection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/element-selection/-/element-selection-2.1.0.tgz", + "integrity": "sha512-mtFtBl6HIa/s9TWohXw+Z5eJoeYTqylrIcHvS7oVv0uM7IyeRwBW65Q7z+KtLfq/LW+2Sw/XDyvR+VN/DawBPw==", + "license": "MIT", + "dependencies": { + "@cropper/element": "^2.1.0", + "@cropper/element-canvas": "^2.1.0", + "@cropper/element-image": "^2.1.0", + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/@cropper/element-shade": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/element-shade/-/element-shade-2.1.0.tgz", + "integrity": "sha512-zMdyqbb0lc0Vd1oj2Z1miIZvhyZG41OXMHvrNt0hNwblh0dVdrvtw48lnFDgRv+672vt2CNx7Q04GuvCQfPlgg==", + "license": "MIT", + "dependencies": { + "@cropper/element": "^2.1.0", + "@cropper/element-canvas": "^2.1.0", + "@cropper/element-selection": "^2.1.0", + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/@cropper/element-viewer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/element-viewer/-/element-viewer-2.1.0.tgz", + "integrity": "sha512-XnxlQuqHitd1FOFZ6E0yXAF5NYd/LyIvONLLHI9p1rJw747WYKUPxQaSYtFKF7IOizJu/8mMj++Zc1dZ5ZP3YQ==", + "license": "MIT", + "dependencies": { + "@cropper/element": "^2.1.0", + "@cropper/element-canvas": "^2.1.0", + "@cropper/element-image": "^2.1.0", + "@cropper/element-selection": "^2.1.0", + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/@cropper/elements": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/elements/-/elements-2.1.0.tgz", + "integrity": "sha512-qvzlYDn3VQgPPpsCu6Gi1XUO0v3vpXQFSjjxcVijbXeNsl/eiKrN7H9/CEiRgi5vr8kXfd7ZvgYxBjUBbH+y+w==", + "license": "MIT", + "dependencies": { + "@cropper/element": "^2.1.0", + "@cropper/element-canvas": "^2.1.0", + "@cropper/element-crosshair": "^2.1.0", + "@cropper/element-grid": "^2.1.0", + "@cropper/element-handle": "^2.1.0", + "@cropper/element-image": "^2.1.0", + "@cropper/element-selection": "^2.1.0", + "@cropper/element-shade": "^2.1.0", + "@cropper/element-viewer": "^2.1.0" + } + }, + "node_modules/@cropper/utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cropper/utils/-/utils-2.1.0.tgz", + "integrity": "sha512-wLtpZ4/UWgo+fGmG8NBWge8x5ehjfDe9ovleDfLy8kpwFaw43XXOEXQtRL1UNr0u4JZxaeO8FcXcolRWUUrlRQ==", + "license": "MIT" + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", + "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/selector-resolve-nested": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.0.tgz", + "integrity": "sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.1.1" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz", + "integrity": "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.1.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.2.tgz", + "integrity": "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "peerDependencies": { + "eslint": "^8.40 || 9 || 10" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.11.0.tgz", + "integrity": "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@iconify-json/lucide": { + "version": "1.2.89", + "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.89.tgz", + "integrity": "sha512-9rZaJZn8VBls1KZnGaFTnqqZrUkd++XB3vy9WYIMgmHHgLxQMEZXg3V+oJSEeit0kCNr/OfDBmrDwuGl/LZulA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz", + "integrity": "sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz", + "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.0.tgz", + "integrity": "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", + "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.50.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.1.tgz", + "integrity": "sha512-XRHD2i3zC4ukhz2iCQzO4mbsts081PAZnnMAQ7LNpWeYgeBmwMsalf0FGSwhFXBbtr2XViPKnFJBDCckWqrsLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.6.2", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "deepmerge": "^4.3.1", + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "obug": "^2.1.0" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/svelte": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.3.1.tgz", + "integrity": "sha512-8Ez7ZOqW5geRf9PF5rkuopODe5RGy3I9XR+kc7zHh26gBiktLaxTfKmhlGaSHYUOTQE7wFsLMN9xCJVCszw47w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testing-library/dom": "9.x.x || 10.x.x", + "@testing-library/svelte-core": "1.0.0" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", + "vite": "*", + "vitest": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@testing-library/svelte-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/svelte-core/-/svelte-core-1.0.0.tgz", + "integrity": "sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0" + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cropperjs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/cropperjs/-/cropperjs-1.1.5.tgz", + "integrity": "sha512-ofxLqZgZK6qtTjPedXfnph2nodHueejdNLtH/ljaNbM8rGRZg17YdBORJ/xYtKBdsonc7bIYKLGBfTGu/IQtcg==", + "license": "MIT" + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.55.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.55.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitest/coverage-istanbul": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-4.0.18.tgz", + "integrity": "sha512-0OhjP30owEDihYTZGWuq20rNtV1RjjJs1Mv4MaZIKcFBmiLUXX7HJLX4fU7wE+Mrc3lQxI2HKq6WrSXi5FGuCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@istanbuljs/schema": "^0.1.3", + "@jridgewell/gen-mapping": "^0.3.13", + "@jridgewell/trace-mapping": "0.3.31", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-instrument": "^6.0.3", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.0.18" + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.18.tgz", + "integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.0.18" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz", + "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/brace-expansion/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cacheable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.2.tgz", + "integrity": "sha512-w+ZuRNmex9c1TR9RcsxbfTKCjSL0rh1WA5SABbrWprIHeNBdmyQLSYonlDy9gpD+63XT8DgZ/wNh1Smvc9WnJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.0.7", + "@cacheable/utils": "^2.3.3", + "hookified": "^1.15.0", + "keyv": "^5.5.5", + "qified": "^0.6.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001767", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz", + "integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cropperjs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-2.1.0.tgz", + "integrity": "sha512-SsSDqdVRl+mjbIBkGWlk1gCGcc+HzBqCbH5EQ+1tkAFUdxq2KUGukXF1RqhmvXrrdrX7PDwSUkWgXS7E36KvGQ==", + "license": "MIT", + "dependencies": { + "@cropper/elements": "^2.1.0", + "@cropper/utils": "^2.1.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-functions-list": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz", + "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssfontparser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz", + "integrity": "sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devalue": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", + "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", + "license": "MIT" + }, + "node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@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.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.15.0.tgz", + "integrity": "sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.6.1", + "@jridgewell/sourcemap-codec": "^1.5.0", + "esutils": "^2.0.3", + "globals": "^16.0.0", + "known-css-properties": "^0.37.0", + "postcss": "^8.4.49", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^7.0.0", + "semver": "^7.6.3", + "svelte-eslint-parser": "^1.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^8.57.1 || ^9.0.0 || ^10.0.0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-svelte/node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/eslint/node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.2.tgz", + "integrity": "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.2.tgz", + "integrity": "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.20" + } + }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.20.tgz", + "integrity": "sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.3.2", + "flatted": "^3.3.3", + "hookified": "^1.15.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.2.tgz", + "integrity": "sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.1.2", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/globals": { + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", + "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.0.tgz", + "integrity": "sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hashery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.4.0.tgz", + "integrity": "sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.14.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-tags": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-5.1.0.tgz", + "integrity": "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.0.0.tgz", + "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^5.3.7", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "undici": "^7.20.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/konva": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/konva/-/konva-10.2.0.tgz", + "integrity": "sha512-JBoz0Xjbf49UPxCZegZ4WseqOzJ+C4AUDOtJ9eBve5RS5Fcq/u8TdBD5fDl/uPFInpC3a9uycm0sRyZpF4hheg==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/lavrton" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/konva" + }, + { + "type": "github", + "url": "https://github.com/sponsors/lavrton" + } + ], + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mathml-tag-names": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz", + "integrity": "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/meow": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.0.0.tgz", + "integrity": "sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", + "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/moo-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", + "integrity": "sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^1.1.4" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/playwright": { + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", + "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", + "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-html": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.8.1.tgz", + "integrity": "sha512-OLF6P7qctfAWayOhLpcVnTGqVeJzu2W3WpIYelfz2+JV5oGxfkcEvweN9U4XpeqE0P98dcD9ssusGwlF0TK0uQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "htmlparser2": "^8.0.0", + "js-tokens": "^9.0.0", + "postcss": "^8.5.0", + "postcss-safe-parser": "^6.0.0" + }, + "engines": { + "node": "^12 || >=14" + } + }, + "node_modules/postcss-html/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/postcss-html/node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.1.tgz", + "integrity": "sha512-xL49LCloMoZRvSwa6IEdN2GV6cq2IqpYGstYtMT+5wmml1/dClEoI0MZR78MiVPpu6BdQFfN0/y73yO6+br5Pg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qified": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.6.0.tgz", + "integrity": "sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.14.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.2.0.tgz", + "integrity": "sha512-602jhMkRt6P1dSh9kEzbFIaOKY//h4D0E7u/w2WHKxmi5VAjjMqe6P8rQPJuCWdbB3apOkjOFN5kcg6qWPIZWQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.0.26", + "@csstools/css-tokenizer": "^4.0.0", + "@csstools/media-query-list-parser": "^5.0.0", + "@csstools/selector-resolve-nested": "^4.0.0", + "@csstools/selector-specificity": "^6.0.0", + "balanced-match": "^3.0.1", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^11.1.2", + "global-modules": "^2.0.0", + "globby": "^16.1.0", + "globjoin": "^0.1.4", + "html-tags": "^5.1.0", + "ignore": "^7.0.5", + "import-meta-resolve": "^4.2.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", + "mathml-tag-names": "^4.0.0", + "meow": "^14.0.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.1", + "postcss-value-parser": "^4.2.0", + "string-width": "^8.1.0", + "supports-hyperlinks": "^4.4.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^7.0.0" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/stylelint-config-html": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-html/-/stylelint-config-html-1.1.0.tgz", + "integrity": "sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12 || >=14" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "postcss-html": "^1.0.0", + "stylelint": ">=14.0.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz", + "integrity": "sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^17.0.0" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-calc": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.0.1.tgz", + "integrity": "sha512-bsDKIP6f4ta2DO9t+rAbSSwv4EMESXy5ZIvzQl1afmD6Z1XHkVu9ijcG9QR/qSgQS1dVa+RaQ/MfQ7FIB/Dn1Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.0.tgz", + "integrity": "sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.4.0.tgz", + "integrity": "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^5.0.1", + "supports-color": "^10.2.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", + "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/svelte": { + "version": "5.49.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.49.1.tgz", + "integrity": "sha512-jj95WnbKbXsXXngYj28a4zx8jeZx50CN/J4r0CEeax2pbfdsETv/J1K8V9Hbu3DCXnpHz5qAikICuxEooi7eNQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.6.2", + "esm-env": "^1.2.1", + "esrap": "^2.2.2", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.6.tgz", + "integrity": "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.1.tgz", + "integrity": "sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.0", + "postcss": "^8.4.49", + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0", + "pnpm": "10.24.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-konva": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/svelte-konva/-/svelte-konva-1.0.1.tgz", + "integrity": "sha512-XfiSF9+J9phRupYGzMfbp46dnqsgZfN3Fwqei4YVAy0/9dr7k/qGtEucOqEcwgL1GEI2rxwSFXH0S5zbZqyMJQ==", + "license": "MIT", + "peerDependencies": { + "konva": "8 - 10", + "svelte": "^5.0.0" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.22", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.22.tgz", + "integrity": "sha512-nqpKFC53CgopKPjT6Wfb6tpIcZXHcI6G37hesvikhx0EmUGPkZrujRyAjgnmp1SHNgpQfKVanZ+KfpANFt2Hxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.22" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.22", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.22.tgz", + "integrity": "sha512-KgbTDC5wzlL6j/x6np6wCnDSMUq4kucHNm00KXPbfNzmllCmtmvtykJHfmgdHntwIeupW04y8s1N/43S1PkQDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", + "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.55.0", + "@typescript-eslint/parser": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", + "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-icons": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-23.0.1.tgz", + "integrity": "sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/utils": "^3.1.0", + "local-pkg": "^1.1.2", + "obug": "^2.1.1", + "unplugin": "^2.3.11" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@svgr/core": ">=7.0.0", + "@svgx/core": "^1.0.1", + "@vue/compiler-sfc": "^3.0.2", + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@svgr/core": { + "optional": true + }, + "@svgx/core": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "svelte": { + "optional": true + } + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest-canvas-mock": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitest-canvas-mock/-/vitest-canvas-mock-1.1.3.tgz", + "integrity": "sha512-zlKJR776Qgd+bcACPh0Pq5MG3xWq+CdkACKY/wX4Jyija0BSz8LH3aCCgwFKYFwtm565+050YFEGG9Ki0gE/Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssfontparser": "^1.2.1", + "moo-color": "^1.0.3" + }, + "peerDependencies": { + "vitest": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/web-animations-js": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.2.tgz", + "integrity": "sha512-TOMFWtQdxzjWp8qx4DAraTWTsdhxVSiWa6NkPFSaPtZ1diKUxTn4yTix73A1euG1WbSOMMPcY51cnjTIHrGtDA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz", + "integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/write-file-atomic": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.0.tgz", + "integrity": "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index ba9f10e..b545312 100644 --- a/package.json +++ b/package.json @@ -1,61 +1,73 @@ -{ - "name": "twitch-panels", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "prepare": "svelte-kit sync || echo ''", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "lint:css": "stylelint '**/*.{css,svelte}", - "check:css": "node scripts/css-vars.js", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "test": "vitest", - "test:ui": "vitest --ui", - "test:run": "vitest run", - "test:coverage": "vitest run --coverage", - "test:unit": "vitest run tests/unit", - "test:integration": "vitest run tests/integration", - "test:e2e": "playwright test" - }, - "devDependencies": { - "@iconify-json/lucide": "^1.2.89", - "@playwright/test": "^1.58.1", - "@sveltejs/adapter-auto": "^7.0.0", - "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.50.1", - "@sveltejs/vite-plugin-svelte": "^6.2.4", - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/svelte": "^5.3.1", - "@testing-library/user-event": "^14.6.1", - "@types/file-saver": "^2.0.7", - "@types/node": "^25.1.0", - "@vitest/coverage-istanbul": "^4.0.18", - "@vitest/ui": "^4.0.18", - "glob": "^13.0.2", - "jsdom": "^28.0.0", - "konva": "^10.2.0", - "stylelint": "^17.2.0", - "stylelint-config-html": "^1.1.0", - "stylelint-config-recommended": "^18.0.0", - "svelte": "^5.48.2", - "svelte-check": "^4.3.5", - "typescript": "^5.9.3", - "unplugin-icons": "^23.0.1", - "vite": "^7.3.1", - "vitest": "^4.0.18", - "vitest-canvas-mock": "^1.1.3", - "web-animations-js": "^2.3.2" - }, - "dependencies": { - "@types/cropperjs": "^1.1.5", - "@types/uuid": "^10.0.0", - "cropperjs": "^2.1.0", - "file-saver": "^2.0.5", - "jszip": "^3.10.1", - "svelte-konva": "^1.0.1", - "uuid": "^13.0.0" - } -} +{ + "name": "twitch-panels", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "lint:css": "stylelint '**/*.{css,svelte}", + "check:css": "node scripts/css-vars.js", + "check:all": "npm run check && npm run check:css && npm run lint:css && npm run lint && npm run test:run", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "test": "vitest", + "test:ui": "vitest --ui", + "test:run": "vitest run", + "test:coverage": "vitest run --coverage", + "test:unit": "vitest run tests/unit", + "test:integration": "vitest run tests/integration", + "test:e2e": "playwright test", + "lint": "prettier --check . && eslint .", + "format": "prettier --write ." + }, + "devDependencies": { + "@eslint/compat": "^2.0.2", + "@eslint/js": "^9.39.2", + "@iconify-json/lucide": "^1.2.89", + "@playwright/test": "^1.58.1", + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.50.1", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/svelte": "^5.3.1", + "@testing-library/user-event": "^14.6.1", + "@types/file-saver": "^2.0.7", + "@types/node": "^20", + "@vitest/coverage-istanbul": "^4.0.18", + "@vitest/ui": "^4.0.18", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.14.0", + "glob": "^13.0.2", + "globals": "^17.3.0", + "jsdom": "^28.0.0", + "konva": "^10.2.0", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.4.1", + "stylelint": "^17.2.0", + "stylelint-config-html": "^1.1.0", + "stylelint-config-recommended": "^18.0.0", + "svelte": "^5.48.2", + "svelte-check": "^4.3.5", + "typescript": "^5.9.3", + "typescript-eslint": "^8.54.0", + "unplugin-icons": "^23.0.1", + "vite": "^7.3.1", + "vitest": "^4.0.18", + "vitest-canvas-mock": "^1.1.3", + "web-animations-js": "^2.3.2" + }, + "dependencies": { + "@types/cropperjs": "^1.1.5", + "@types/uuid": "^10.0.0", + "cropperjs": "^2.1.0", + "file-saver": "^2.0.5", + "jszip": "^3.10.1", + "svelte-konva": "^1.0.1", + "uuid": "^13.0.0" + } +} diff --git a/plans/FUNCTIONAL_ROADMAP.md b/plans/FUNCTIONAL_ROADMAP.md index 83f6446..cd52b0d 100644 --- a/plans/FUNCTIONAL_ROADMAP.md +++ b/plans/FUNCTIONAL_ROADMAP.md @@ -1,330 +1,330 @@ -# Функциональный план (Product & UX Roadmap) - -## Twitch Panels Creator - -**Анализ версии:** 0.0.1 -**Дата анализа:** 2025-02-09 -**Технологии:** Svelte 5, TypeScript, Konva.js, Cropper.js - ---- - -## 1. Текущие возможности (глазами пользователя) - -### ✅ Реализованный функционал - -#### 1.1 Управление текстами панелей - -**Файлы:** [`src/states/texts.svelte.ts`](src/states/texts.svelte.ts), [`src/components/text/TextManager.svelte`](src/components/text/TextManager.svelte), [`src/components/text/TextConfig.svelte`](src/components/text/TextConfig.svelte) - -- Добавление текстовых панелей через инпут + кнопку/Enter -- Редактирование текста инлайн (двойной клик или прямое редактирование) -- Удаление отдельных текстов -- Глобальные настройки текста (размер, шрифт, цвет, выравнивание, отступы, смещение) -- **Приоритет:** High - это основной рабочий поток - -#### 1.2 Система превью панелей - -**Файлы:** [`src/components/panel/PreviewManager.svelte`](src/components/panel/PreviewManager.svelte), [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte), [`src/components/panel/PreviewAll.svelte`](src/components/panel/PreviewAll.svelte) - -- Просмотр превью текущей панели с настройками текста -- Навигация между панелями (стрелки + индикатор "N / M") -- Просмотр всех панелей одновременно (в скрытом контейнере для экспорта) -- Плавные переходы между панелями (fly transition) -- **Приоритет:** High - критично для UX - -#### 1.3 Экспорт панелей - -**Файлы:** [`src/services/downloadService.ts`](src/services/downloadService.ts) - -- Экспорт отдельной панели в PNG (320x100px) -- Массовый экспорт всех панелей в ZIP-архив -- Использование file-saver и JSZip -- **Приоритет:** High - финальная цель пользователя - -#### 1.4 Система фоновых изображений (частичная) - -**Файлы:** [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts), [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte), [`src/components/image/CropInline.svelte`](src/components/image/CropInline.svelte) - -- Загрузка изображения по URL (только UI кнопки) -- Визуальный интерфейс для обрезки (crop box с handles) -- Настройки яркости и контраста (только UI слайдеры) -- Автозагрузка дефолтного фона при старте -- **Приоритет:** Low - нерабочая система - -#### 1.5 Базовый UI/UX - -**Файлы:** [`src/components/layout/Card.svelte`](src/components/layout/Card.svelte), [`src/components/ui/Button.svelte`](src/components/ui/Button.svelte), [`src/components/ui/RangeSlider.svelte`](src/components/ui/RangeSlider.svelte), [`src/components/ui/ColorPicker.svelte`](src/components/ui/ColorPicker.svelte), [`src/components/ui/SelectFont.svelte`](src/components/ui/SelectFont.svelte), [`src/components/ui/Alignment.svelte`](src/components/ui/Alignment.svelte) - -- Карточная система группировки -- Кнопки 5 типов (primary, secondary, outline, danger, mini) -- Слайдеры с отображением значения -- Цветовой пикер -- Выбор шрифта из 8 системных -- Три кнопки выравнивания -- Темная/светлая тема -- **Приоритет:** Medium - работает, но можно улучшить - ---- - -## 2. Low-hanging fruit (Ближайшие улучшения) - -### 2.1 Завершение системы изображений (High Priority) - -**Проблема:** [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte) имеет UI, но нет логики загрузки/обработки. - -**Требуемые правки:** - -1. **Реализовать загрузку изображений** в [`src/services/imageService.ts`](src/services/imageService.ts): - - Drag & drop поддержка - - Paste из буфера обмена - - Загрузка по URL с валидацией - - Валидация форматов (JPEG, PNG, WebP, GIF) и размера (10MB max) - - **Контракт:** `imageService.uploadImage(source: File | string): Promise` - -2. **Интегрировать cropperjs** в [`src/components/image/CropInline.svelte`](src/components/image/CropInline.svelte): - - Инициализация cropper.js на canvas - - Привязка crop box к данным состояния - - Реализовать drag & drop для перемещения - - Реализовать resize через 8 handles - - **Контракт:** `onCropChange(crop: { left, top, right, bottom })` - -3. **Применить фильтры яркости/контраста**: - - В [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts) добавить `brightness: number`, `contrast: number` - - Применить CSS filters к Konva Image в [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte) - - **Контракт:** `filter: brightness(${brightness}%) contrast(${contrast}%)` - -4. **Сохранить обрезанное изображение**: - - Метод `imageConfigState.applyCrop()` должен обновлять `image` с обрезанными данными - - Использовать canvas для actual cropping - -### 2.2 Валидация и состояния загрузки (Medium Priority) - -**Тексты:** - -- [`src/components/text/TextInput.svelte`](src/components/text/TextInput.svelte): добавить валидацию длины (max 100 chars из [`src/lib/constants.ts`](src/lib/constants.ts):32) -- Показать ошибку при превышении длины -- Добавить счетчик символов "3/100" - -**Изображения:** - -- [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte): показать состояние загрузки (spinner) при загрузке по URL -- Показать ошибку при неудачной загрузке -- Валидация формата/размера до загрузки - -**Экспорт:** - -- [`src/services/downloadService.ts`](src/services/downloadService.ts): показать прогресс-бар при `downloadAll()` -- Отключить кнопки во время экспорта -- Показать toast уведомление об успехе/ошибке - -### 2.3 Пустые состояния (Medium Priority) - -**Списки:** - -- [`src/components/text/TextManager.svelte`](src/components/text/TextManager.svelte): когда `textsState.texts.length === 0`, показать красивый empty state вместо пустого UL -- [`src/components/panel/PreviewManager.svelte`](src/components/panel/PreviewManager.svelte): уже есть empty state, но можно улучшить (иконка + текст + CTA) - -**Изображение:** - -- [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte): когда нет изображения, показать placeholder с инструкцией - -### 2.4 Улучшение доступности (Medium Priority) - -**Файлы для правок:** - -- Все кнопки уже имеют `aria-label` ✅ -- [`src/components/text/TextInput.svelte`](src/components/text/TextInput.svelte): добавить `aria-describedby` для валидации -- [`src/components/text/TextInlineEdit.svelte`](src/components/text/TextInlineEdit.svelte): добавить `aria-label` на delete button (уже есть) -- Добавить `role="region"` и `aria-label` на карточки -- Убедиться, что фокусный порядок логичен (Tab navigation) - ---- - -## 3. Масштабирование (Крупные модули/интеграции) - -### 3.1 Сохранение и загрузка проектов (High Priority) - -**Проблема:** Нет persistence, пользователь теряет данные при перезагрузке. - -**Решение:** - -1. **Создать [`src/services/storageService.ts`](src/services/storageService.ts):** - - `saveProject(project: Project): Promise` → localStorage - - `loadProject(id: string): Project | null` - - `listProjects(): ProjectInfo[]` - - `deleteProject(id: string)` - - **Project тип:** `{ id, name, createdAt, texts, imageConfig, textConfig }` - -2. **Добавить UI для управления проектами:** - - [`src/components/layout/AppHeader.svelte`](src/components/layout/AppHeader.svelte): добавить кнопку "Проекты" → открывает модалку - - [`src/components/project/ProjectManager.svelte`](src/components/project/ProjectManager.svelte) (новый): - - Список сохраненных проектов - - Создание/переименование/удаление - - Экспорт/импорт JSON (для бэкапа) - -3. **Автосохранение:** - - В [`src/routes/+layout.svelte`](src/routes/+layout.svelte): `$effect` на изменениях состояний → debounced save - - Восстановление при загрузке страницы - -**User Retention impact:** Высокий - пользователи смогут возвращаться к своим работам. - -### 3.2 Расширенные настройки текста (Medium Priority) - -**Файлы для модификации:** [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts), [`src/components/text/TextConfig.svelte`](src/components/text/TextConfig.svelte) - -Добавить: - -- **Тень текста:** `textShadow: { offsetX, offsetY, blur, color }` -- **Прозрачность:** `opacity: number` (0-100) -- **Градиент:** `gradient: { type: 'linear' | 'radial', colors: HexColor[], angle? }` (сложнее) -- **Несколько текстовых блоков на панели:** потребует переархитектуры Preview.svelte - -**Приоритет:** Medium - улучшает качество панелей, но требует работы с Konva. - -### 3.3 Расширенные настройки изображений (Medium Priority) - -**Файлы:** [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts), [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte) - -Добавить: - -- **Фильтры:** blur, sepia, saturate, grayscale -- **Наложение:** возможность добавить второй слой изображения -- **Режимы наложения:** multiply, screen, overlay (CSS blend modes) - -**Приоритет:** Medium - улучшает креативность, но сложная реализация. - -### 3.4 Пресеты и шаблоны (Low Priority) - -**Новые файлы:** - -- [`src/services/templateService.ts`](src/services/templateService.ts) -- [`src/components/template/TemplateGallery.svelte`](src/components/template/TemplateGallery.svelte) - -**Функционал:** - -- Предустановленные шаблоны (разные стили текста/изображений) -- Сохранение пользовательских пресетов -- Применение пресета к текущему проекту - -**Приоритет:** Low - nice-to-have, не критично для MVP. - -### 3.5 Интеграция с Twitch API (Future) - -**Возможные интеграции:** - -- Прямая загрузка панелей на Twitch через API -- Синхронизация с существующими панелями -- Планирование публикации - -**Приоритет:** Low - требует OAuth, сложная интеграция. - ---- - -## 4. User Retention (Повторное использование) - -### 4.1 Сохранение проектов (см. 3.1) - HIGH - -### 4.2 Настройки пользователя (Medium Priority) - -**Файлы:** [`src/states/theme.svelte.ts`](src/states/theme.svelte.ts) уже есть, но нет сохранения. - -**Добавить:** - -- Сохранение темы в localStorage (частично есть в [`src/routes/+layout.svelte`](src/routes/+layout.svelte):15) -- Сохранение предпочитаемого шрифта -- Сохранение последних использованных цветов -- **Файл:** [`src/services/preferencesService.ts`](src/services/preferencesService.ts) - -### 4.3 Экспорт в разные форматы (Medium Priority) - -**Текущее состояние:** Только PNG. - -**Добавить:** - -- Экспорт в JPEG (с настройкой качества) -- Экспорт в WebP (современный формат) -- Экспорт в PDF (для печати) -- **Файл:** [`src/services/exportService.ts`](src/services/exportService.ts) (расширить downloadService) - -**Приоритет:** Medium - увеличивает полезность. - -### 4.4 История изменений (Low Priority) - -**Новый файл:** [`src/services/historyService.ts`](src/services/historyService.ts) - -**Функционал:** - -- Undo/Redo для текстов и настроек -- Хранение N последних состояний -- Горячие клавиши Ctrl+Z / Ctrl+Y - -**Приоритет:** Low - удобно, но не обязательно. - -### 4.5 Совместная работа (Future) - -**Сложная интеграция:** - -- Share link с состоянием (закодировать в URL) -- Real-time collaboration (WebSocket) -- Комментарии/ревью - -**Приоритет:** Very Low - далеко от MVP. - ---- - -## 5. Приоритизация по фазам - -### Фаза 1: Завершение базового функционала (2-3 недели) - -**Цель:** Приложение должно работать end-to-end - -1. Полная реализация системы изображений (2.1) - **High** -2. Сохранение проектов (3.1) - **High** -3. Валидация и состояния загрузки (2.2) - **Medium** -4. Пустые состояния (2.3) - **Medium** -5. Настройки пользователя (4.2) - **Medium** - -**Критерий успеха:** Пользователь может создать, сохранить и загрузить проект с изображением и текстом. - -### Фаза 2: Улучшение UX и расширение (3-4 недели) - -1. Расширенные настройки текста (3.2) - **Medium** -2. Расширенные настройки изображений (3.3) - **Medium** -3. Экспорт в разные форматы (4.3) - **Medium** -4. Улучшение доступности (2.4) - **Medium** -5. Пресеты и шаблоны (3.4) - **Low** - -**Критерий успеха:** Пользователь может создавать сложные панели с эффектами и быстро повторять стили. - -### Фаза 3: Продвинутые фичи (4+ недели) - -1. История изменений (4.4) - **Low** -2. Интеграция с Twitch API (3.5) - **Low** -3. Совместная работа (4.5) - **Very Low** - ---- - -## 6. Метрики успеха - -- **Активация:** >70% пользователей создают хотя бы 1 панель -- **Сохранение:** >30% пользователей сохраняют проект -- **Экспорт:** >50% пользователей экспортируют хотя бы 1 панель -- **Время на создание:** <2 минут для набора из 5 панелей -- **Retention week 1:** >40% возвращаются - ---- - -## 7. Риски и ограничения - -1. **Сложность Konva:** Ограниченная кастомизация текста (несколько блоков на панели потребует переписывания Preview.svelte) -2. **Производительность:** Множество Konva stages (konvaAllStages) могут тормозить при 50+ панелях -3. **Браузерные ограничения:** localStorage 5-10MB, может не хватить для изображений -4. **Cropper.js:** Требует доработки для работы с canvas/Konva - ---- - -**Следующие шаги:** - -1. Утвердить функциональный план -2. Перейти к техническому плану (ENGINEERING_IMPROVEMENTS.md) -3. Начать реализацию Фазы 1 (начиная с imageService) +# Функциональный план (Product & UX Roadmap) + +## Twitch Panels Creator + +**Анализ версии:** 0.0.1 +**Дата анализа:** 2025-02-09 +**Технологии:** Svelte 5, TypeScript, Konva.js, Cropper.js + +--- + +## 1. Текущие возможности (глазами пользователя) + +### ✅ Реализованный функционал + +#### 1.1 Управление текстами панелей + +**Файлы:** [`src/states/texts.svelte.ts`](src/states/texts.svelte.ts), [`src/components/text/TextManager.svelte`](src/components/text/TextManager.svelte), [`src/components/text/TextConfig.svelte`](src/components/text/TextConfig.svelte) + +- Добавление текстовых панелей через инпут + кнопку/Enter +- Редактирование текста инлайн (двойной клик или прямое редактирование) +- Удаление отдельных текстов +- Глобальные настройки текста (размер, шрифт, цвет, выравнивание, отступы, смещение) +- **Приоритет:** High - это основной рабочий поток + +#### 1.2 Система превью панелей + +**Файлы:** [`src/components/panel/PreviewManager.svelte`](src/components/panel/PreviewManager.svelte), [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte), [`src/components/panel/PreviewAll.svelte`](src/components/panel/PreviewAll.svelte) + +- Просмотр превью текущей панели с настройками текста +- Навигация между панелями (стрелки + индикатор "N / M") +- Просмотр всех панелей одновременно (в скрытом контейнере для экспорта) +- Плавные переходы между панелями (fly transition) +- **Приоритет:** High - критично для UX + +#### 1.3 Экспорт панелей + +**Файлы:** [`src/services/downloadService.ts`](src/services/downloadService.ts) + +- Экспорт отдельной панели в PNG (320x100px) +- Массовый экспорт всех панелей в ZIP-архив +- Использование file-saver и JSZip +- **Приоритет:** High - финальная цель пользователя + +#### 1.4 Система фоновых изображений (частичная) + +**Файлы:** [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts), [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte), [`src/components/image/CropInline.svelte`](src/components/image/CropInline.svelte) + +- Загрузка изображения по URL (только UI кнопки) +- Визуальный интерфейс для обрезки (crop box с handles) +- Настройки яркости и контраста (только UI слайдеры) +- Автозагрузка дефолтного фона при старте +- **Приоритет:** Low - нерабочая система + +#### 1.5 Базовый UI/UX + +**Файлы:** [`src/components/layout/Card.svelte`](src/components/layout/Card.svelte), [`src/components/ui/Button.svelte`](src/components/ui/Button.svelte), [`src/components/ui/RangeSlider.svelte`](src/components/ui/RangeSlider.svelte), [`src/components/ui/ColorPicker.svelte`](src/components/ui/ColorPicker.svelte), [`src/components/ui/SelectFont.svelte`](src/components/ui/SelectFont.svelte), [`src/components/ui/Alignment.svelte`](src/components/ui/Alignment.svelte) + +- Карточная система группировки +- Кнопки 5 типов (primary, secondary, outline, danger, mini) +- Слайдеры с отображением значения +- Цветовой пикер +- Выбор шрифта из 8 системных +- Три кнопки выравнивания +- Темная/светлая тема +- **Приоритет:** Medium - работает, но можно улучшить + +--- + +## 2. Low-hanging fruit (Ближайшие улучшения) + +### 2.1 Завершение системы изображений (High Priority) + +**Проблема:** [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte) имеет UI, но нет логики загрузки/обработки. + +**Требуемые правки:** + +1. **Реализовать загрузку изображений** в [`src/services/imageService.ts`](src/services/imageService.ts): + - Drag & drop поддержка + - Paste из буфера обмена + - Загрузка по URL с валидацией + - Валидация форматов (JPEG, PNG, WebP, GIF) и размера (10MB max) + - **Контракт:** `imageService.uploadImage(source: File | string): Promise` + +2. **Интегрировать cropperjs** в [`src/components/image/CropInline.svelte`](src/components/image/CropInline.svelte): + - Инициализация cropper.js на canvas + - Привязка crop box к данным состояния + - Реализовать drag & drop для перемещения + - Реализовать resize через 8 handles + - **Контракт:** `onCropChange(crop: { left, top, right, bottom })` + +3. **Применить фильтры яркости/контраста**: + - В [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts) добавить `brightness: number`, `contrast: number` + - Применить CSS filters к Konva Image в [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte) + - **Контракт:** `filter: brightness(${brightness}%) contrast(${contrast}%)` + +4. **Сохранить обрезанное изображение**: + - Метод `imageConfigState.applyCrop()` должен обновлять `image` с обрезанными данными + - Использовать canvas для actual cropping + +### 2.2 Валидация и состояния загрузки (Medium Priority) + +**Тексты:** + +- [`src/components/text/TextInput.svelte`](src/components/text/TextInput.svelte): добавить валидацию длины (max 100 chars из [`src/lib/constants.ts`](src/lib/constants.ts):32) +- Показать ошибку при превышении длины +- Добавить счетчик символов "3/100" + +**Изображения:** + +- [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte): показать состояние загрузки (spinner) при загрузке по URL +- Показать ошибку при неудачной загрузке +- Валидация формата/размера до загрузки + +**Экспорт:** + +- [`src/services/downloadService.ts`](src/services/downloadService.ts): показать прогресс-бар при `downloadAll()` +- Отключить кнопки во время экспорта +- Показать toast уведомление об успехе/ошибке + +### 2.3 Пустые состояния (Medium Priority) + +**Списки:** + +- [`src/components/text/TextManager.svelte`](src/components/text/TextManager.svelte): когда `textsState.texts.length === 0`, показать красивый empty state вместо пустого UL +- [`src/components/panel/PreviewManager.svelte`](src/components/panel/PreviewManager.svelte): уже есть empty state, но можно улучшить (иконка + текст + CTA) + +**Изображение:** + +- [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte): когда нет изображения, показать placeholder с инструкцией + +### 2.4 Улучшение доступности (Medium Priority) + +**Файлы для правок:** + +- Все кнопки уже имеют `aria-label` ✅ +- [`src/components/text/TextInput.svelte`](src/components/text/TextInput.svelte): добавить `aria-describedby` для валидации +- [`src/components/text/TextInlineEdit.svelte`](src/components/text/TextInlineEdit.svelte): добавить `aria-label` на delete button (уже есть) +- Добавить `role="region"` и `aria-label` на карточки +- Убедиться, что фокусный порядок логичен (Tab navigation) + +--- + +## 3. Масштабирование (Крупные модули/интеграции) + +### 3.1 Сохранение и загрузка проектов (High Priority) + +**Проблема:** Нет persistence, пользователь теряет данные при перезагрузке. + +**Решение:** + +1. **Создать [`src/services/storageService.ts`](src/services/storageService.ts):** + - `saveProject(project: Project): Promise` → localStorage + - `loadProject(id: string): Project | null` + - `listProjects(): ProjectInfo[]` + - `deleteProject(id: string)` + - **Project тип:** `{ id, name, createdAt, texts, imageConfig, textConfig }` + +2. **Добавить UI для управления проектами:** + - [`src/components/layout/AppHeader.svelte`](src/components/layout/AppHeader.svelte): добавить кнопку "Проекты" → открывает модалку + - [`src/components/project/ProjectManager.svelte`](src/components/project/ProjectManager.svelte) (новый): + - Список сохраненных проектов + - Создание/переименование/удаление + - Экспорт/импорт JSON (для бэкапа) + +3. **Автосохранение:** + - В [`src/routes/+layout.svelte`](src/routes/+layout.svelte): `$effect` на изменениях состояний → debounced save + - Восстановление при загрузке страницы + +**User Retention impact:** Высокий - пользователи смогут возвращаться к своим работам. + +### 3.2 Расширенные настройки текста (Medium Priority) + +**Файлы для модификации:** [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts), [`src/components/text/TextConfig.svelte`](src/components/text/TextConfig.svelte) + +Добавить: + +- **Тень текста:** `textShadow: { offsetX, offsetY, blur, color }` +- **Прозрачность:** `opacity: number` (0-100) +- **Градиент:** `gradient: { type: 'linear' | 'radial', colors: HexColor[], angle? }` (сложнее) +- **Несколько текстовых блоков на панели:** потребует переархитектуры Preview.svelte + +**Приоритет:** Medium - улучшает качество панелей, но требует работы с Konva. + +### 3.3 Расширенные настройки изображений (Medium Priority) + +**Файлы:** [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts), [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte) + +Добавить: + +- **Фильтры:** blur, sepia, saturate, grayscale +- **Наложение:** возможность добавить второй слой изображения +- **Режимы наложения:** multiply, screen, overlay (CSS blend modes) + +**Приоритет:** Medium - улучшает креативность, но сложная реализация. + +### 3.4 Пресеты и шаблоны (Low Priority) + +**Новые файлы:** + +- [`src/services/templateService.ts`](src/services/templateService.ts) +- [`src/components/template/TemplateGallery.svelte`](src/components/template/TemplateGallery.svelte) + +**Функционал:** + +- Предустановленные шаблоны (разные стили текста/изображений) +- Сохранение пользовательских пресетов +- Применение пресета к текущему проекту + +**Приоритет:** Low - nice-to-have, не критично для MVP. + +### 3.5 Интеграция с Twitch API (Future) + +**Возможные интеграции:** + +- Прямая загрузка панелей на Twitch через API +- Синхронизация с существующими панелями +- Планирование публикации + +**Приоритет:** Low - требует OAuth, сложная интеграция. + +--- + +## 4. User Retention (Повторное использование) + +### 4.1 Сохранение проектов (см. 3.1) - HIGH + +### 4.2 Настройки пользователя (Medium Priority) + +**Файлы:** [`src/states/theme.svelte.ts`](src/states/theme.svelte.ts) уже есть, но нет сохранения. + +**Добавить:** + +- Сохранение темы в localStorage (частично есть в [`src/routes/+layout.svelte`](src/routes/+layout.svelte):15) +- Сохранение предпочитаемого шрифта +- Сохранение последних использованных цветов +- **Файл:** [`src/services/preferencesService.ts`](src/services/preferencesService.ts) + +### 4.3 Экспорт в разные форматы (Medium Priority) + +**Текущее состояние:** Только PNG. + +**Добавить:** + +- Экспорт в JPEG (с настройкой качества) +- Экспорт в WebP (современный формат) +- Экспорт в PDF (для печати) +- **Файл:** [`src/services/exportService.ts`](src/services/exportService.ts) (расширить downloadService) + +**Приоритет:** Medium - увеличивает полезность. + +### 4.4 История изменений (Low Priority) + +**Новый файл:** [`src/services/historyService.ts`](src/services/historyService.ts) + +**Функционал:** + +- Undo/Redo для текстов и настроек +- Хранение N последних состояний +- Горячие клавиши Ctrl+Z / Ctrl+Y + +**Приоритет:** Low - удобно, но не обязательно. + +### 4.5 Совместная работа (Future) + +**Сложная интеграция:** + +- Share link с состоянием (закодировать в URL) +- Real-time collaboration (WebSocket) +- Комментарии/ревью + +**Приоритет:** Very Low - далеко от MVP. + +--- + +## 5. Приоритизация по фазам + +### Фаза 1: Завершение базового функционала (2-3 недели) + +**Цель:** Приложение должно работать end-to-end + +1. Полная реализация системы изображений (2.1) - **High** +2. Сохранение проектов (3.1) - **High** +3. Валидация и состояния загрузки (2.2) - **Medium** +4. Пустые состояния (2.3) - **Medium** +5. Настройки пользователя (4.2) - **Medium** + +**Критерий успеха:** Пользователь может создать, сохранить и загрузить проект с изображением и текстом. + +### Фаза 2: Улучшение UX и расширение (3-4 недели) + +1. Расширенные настройки текста (3.2) - **Medium** +2. Расширенные настройки изображений (3.3) - **Medium** +3. Экспорт в разные форматы (4.3) - **Medium** +4. Улучшение доступности (2.4) - **Medium** +5. Пресеты и шаблоны (3.4) - **Low** + +**Критерий успеха:** Пользователь может создавать сложные панели с эффектами и быстро повторять стили. + +### Фаза 3: Продвинутые фичи (4+ недели) + +1. История изменений (4.4) - **Low** +2. Интеграция с Twitch API (3.5) - **Low** +3. Совместная работа (4.5) - **Very Low** + +--- + +## 6. Метрики успеха + +- **Активация:** >70% пользователей создают хотя бы 1 панель +- **Сохранение:** >30% пользователей сохраняют проект +- **Экспорт:** >50% пользователей экспортируют хотя бы 1 панель +- **Время на создание:** <2 минут для набора из 5 панелей +- **Retention week 1:** >40% возвращаются + +--- + +## 7. Риски и ограничения + +1. **Сложность Konva:** Ограниченная кастомизация текста (несколько блоков на панели потребует переписывания Preview.svelte) +2. **Производительность:** Множество Konva stages (konvaAllStages) могут тормозить при 50+ панелях +3. **Браузерные ограничения:** localStorage 5-10MB, может не хватить для изображений +4. **Cropper.js:** Требует доработки для работы с canvas/Konva + +--- + +**Следующие шаги:** + +1. Утвердить функциональный план +2. Перейти к техническому плану (ENGINEERING_IMPROVEMENTS.md) +3. Начать реализацию Фазы 1 (начиная с imageService) diff --git a/plans/TECHNICAL_ROADMAP.md b/plans/TECHNICAL_ROADMAP.md index 3bdcae4..8d765ee 100644 --- a/plans/TECHNICAL_ROADMAP.md +++ b/plans/TECHNICAL_ROADMAP.md @@ -1,1145 +1,1145 @@ -# Технический план (Engineering & Architecture Roadmap) - -## Twitch Panels Creator - -**Анализ версии:** 0.0.1 -**Дата анализа:** 2025-02-09 -**Технологии:** Svelte 5, TypeScript, Konva.js, Vitest - ---- - -## 1. Аудит текущей архитектуры - -### 1.1 State Management - КРИТИЧЕСКИЕ ПРОБЛЕМЫ - -**Проблема:** Несогласованные паттерны управления состоянием. - -#### Паттерн A: Фабрика функций (✅ Хорошо) - -**Файлы:** [`src/states/texts.svelte.ts`](src/states/texts.svelte.ts), [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts), [`src/states/theme.svelte.ts`](src/states/theme.svelte.ts), [`src/states/konvaStage.svelte.ts`](src/states/konvaStage.svelte.ts) - -```typescript -// Пример: texts.svelte.ts -function createState() { - let texts = $state(defaultTexts); - let nextId = $state(defaultTexts.length); - - return { - get texts() { return texts; }, - addText(text: string) { ... }, - removeText(id: number) { ... }, - }; -} -export const textsState = createState(); -``` - -**Преимущества:** - -- Инкапсуляция приватных переменных -- Геттеры/сеттеры для валидации -- Легко тестировать (можно создавать инстансы) - -**Недостатки:** - -- Избыточный код геттеров/сеттеров (см. textConfig.svelte.ts:24-61) - -#### Паттерн B: Прямой $state (❌ ПЛОХО) - -**Файлы:** [`src/states/konvaAllStages.svelte.ts`](src/states/konvaAllStages.svelte.ts), [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts) - -```typescript -// ПЛОХО - глобальный массив без инкапсуляции -export const konvaAllStagesState: Array = $state([]); - -// ПЛОХО - класс с $state полями, но без фабрики -export class ImageConfigState { - image = $state(undefined); - imageLink = $state(""); - // ... -} -export const imageConfigState = new ImageConfigState(); -``` - -**Проблемы:** - -- `konvaAllStagesState` - глобальный изменяемый массив, нет контроля над операциями -- `ImageConfigState` - класс с $state, но не фабрика, сложно тестировать -- Нет единого подхода - -#### Паттерн C: Смешанный (⚠️ НЕПРАВИЛЬНО) - -**Файлы:** [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts) - класс с $state, но с приватными полями и методами - -**Рекомендация:** Привести все состояния к **Фабрике функций** (Pattern A). - ---- - -### 1.2 Сервисный слой - ОТСУТСТВУЕТ - -**Текущее состояние:** - -- ✅ [`src/services/downloadService.ts`](src/services/downloadService.ts) - хороший пример -- ❌ `imageService.ts` - отсутствует (логика загрузки размазана) -- ❌ `storageService.ts` - отсутствует (localStorage в +layout.svelte:15) - -**Проблема:** Бизнес-логика смешана с UI-компонентами. - -**Пример:** [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte) - UI без логики, кнопки не работают. - -**Рекомендация:** Создать сервисы для всех доменных областей. - ---- - -### 1.3 Компонентная архитектура - ОЦЕНКА ВСЕХ ГРУПП - -#### Группа 1: layout/ (Базовые layout-компоненты) - -**Файлы:** Card.svelte, AppHeader.svelte, PanelBar.svelte, TextBar.svelte, SettingsGrid.svelte, SettingsRow.svelte, InputGroup.svelte - -**Оценка:** ✅ **Хорошо** - -- Четкое разделение: Card — контейнер, SettingsGrid/Row — формы, PanelBar/TextBar — компоновка секций -- InputGroup.svelte простой flex wrapper, выполняет свою функцию -- AppHeader.svelte содержит логику переключения темы, но это уместно (header — привязка к UI) -- Нет дублирования, каждый компонент имеет уникальную ответственность -- **Рекомендаций:** Нет - ---- - -#### Группа 2: panel/ (Превью панелей) - -**Файлы:** Preview.svelte, PreviewAll.svelte, PreviewControls.svelte, PreviewManager.svelte - -**Оценка:** ✅ **Хорошо** - -- Preview — чистый компонент, принимает `text` и `stage` как props, рендерит Konva Stage + Layer + Image + Text -- PreviewAll — off-screen рендеринг всех stages (необходимость для экспорта) -- PreviewControls — навигация (prev/next), чистая логика -- PreviewManager — оркестратор: управляет current index, direction, вызывает downloadService -- **Вывод:** Хорошее разделение, каждая панель имеет свою зону ответственности - -**Потенциальная проблема:** - -- PreviewManager.svelte:22-30 содержит $effect для синхронизации current с textsState.texts.length — это бизнес-логика, но в данном контексте допустимо -- **Рекомендация:** Если логика усложнится, вынести в отдельный хук/сервис - ---- - -#### Группа 3: text/ (Управление текстами) - -**Файлы:** TextManager.svelte, TextConfig.svelte, TextInput.svelte, TextInlineEdit.svelte - -**Оценка:** ✅ **Хорошо** - -- TextManager — управление списком текстов (add/delete), вызывает textsState -- TextConfig — UI для глобальных настроек текста, bind:value к textConfigState -- TextInput — простой input с обработкой Enter -- TextInlineEdit — inline-редактирование с кнопкой удаления -- **Вывод:** Чистые, сфокусированные компоненты, нет дублирования - -**Замечание:** - -- TextInlineEdit.svelte:17 — `` без валидации maxLength (есть в constants.ts:32) -- **Рекомендация:** Добавить `maxlength={TYPOGRAPHY.MAX_TEXT_LENGTH}` и визуальный счетчик - ---- - -#### Группа 4: image/ (Работа с изображениями) - -**Файлы:** ImageManager.svelte, CropInline.svelte - -**Оценка:** ⚠️ **Частично реализовано (нефункционально)** - -- ImageManager.svelte — UI есть, но кнопки не работают (Upload, Edit, Reset) -- CropInline.svelte — визуальный crop box с handles, но нет интеграции с cropperjs и imageConfigState -- Настройки яркости/контраста — только UI, нет логики применения -- **Вывод:** Компоненты выглядят готовыми, но функциональность отсутствует - -**Рекомендации (High Priority):** - -- Реализовать imageService.ts и интегрировать в ImageManager -- Добавить логику crop в CropInline (drag, resize, update imageConfigState) -- Применить фильтры яркости/контраста к Konva Image в Preview.svelte - ---- - -#### Группа 5: ui/ (Переиспользуемые UI-компоненты) - -**Файлы:** Button.svelte, RangeSlider.svelte, ColorPicker.svelte, SelectFont.svelte, Alignment.svelte, Badge.svelte - -**Оценка:** ✅ **Хорошо** - -- Button — 5 типов (primary, secondary, outline, danger, mini), хороший API -- RangeSlider — простой wrapper с отображением значения -- ColorPicker — input type="color" + отображение hex -- SelectFont — hardcoded список шрифтов (можно улучшить, вынести в constants) -- Alignment — 3 кнопки выравнивания, bind:align -- Badge — простой компонент для счетчиков -- **Вывод:** Все компоненты маленькие, сфокусированные, легко переиспользовать - -**Рекомендации (Low Priority):** - -- SelectFont.svelte:9-18 — шрифты hardcoded, лучше взять из TYPOGRAPHY.FONT_FAMILIES (constants.ts:15-24) -- Button.svelte — добавить sizes (sm, md, lg) для гибкости -- RangeSlider.svelte — добавить label и unit props для лучшего UX - ---- - -**Общий вывод по компонентной архитектуре:** - -| Группа | Оценка | Приоритет исправления | -| ------ | ----------- | ------------------------------ | -| layout | ✅ Хорошо | Нет | -| panel | ✅ Хорошо | Нет | -| text | ✅ Хорошо | Low (валидация) | -| image | ⚠️ Частично | **High** (доделать функционал) | -| ui | ✅ Хорошо | Low (улучшения) | - -**Критическая проблема:** Только группа `image/` требует срочной доработки. Остальные группы следуют best practices и не нуждаются в рефакторинге. - ---- - -### 1.4 Типизация - УДОВЛЕТВОРИТЕЛЬНО - -**Хорошее:** - -- TypeScript везде -- Интерфейсы для Props компонентов -- Типы для сервисов (DownloadItem, DownloadResult) - -**Проблемы:** - -- [`src/lib/types.ts`](src/lib/types.ts) содержит только `HexColor` - слишком мало -- Нет типов для Project, Template, ImageConfig (есть в states, но не экспортированы) -- `any` в тестах (см. [`tests/unit/services/downloadService.test.ts`](tests/unit/services/downloadService.test.ts):27) - -**Рекомендация:** - -- Создать `src/lib/types/index.ts` с экспортом всех доменных типов -- Убрать `any` из тестов, создать моки с правильными типами - ---- - -### 1.5 Обработка ошибок - ХОРОШО - -**Файлы:** [`src/lib/error.types.ts`](src/lib/error.types.ts), [`src/lib/utils/errorUtils.ts`](src/lib/utils/errorUtils.ts) - -**Преимущества:** - -- Иерархия ошибок (AppError → ImageError, TextError, CanvasError, StorageError) -- Утилиты для форматирования и логирования -- Используется в downloadService - -**Недостатки:** - -- Нет обработки ошибок в UI (нет toast/notification системы) -- Ошибки только в консоль, пользователь не видит - -**Рекомендация:** Добавить notification system (например, `src/components/ui/Notification.svelte`). - ---- - -### 1.6 Производительность - РИСКИ - -**Критические точки:** - -1. **Konva stages:** - - [`src/states/konvaAllStages.svelte.ts`](src/states/konvaAllStages.svelte.ts) хранит ВСЕ stages в памяти - - [`src/components/panel/PreviewAll.svelte`](src/components/panel/PreviewAll.svelte) рендерит ВСЕ панели одновременно - - **Проблема:** При 50+ панелях будет лагать - -2. **Нет ленивой загрузки:** - - Все компоненты загружаются сразу - - Изображения загружаются без lazy loading - -3. **Нет debouncing:** - - [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts) - каждый drag слайдера вызывает перерисовку Konva - - Нет оптимизации частых обновлений - -**Рекомендация:** - -- Виртуализация списка панелей (virtual scrolling) -- Debounce для слайдеров (100-150ms) -- Ленивая загрузка изображений - ---- - -## 2. Укрепление базы (Рефакторинг) - -### 2.1 Привести все состояния к фабричному паттерну (High Priority) - -**Цель:** Единообразная структура для всех stores. - -**Текущие состояния:** - -| Файл | Текущий паттерн | Целевой паттерн | -| ------------------------ | -------------------- | ------------------------------- | -| texts.svelte.ts | ✅ Фабрика | Оставить | -| textConfig.svelte.ts | ✅ Фабрика | Упростить геттеры/сеттеры | -| theme.svelte.ts | ✅ Фабрика | Оставить | -| konvaStage.svelte.ts | ✅ Фабрика | Оставить | -| konvaAllStages.svelte.ts | ❌ Глобальный $state | Переделать в фабрику с методами | -| imageConfig.svelte.ts | ❌ Класс с $state | Переделать в фабрику | - -**Пример рефакторинга konvaAllStages:** - -```typescript -// BEFORE (src/states/konvaAllStages.svelte.ts) -export const konvaAllStagesState: Array = $state([]); - -// AFTER -function createState() { - let stages: Array = $state([]); - - return { - get stages() { - return stages; - }, - addStage(stage: Stage) { - stages.push(stage); - }, - removeStage(stage: Stage) { - stages = stages.filter((s) => s !== stage); - }, - clear() { - stages = []; - }, - }; -} -export const konvaAllStagesState = createState(); -``` - -**Задача:** Рефакторинг всех состояний к единому паттерну. - ---- - -### 2.2 Создать сервисный слой (High Priority) - -**Требуемые сервисы:** - -#### 2.2.1 imageService.ts - -**Ответственность:** Загрузка, обработка, валидация изображений. - -**API:** - -```typescript -interface ImageService { - // Загрузка - uploadFromFile(file: File): Promise; - uploadFromURL(url: string): Promise; - uploadFromPaste(clipboardData: DataTransfer): Promise; - - // Обработка - crop(image: ImageData, crop: CropRect): ImageData; - applyFilters(image: ImageData, filters: ImageFilters): ImageData; - - // Валидация - validateFormat(file: File): boolean; - validateSize(file: File): boolean; - - // Кэш - clearCache(): void; -} -``` - -**Реализация:** - -- Использовать Canvas API для обрезки и фильтров -- Кэшировать загруженные изображения (Map) -- Обработка ошибок через ImageError - -#### 2.2.2 Упрощенный подход: textsState как основной store - -**Решение:** Вместо panelService оставить `textsState` как основной store для панелей. - -**Архитектура:** - -``` -textsState (массив текстов) + textConfigState (глобальные настройки) + imageConfigState (глобальное изображение) - ↓ -PreviewManager (навигация по textsState) - ↓ -Preview (рендерит панель на основе text + textConfig + imageConfig) - ↓ -downloadService (экспорт) -``` - -**Преимущества:** - -- Нет лишних абстракций -- Прямая реактивность через Svelte 5 runes -- Простая debugging -- Минимальный порог входа - -**Когда добавить panelService:** Только если появятся персональные настройки на панель (не глобальные), или сложная бизнес-логика (валидация, bulk-операции, undo/redo). - -**Текущая логика в компонентах:** - -- `TextManager` → `textsState.addText()` / `removeText()` -- `PreviewManager` → читает `textsState.texts` для навигации -- `Preview` → читает `textsState.texts[current]` + `textConfigState` + `imageConfigState` - -Это **достаточно** для MVP. - -#### 2.2.3 storageService.ts - -**Ответственность:** Сохранение/загрузка проектов. - -**API:** - -```typescript -interface Project { - id: string; - name: string; - createdAt: Date; - updatedAt: Date; - panels: Panel[]; - globalConfig: { - textConfig: TextConfig; - imageConfig: ImageConfig | null; - }; -} - -interface StorageService { - saveProject(project: Project): Promise; - loadProject(id: string): Project | null; - listProjects(): ProjectInfo[]; - deleteProject(id: string): void; - autoSave(project: Project): void; // debounced -} -``` - -**Реализация:** - -- localStorage с ключом `twitch-panels-projects` -- JSON сериализация -- Обработка квот localStorage (5-10MB) -- Восстановление при старте - -#### 2.2.4 exportService.ts (расширить downloadService) - -**Ответственность:** Экспорт в разные форматы. - -**API:** - -```typescript -interface ExportService { - exportPanel(panel: Panel, format: "png" | "jpeg" | "webp", quality?: number): Promise; - exportAll(panels: Panel[], format: "png" | "jpeg" | "webp", quality?: number): Promise; - exportPDF(panels: Panel[]): Promise; -} -``` - -**Реализация:** - -- Использовать canvas.toBlob с качеством -- PDF через библиотеку (pdf-lib или jsPDF) - ---- - -### 2.3 Вынести общие UI-компоненты в библиотеку (Medium Priority) - -**Проблема:** Компоненты в `src/components/ui/` и `src/components/layout/` используются везде, но: - -- Нет единого стиля (разные отступы, размеры) -- Нет документации -- Сложно переиспользовать в других проектах - -**Решение:** - -1. Создать `src/lib/components/` (или отдельный пакет) -2. Документировать API каждого компонента -3. Стандартизировать пропсы (size, variant, color scheme) -4. Добавить темы (dark/light уже есть, но нужно формализовать) - -**Компоненты для выноса:** - -- Button (уже хорош, но добавить sizes: sm, md, lg) -- RangeSlider (добавить label, unit) -- ColorPicker (добавить preset colors) -- Card (упростить API, убрать snippets) - ---- - -### 2.4 Оптимизировать работу с Konva (High Priority) - -**Проблемы:** - -1. **Утечка памяти:** - - [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts):28-33 есть cleanup, но вызывается только при новой загрузке - - Stages не уничтожаются при удалении панелей - - konvaAllStages растет бесконечно - -2. **Нет управления жизненным циклом:** - - Stages создаются в Preview.svelte, но не уничтожаются - - Нет вызова `stage.destroy()` при unmount - -**Решение:** - -1. **Добавить destroy в Preview.svelte:** - -```typescript -import { onDestroy } from "svelte"; -onDestroy(() => { - if (stage?.node) { - stage.node.destroy(); - } -}); -``` - -2. **Очищать konvaAllStages при удалении панелей:** - - В `textsState.removeText()` → удалять соответствующий stage из konvaAllStagesState - - Вызывать `stage.destroy()` - -3. **Лимитировать количество stages:** - - Виртуализация: рендерить только видимые панели - - Или: рендерить все, но в hidden container (уже есть PreviewAll), но stages все равно в памяти - -**Рекомендация:** Реализовать virtual scrolling для PreviewAll. - ---- - -## 3. Стратегия тестирования - -### 3.1 Текущее покрытие - -**Анализ тестов:** - -- ✅ Unit тесты для states (texts, imageConfig, konvaStage, konvaAllStages, textConfig, theme) -- ✅ Unit тесты для компонентов (Preview, PreviewAll, PreviewControls, PreviewManager, TextConfig, TextInlineEdit, TextInput, TextManager, CropInline, ImageManager, Card, InputGroup, SettingsGrid, SettingsRow, TextBar, AppHeader, Alignment, Badge, Button, ColorPicker, RangeSlider, SelectFont) -- ✅ Unit тесты для сервисов (downloadService) -- ✅ Unit тесты для утилит (errorUtils) -- ✅ Unit тесты для констант и типов - -**Проблемы:** - -- ❌ Нет интеграционных тестов (связь компонентов + состояния) -- ❌ Нет E2E тестов (полные user flows) -- ❌ Нет тестов для imageService (его нет) -- ❌ Нет тестов для storageService (его нет) -- ❌ Нет тестов для контрактов между компонентами - -**Оценка покрытия:** ~40-50% (только unit, нет интеграций) - ---- - -### 3.2 Оптимальный баланс тестов - -**Целевое распределение:** - -- **Unit:** 40% - изолированные компоненты, утилиты, состояния -- **Integration:** 40% - взаимодействие компонентов, сервисов, состояний -- **E2E:** 20% - полные user flows (создание проекта → экспорт) - ---- - -### 3.3 Критические сценарии для тестирования - -#### HIGH PRIORITY (Интеграционные тесты) - -**1. Текстовый flow:** - -``` -TextManager → textsState → PreviewManager → Preview → downloadService -``` - -**Тест:** [`tests/integration/text-flow.test.ts`](tests/integration/text-flow.test.ts) - -```typescript -it("should create panel with text and export it", async () => { - // 1. Добавить текст через TextManager - // 2. Проверить textsState.texts - // 3. Проверить PreviewManager отображает текст - // 4. Нажать "Скачать" - // 5. Проверить downloadService.downloadPanel вызван с правильным именем -}); -``` - -**2. Изображение flow:** - -``` -ImageManager → imageService → imageConfigState → Preview → export -``` - -**Тест:** [`tests/integration/image-flow.test.ts`](tests/integration/image-flow.test.ts) - -```typescript -it("should upload image, crop it, and apply to panel", async () => { - // 1. Загрузить изображение (mock file) - // 2. Проверить imageConfigState.image - // 3. Установить crop values - // 4. Проверить Preview отображает обрезанное изображение -}); -``` - -**3. Навигация flow:** - -``` -textsState → PreviewControls → PreviewManager → Preview -``` - -**Тест:** [`tests/integration/navigation-flow.test.ts`](tests/integration/navigation-flow.test.ts) - -```typescript -it("should navigate between panels correctly", async () => { - // 1. Добавить 3 текста - // 2. Проверить PreviewControls показывает "1 / 3" - // 3. Клик next → проверяем PreviewManager.current = 1 - // 4. Клик prev → проверяем PreviewManager.current = 0 - // 5. Проверить transition direction -}); -``` - -**4. Project persistence flow:** - -``` -storageService ↔ localStorage ↔ app state -``` - -**Тест:** [`tests/integration/storage-flow.test.ts`](tests/integration/storage-flow.test.ts) - -```typescript -it("should save and load project", async () => { - // 1. Создать проект (несколько панелей) - // 2. Вызвать storageService.saveProject() - // 3. Проверить localStorage содержит данные - // 4. Перезагрузить страницу (или создать новый инстанс) - // 5. Вызвать storageService.loadProject() - // 6. Проверить состояние восстановлено -}); -``` - ---- - -#### MEDIUM PRIORITY (E2E тесты) - -**1. Полный user flow:** - -- Открыть страницу -- Добавить 3 текста -- Загрузить изображение -- Настроить текст (размер, цвет, выравнивание) -- Настроить изображение (яркость, контраст, crop) -- Экспортировать все панели в ZIP -- Проверить ZIP содержит 3 файла - -**Тест:** [`tests/e2e/create-panels.spec.ts`](tests/e2e/create-panels.spec.ts) (Playwright) - -**2. Project management flow:** - -- Создать проект -- Сохранить -- Создать второй проект -- Переключиться между проектами -- Удалить проект - -**Тест:** [`tests/e2e/projects.spec.ts`](tests/e2e/projects.spec.ts) - ---- - -### 3.4 Тесты для новых сервисов - -**imageService.test.ts:** - -- uploadFromFile (валидный/невалидный файл) -- uploadFromURL (успех/ошибка) -- crop (граничные случаи) -- applyFilters (brightness, contrast) -- validateFormat/validateSize - -**textsState.test.ts** (уже есть, расширить): - -- Тесты на синхронизацию с konvaAllStages -- Тесты на очистку stages при удалении текста - -- createPanel -- updatePanel -- deletePanel -- validatePanel (текст слишком длинный, нет изображения и т.д.) - -**storageService.test.ts:** - -- saveProject (квоты localStorage) -- loadProject (несуществующий, поврежденный JSON) -- listProjects -- autoSave (debounce) - ---- - -### 3.5 Улучшение существующих тестов - -**Проблемы в текущих тестах:** - -1. **tests/unit/states/texts.test.ts:70-100** - хорошие интеграционные тесты внутри state, но: - - Нет тестов на edge cases (clear после удаления всех) - - Нет тестов на валидацию (длина текста) - -2. **tests/unit/services/downloadService.test.ts** - хорошие моки, но: - - Нет теста на `downloadAll` с пустым массивом - - Нет теста на ошибку при создании ZIP - -3. **Компонентные тесты:** - - Большинство - "should render without crashing" - - Нет тестов на взаимодействие (клики, ввод) - - Нет тестов на пропсы - -**Рекомендация:** - -- Добавить тесты на взаимодействие для всех компонентов -- Тесты на edge cases (empty state, error state, loading state) -- Тесты на accessibility (aria-labels, keyboard navigation) - ---- - -## 4. Приоритизация улучшений - -### Фаза 1: Стабилизация (2-3 недели) - HIGH PRIORITY - -#### 1.1 Рефакторинг состояний (High) - -**Задача:** Привести все состояния к фабричному паттерну. - -**Файлы:** - -- [`src/states/konvaAllStages.svelte.ts`](src/states/konvaAllStages.svelte.ts) - переделать в фабрику -- [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts) - переделать в фабрику, убрать класс -- [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts) - упростить геттеры/сеттеры (использовать $state напрямую) - -**Тесты:** Обновить существующие тесты под новые API. - -**Критерий успеха:** Все состояния имеют одинаковую структуру. - ---- - -#### 1.2 Создание сервисного слоя (High) - -**Задача:** Вынести всю бизнес-логику в сервисы. - -**Файлы:** - -- [`src/services/imageService.ts`](src/services/imageService.ts) - новая -- [`src/services/storageService.ts`](src/services/storageService.ts) - новая -- [`src/services/exportService.ts`](src/services/exportService.ts) - новая (расширить downloadService) - -**Миграция:** - -1. Создать imageService -2. Переместить логику загрузки из ImageManager в imageService -3. Интегрировать в ImageManager -4. Аналогично для storageService - -**Тесты:** Unit тесты для каждого сервиса (80%+ покрытия). - -**Критерий успеха:** Компоненты только для UI, вся логика в сервисах. - ---- - -#### 1.3 Интеграционные тесты (High) - -**Задача:** Покрыть критические user flows. - -**Файлы:** - -- [`tests/integration/text-flow.test.ts`](tests/integration/text-flow.test.ts) -- [`tests/integration/image-flow.test.ts`](tests/integration/image-flow.test.ts) -- [`tests/integration/navigation-flow.test.ts`](tests/integration/navigation-flow.test.ts) -- [`tests/integration/storage-flow.test.ts`](tests/integration/storage-flow.test.ts) - -**Технологии:** Vitest + Testing Library (не Playwright, это не E2E). - -**Критерий успеха:** 100% покрытия критических flows. - ---- - -#### 1.4 Управление памятью Konva (High) - -**Задача:** Предотвратить утечки памяти. - -**Файлы:** - -- [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte) - добавить onDestroy -- [`src/states/konvaAllStages.svelte.ts`](src/states/konvaAllStages.svelte.ts) - добавить очистку -- [`src/states/texts.svelte.ts`](src/states/texts.svelte.ts) - интегрировать очистку stages в removeText() - -**Тесты:** Тест на отсутствие утечек (Jest memory leak detection). - -**Критерий успеха:** stages уничтожаются при удалении панелей. - ---- - -### Фаза 2: Улучшение качества (3-4 недели) - MEDIUM PRIORITY - -#### 2.1 Улучшение UI-компонентов (Medium) - -**Задача:** Стандартизировать и вынести в библиотеку. - -**Файлы:** - -- [`src/lib/components/Button.svelte`](src/lib/components/Button.svelte) - добавить sizes -- [`src/lib/components/RangeSlider.svelte`](src/lib/components/RangeSlider.svelte) - добавить label, unit -- [`src/lib/components/ColorPicker.svelte`](src/lib/components/ColorPicker.svelte) - preset colors - -**Документация:** Storybook или doc comments. - ---- - -#### 2.2 Оптимизация производительности (Medium) - -**Задача:** Ускорить рендеринг при большом количестве панелей. - -**Задачи:** - -1. Debounce для слайдеров (в TextConfig и ImageManager) -2. Virtual scrolling для PreviewAll (использовать svelte-virtual-list) -3. Ленивая загрузка изображений (IntersectionObserver) - -**Файлы:** - -- [`src/components/text/TextConfig.svelte`](src/components/text/TextConfig.svelte) - debounce -- [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte) - debounce -- [`src/components/panel/PreviewAll.svelte`](src/components/panel/PreviewAll.svelte) - virtual scroll - -**Тесты:** Бенчмарки рендеринга (vitest-bench). - ---- - -#### 2.3 Улучшение доступности (Medium) - -**Задача:** WCAG 2.1 AA compliance. - -**Файлы:** - -- Все компоненты: добавить `role`, `aria-*`, `tabindex` -- Клавиатурная навигация (Enter, Space, Arrow keys) -- Фокусные состояния (визуальные) -- Контрастность (проверить цвета) - -**Тесты:** axe-core, Playwright accessibility audits. - ---- - -#### 2.4 Расширенные тесты (Medium) - -**Задача:** Покрытие 80%+ кода. - -**Файлы:** - -- Тесты для всех сервисов (imageService, storageService, exportService) -- Тесты для всех компонентов (интеграционные) -- E2E тесты (Playwright) - -**Критерий успеха:** Coverage >80%. - ---- - -### Фаза 3: Продвинутые фичи (4+ недели) - LOW PRIORITY - -#### 3.1 Расширенные настройки текста (Low) - -**Задача:** Тени, градиенты, прозрачность, несколько блоков. - -**Файлы:** - -- [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts) - добавить shadow, opacity -- [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte) - применить настройки - -**Сложность:** Высокая (Konva Text ограничен). - ---- - -#### 3.2 Расширенные настройки изображений (Low) - -**Задача:** Фильтры, слои, blend modes. - -**Файлы:** - -- [`src/services/imageService.ts`](src/services/imageService.ts) - фильтры -- [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte) - отображение - ---- - -#### 3.3 Пресеты и шаблоны (Low) - -**Задача:** Шаблоны для быстрого старта. - -**Файлы:** - -- [`src/services/templateService.ts`](src/services/templateService.ts) -- [`src/components/template/TemplateGallery.svelte`](src/components/template/TemplateGallery.svelte) - ---- - -## 5. Безопасность - -### 5.1 Валидация входных данных (High) - -**Текущее состояние:** Почти отсутствует. - -**Требования:** - -1. **Тексты:** - - [`src/states/texts.svelte.ts`](src/states/texts.svelte.ts):17 - есть `trim().length === 0`, но нет max length - - Добавить: `if (text.length > TYPOGRAPHY.MAX_TEXT_LENGTH) throw new TextError(...)` - -2. **Изображения:** - - [`src/services/imageService.ts`](src/services/imageService.ts) - валидация: - - Формат: `file.type in IMAGE_SETTINGS.SUPPORTED_FORMATS` - - Размер: `file.size <= IMAGE_SETTINGS.MAX_FILE_SIZE` - - URL: валидация схемы (http/https), нет XSS - -3. **URL изображений:** - - Защита от XSS: не допускать `javascript:` URLs - - Проверка CORS (crossOrigin="anonymous" уже есть) - ---- - -### 5.2 Защита от XSS (High) - -**Риски:** - -- Текст рендерится в Konva Text - безопасно (не innerHTML) -- Но если добавить HTML в будущем - нужно санитизировать - -**Решение:** Использовать DOMPurify для любого HTML. - ---- - -### 5.3 Безопасная работа с localStorage (Medium) - -**Риски:** - -- localStorage не изолирован (другие скрипты на домене) -- Квоты (5-10MB) -- Поврежденные данные - -**Решение:** - -- Валидация при загрузке: `JSON.parse` в try-catch, проверка схемы -- Ограничение размера: `JSON.stringify(project).length < 5_000_000` -- Fallback: если localStorage полон/поврежден - предложить экспорт JSON - -**Файл:** [`src/services/storageService.ts`](src/services/storageService.ts) - ---- - -## 6. Мониторинг и observability - -### 6.1 Логирование ошибок (Medium) - -**Текущее состояние:** [`src/lib/utils/errorUtils.ts`](src/lib/utils/errorUtils.ts):19-26 - console.error - -**Улучшение:** - -- Отправлять ошибки в external service (Sentry, LogRocket) -- Добавить контекст (user id, project id, action) -- User-friendly сообщения (не показывать stack trace) - -**Файл:** [`src/services/errorReportingService.ts`](src/services/errorReportingService.ts) - ---- - -### 6.2 Метрики производительности (Low) - -**Что измерять:** - -- Время загрузки страницы -- Время рендеринга панелей (per panel) -- Размер памяти (heap size) -- Количество stages - -**Инструменты:** Web Vitals, Chrome DevTools Performance. - ---- - -## 7. CI/CD - -### 7.1 Текущее состояние - -**package.json scripts:** - -- `test` - vitest -- `test:unit` - unit tests -- `test:integration` - нет такой папки -- `test:e2e` - playwright test (но playwright не в devDependencies? ✅ есть) - -**Проблемы:** - -- Нет папки `tests/integration/` -- Нет папки `tests/e2e/` -- Нет GitHub Actions / CI pipeline - ---- - -### 7.2 Настроить CI (Medium Priority) - -**Цель:** Автоматические тесты на каждом PR. - -**GitHub Actions:** - -```yaml -# .github/workflows/ci.yml -on: [push, pull_request] -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - run: npm ci - - run: npm run check - - run: npm run test:unit - - run: npm run test:integration - - run: npm run test:e2e - - run: npm run build -``` - -**Критерий успеха:** Все тесты проходят на CI. - ---- - -## 8. Документация - -### 8.1 Кодовая документация (Medium) - -**Текущее состояние:** Нет JSDoc/TSDoc комментариев. - -**Рекомендация:** - -- Добавить JSDoc для всех публичных API (сервисы, состояния, компоненты) -- Использовать `/** */` с `@param`, `@returns`, `@throws` -- Генерация docs через TypeDoc - -**Пример:** - -```typescript -/** - * Uploads an image from a File object. - * @param file - The File object to upload - * @returns Promise - The processed image data - * @throws {ImageError} If file format or size is invalid - */ -uploadFromFile(file: File): Promise { ... } -``` - ---- - -### 8.2 Architecture Decision Records (ADR) (Low) - -**Цель:** Зафиксировать ключевые архитектурные решения. - -**Файлы:** `docs/adr/` (или `plans/adr/`) - -**Примеры:** - -- ADR-001: Выбор Svelte 5 с runes -- ADR-002: Паттерн фабрики для состояний -- ADR-003: Сервисный слой для бизнес-логики -- ADR-004: Konva.js для рендеринга - ---- - -## 9. Приоритизация по фазам - -### Фаза 1: Стабилизация (2-3 недели) - КРИТИЧНО - -**Цель:** Устранить архитектурные проблемы, создать сервисный слой, добавить тесты. - -**Задачи:** - -1. Рефакторинг состояний (1.1) - 2 дня -2. Создание imageService (2.2.1) - 3 дня -3. Создание storageService (2.2.2) - 2 дня -4. Интеграционные тесты (3.3) - 3 дня -5. Управление памятью Konva (2.4) - 2 дня - -**Итого:** ~15 рабочих дней - -**Критерий успеха:** - -- Все состояния используют фабричный паттерн -- Сервисы покрыты unit тестами (>80%) -- Интеграционные тесты покрывают критические flows -- Нет утечек памяти при создании/удалении панелей - ---- - -### Фаза 2: Улучшение качества (3-4 недели) - ВАЖНО - -**Цель:** Повысить поддерживаемость, производительность, доступность. - -**Задачи:** - -1. Стандартизация UI-компонентов (2.3) - 3 дня -2. Оптимизация производительности (2.2) - 4 дня -3. Улучшение доступности (2.4) - 3 дня -4. Расширенные тесты (3.4) - 5 дней -5. CI/CD настройка (7.2) - 2 дня -6. Документация (8.1) - 2 дня - -**Итого:** ~19 рабочих дней - -**Критерий успеха:** - -- Покрытие тестами >80% -- Все компоненты доступны (WCAG AA) -- Производительность: рендеринг 50 панелей < 1s -- CI паsses на всех PR - ---- - -### Фаза 3: Продвинутые фичи (4+ недели) - НИЗКИЙ - -**Цель:** Добавить удобные фичи для power users. - -**Задачи:** - -1. Расширенные настройки текста (3.1) - 1 неделя -2. Расширенные настройки изображений (3.2) - 1 неделя -3. Пресеты и шаблоны (3.3) - 5 дней -4. История изменений (4.4) - 3 дня -5. Экспорт в PDF (4.3) - 4 дня - -**Итого:** ~4 недели - ---- - -## 10. Метрики технического успеха - -- **Покрытие тестами:** >80% (критические paths: 100%) -- **Производительность:** FCP < 1s, LCP < 2.5s, рендеринг 50 панелей < 1s -- **Утечки памяти:** 0 (проверка через Chrome DevTools) -- **Доступность:** WCAG 2.1 AA compliance (axe-score > 90) -- **TypeScript ошибки:** 0 (strict mode) -- **Lint:** 0 ошибок (eslint + svelte-check) -- **CI:** 100% pass rate - ---- - -## 11. Риски и митигация - -| Риск | Вероятность | Влияние | Митигация | -| ---------------------------------- | ----------- | ------- | --------------------------------------------------- | -| Сложность Konva | Medium | High | Документировать API, добавить абстракции | -| Утечки памяти | High | High | Приоритет 1.4, тесты на память | -| Нет эксперта по Svelte 5 | Medium | Medium | Изучить документацию, использовать best practices | -| Cropper.js интеграция | Medium | Medium | Выделить отдельный сервис, тестировать изолированно | -| localStorage квоты | Low | Medium | Валидация размера, fallback на JSON export | -| Производительность при 50+ панелях | High | Medium | Виртуализация (Фаза 2) | - ---- - -## 12. Следующие шаги - -1. Утвердить технический план -2. Начать с **Фазы 1**: - - День 1-2: Рефакторинг konvaAllStages и imageConfig - - День 3-5: Создание imageService - - День 6-7: Создание storageService - - День 11-13: Интеграционные тесты - - День 14-15: Управление памятью Konva -3. Запустить CI после Фазы 1 -4. Продолжить Фазу 2 - ---- - -**Примечание:** Этот план должен обновляться по мере реализации. Каждая фаза заканчивается review и корректировкой последующих фаз. +# Технический план (Engineering & Architecture Roadmap) + +## Twitch Panels Creator + +**Анализ версии:** 0.0.1 +**Дата анализа:** 2025-02-09 +**Технологии:** Svelte 5, TypeScript, Konva.js, Vitest + +--- + +## 1. Аудит текущей архитектуры + +### 1.1 State Management - КРИТИЧЕСКИЕ ПРОБЛЕМЫ + +**Проблема:** Несогласованные паттерны управления состоянием. + +#### Паттерн A: Фабрика функций (✅ Хорошо) + +**Файлы:** [`src/states/texts.svelte.ts`](src/states/texts.svelte.ts), [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts), [`src/states/theme.svelte.ts`](src/states/theme.svelte.ts), [`src/states/konvaStage.svelte.ts`](src/states/konvaStage.svelte.ts) + +```typescript +// Пример: texts.svelte.ts +function createState() { + let texts = $state(defaultTexts); + let nextId = $state(defaultTexts.length); + + return { + get texts() { return texts; }, + addText(text: string) { ... }, + removeText(id: number) { ... }, + }; +} +export const textsState = createState(); +``` + +**Преимущества:** + +- Инкапсуляция приватных переменных +- Геттеры/сеттеры для валидации +- Легко тестировать (можно создавать инстансы) + +**Недостатки:** + +- Избыточный код геттеров/сеттеров (см. textConfig.svelte.ts:24-61) + +#### Паттерн B: Прямой $state (❌ ПЛОХО) + +**Файлы:** [`src/states/konvaAllStages.svelte.ts`](src/states/konvaAllStages.svelte.ts), [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts) + +```typescript +// ПЛОХО - глобальный массив без инкапсуляции +export const konvaAllStagesState: Array = $state([]); + +// ПЛОХО - класс с $state полями, но без фабрики +export class ImageConfigState { + image = $state(undefined); + imageLink = $state(""); + // ... +} +export const imageConfigState = new ImageConfigState(); +``` + +**Проблемы:** + +- `konvaAllStagesState` - глобальный изменяемый массив, нет контроля над операциями +- `ImageConfigState` - класс с $state, но не фабрика, сложно тестировать +- Нет единого подхода + +#### Паттерн C: Смешанный (⚠️ НЕПРАВИЛЬНО) + +**Файлы:** [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts) - класс с $state, но с приватными полями и методами + +**Рекомендация:** Привести все состояния к **Фабрике функций** (Pattern A). + +--- + +### 1.2 Сервисный слой - ОТСУТСТВУЕТ + +**Текущее состояние:** + +- ✅ [`src/services/downloadService.ts`](src/services/downloadService.ts) - хороший пример +- ❌ `imageService.ts` - отсутствует (логика загрузки размазана) +- ❌ `storageService.ts` - отсутствует (localStorage в +layout.svelte:15) + +**Проблема:** Бизнес-логика смешана с UI-компонентами. + +**Пример:** [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte) - UI без логики, кнопки не работают. + +**Рекомендация:** Создать сервисы для всех доменных областей. + +--- + +### 1.3 Компонентная архитектура - ОЦЕНКА ВСЕХ ГРУПП + +#### Группа 1: layout/ (Базовые layout-компоненты) + +**Файлы:** Card.svelte, AppHeader.svelte, PanelBar.svelte, TextBar.svelte, SettingsGrid.svelte, SettingsRow.svelte, InputGroup.svelte + +**Оценка:** ✅ **Хорошо** + +- Четкое разделение: Card — контейнер, SettingsGrid/Row — формы, PanelBar/TextBar — компоновка секций +- InputGroup.svelte простой flex wrapper, выполняет свою функцию +- AppHeader.svelte содержит логику переключения темы, но это уместно (header — привязка к UI) +- Нет дублирования, каждый компонент имеет уникальную ответственность +- **Рекомендаций:** Нет + +--- + +#### Группа 2: panel/ (Превью панелей) + +**Файлы:** Preview.svelte, PreviewAll.svelte, PreviewControls.svelte, PreviewManager.svelte + +**Оценка:** ✅ **Хорошо** + +- Preview — чистый компонент, принимает `text` и `stage` как props, рендерит Konva Stage + Layer + Image + Text +- PreviewAll — off-screen рендеринг всех stages (необходимость для экспорта) +- PreviewControls — навигация (prev/next), чистая логика +- PreviewManager — оркестратор: управляет current index, direction, вызывает downloadService +- **Вывод:** Хорошее разделение, каждая панель имеет свою зону ответственности + +**Потенциальная проблема:** + +- PreviewManager.svelte:22-30 содержит $effect для синхронизации current с textsState.texts.length — это бизнес-логика, но в данном контексте допустимо +- **Рекомендация:** Если логика усложнится, вынести в отдельный хук/сервис + +--- + +#### Группа 3: text/ (Управление текстами) + +**Файлы:** TextManager.svelte, TextConfig.svelte, TextInput.svelte, TextInlineEdit.svelte + +**Оценка:** ✅ **Хорошо** + +- TextManager — управление списком текстов (add/delete), вызывает textsState +- TextConfig — UI для глобальных настроек текста, bind:value к textConfigState +- TextInput — простой input с обработкой Enter +- TextInlineEdit — inline-редактирование с кнопкой удаления +- **Вывод:** Чистые, сфокусированные компоненты, нет дублирования + +**Замечание:** + +- TextInlineEdit.svelte:17 — `` без валидации maxLength (есть в constants.ts:32) +- **Рекомендация:** Добавить `maxlength={TYPOGRAPHY.MAX_TEXT_LENGTH}` и визуальный счетчик + +--- + +#### Группа 4: image/ (Работа с изображениями) + +**Файлы:** ImageManager.svelte, CropInline.svelte + +**Оценка:** ⚠️ **Частично реализовано (нефункционально)** + +- ImageManager.svelte — UI есть, но кнопки не работают (Upload, Edit, Reset) +- CropInline.svelte — визуальный crop box с handles, но нет интеграции с cropperjs и imageConfigState +- Настройки яркости/контраста — только UI, нет логики применения +- **Вывод:** Компоненты выглядят готовыми, но функциональность отсутствует + +**Рекомендации (High Priority):** + +- Реализовать imageService.ts и интегрировать в ImageManager +- Добавить логику crop в CropInline (drag, resize, update imageConfigState) +- Применить фильтры яркости/контраста к Konva Image в Preview.svelte + +--- + +#### Группа 5: ui/ (Переиспользуемые UI-компоненты) + +**Файлы:** Button.svelte, RangeSlider.svelte, ColorPicker.svelte, SelectFont.svelte, Alignment.svelte, Badge.svelte + +**Оценка:** ✅ **Хорошо** + +- Button — 5 типов (primary, secondary, outline, danger, mini), хороший API +- RangeSlider — простой wrapper с отображением значения +- ColorPicker — input type="color" + отображение hex +- SelectFont — hardcoded список шрифтов (можно улучшить, вынести в constants) +- Alignment — 3 кнопки выравнивания, bind:align +- Badge — простой компонент для счетчиков +- **Вывод:** Все компоненты маленькие, сфокусированные, легко переиспользовать + +**Рекомендации (Low Priority):** + +- SelectFont.svelte:9-18 — шрифты hardcoded, лучше взять из TYPOGRAPHY.FONT_FAMILIES (constants.ts:15-24) +- Button.svelte — добавить sizes (sm, md, lg) для гибкости +- RangeSlider.svelte — добавить label и unit props для лучшего UX + +--- + +**Общий вывод по компонентной архитектуре:** + +| Группа | Оценка | Приоритет исправления | +| ------ | ----------- | ------------------------------ | +| layout | ✅ Хорошо | Нет | +| panel | ✅ Хорошо | Нет | +| text | ✅ Хорошо | Low (валидация) | +| image | ⚠️ Частично | **High** (доделать функционал) | +| ui | ✅ Хорошо | Low (улучшения) | + +**Критическая проблема:** Только группа `image/` требует срочной доработки. Остальные группы следуют best practices и не нуждаются в рефакторинге. + +--- + +### 1.4 Типизация - УДОВЛЕТВОРИТЕЛЬНО + +**Хорошее:** + +- TypeScript везде +- Интерфейсы для Props компонентов +- Типы для сервисов (DownloadItem, DownloadResult) + +**Проблемы:** + +- [`src/lib/types.ts`](src/lib/types.ts) содержит только `HexColor` - слишком мало +- Нет типов для Project, Template, ImageConfig (есть в states, но не экспортированы) +- `any` в тестах (см. [`tests/unit/services/downloadService.test.ts`](tests/unit/services/downloadService.test.ts):27) + +**Рекомендация:** + +- Создать `src/lib/types/index.ts` с экспортом всех доменных типов +- Убрать `any` из тестов, создать моки с правильными типами + +--- + +### 1.5 Обработка ошибок - ХОРОШО + +**Файлы:** [`src/lib/error.types.ts`](src/lib/error.types.ts), [`src/lib/utils/errorUtils.ts`](src/lib/utils/errorUtils.ts) + +**Преимущества:** + +- Иерархия ошибок (AppError → ImageError, TextError, CanvasError, StorageError) +- Утилиты для форматирования и логирования +- Используется в downloadService + +**Недостатки:** + +- Нет обработки ошибок в UI (нет toast/notification системы) +- Ошибки только в консоль, пользователь не видит + +**Рекомендация:** Добавить notification system (например, `src/components/ui/Notification.svelte`). + +--- + +### 1.6 Производительность - РИСКИ + +**Критические точки:** + +1. **Konva stages:** + - [`src/states/konvaAllStages.svelte.ts`](src/states/konvaAllStages.svelte.ts) хранит ВСЕ stages в памяти + - [`src/components/panel/PreviewAll.svelte`](src/components/panel/PreviewAll.svelte) рендерит ВСЕ панели одновременно + - **Проблема:** При 50+ панелях будет лагать + +2. **Нет ленивой загрузки:** + - Все компоненты загружаются сразу + - Изображения загружаются без lazy loading + +3. **Нет debouncing:** + - [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts) - каждый drag слайдера вызывает перерисовку Konva + - Нет оптимизации частых обновлений + +**Рекомендация:** + +- Виртуализация списка панелей (virtual scrolling) +- Debounce для слайдеров (100-150ms) +- Ленивая загрузка изображений + +--- + +## 2. Укрепление базы (Рефакторинг) + +### 2.1 Привести все состояния к фабричному паттерну (High Priority) + +**Цель:** Единообразная структура для всех stores. + +**Текущие состояния:** + +| Файл | Текущий паттерн | Целевой паттерн | +| ------------------------ | -------------------- | ------------------------------- | +| texts.svelte.ts | ✅ Фабрика | Оставить | +| textConfig.svelte.ts | ✅ Фабрика | Упростить геттеры/сеттеры | +| theme.svelte.ts | ✅ Фабрика | Оставить | +| konvaStage.svelte.ts | ✅ Фабрика | Оставить | +| konvaAllStages.svelte.ts | ❌ Глобальный $state | Переделать в фабрику с методами | +| imageConfig.svelte.ts | ❌ Класс с $state | Переделать в фабрику | + +**Пример рефакторинга konvaAllStages:** + +```typescript +// BEFORE (src/states/konvaAllStages.svelte.ts) +export const konvaAllStagesState: Array = $state([]); + +// AFTER +function createState() { + let stages: Array = $state([]); + + return { + get stages() { + return stages; + }, + addStage(stage: Stage) { + stages.push(stage); + }, + removeStage(stage: Stage) { + stages = stages.filter((s) => s !== stage); + }, + clear() { + stages = []; + }, + }; +} +export const konvaAllStagesState = createState(); +``` + +**Задача:** Рефакторинг всех состояний к единому паттерну. + +--- + +### 2.2 Создать сервисный слой (High Priority) + +**Требуемые сервисы:** + +#### 2.2.1 imageService.ts + +**Ответственность:** Загрузка, обработка, валидация изображений. + +**API:** + +```typescript +interface ImageService { + // Загрузка + uploadFromFile(file: File): Promise; + uploadFromURL(url: string): Promise; + uploadFromPaste(clipboardData: DataTransfer): Promise; + + // Обработка + crop(image: ImageData, crop: CropRect): ImageData; + applyFilters(image: ImageData, filters: ImageFilters): ImageData; + + // Валидация + validateFormat(file: File): boolean; + validateSize(file: File): boolean; + + // Кэш + clearCache(): void; +} +``` + +**Реализация:** + +- Использовать Canvas API для обрезки и фильтров +- Кэшировать загруженные изображения (Map) +- Обработка ошибок через ImageError + +#### 2.2.2 Упрощенный подход: textsState как основной store + +**Решение:** Вместо panelService оставить `textsState` как основной store для панелей. + +**Архитектура:** + +``` +textsState (массив текстов) + textConfigState (глобальные настройки) + imageConfigState (глобальное изображение) + ↓ +PreviewManager (навигация по textsState) + ↓ +Preview (рендерит панель на основе text + textConfig + imageConfig) + ↓ +downloadService (экспорт) +``` + +**Преимущества:** + +- Нет лишних абстракций +- Прямая реактивность через Svelte 5 runes +- Простая debugging +- Минимальный порог входа + +**Когда добавить panelService:** Только если появятся персональные настройки на панель (не глобальные), или сложная бизнес-логика (валидация, bulk-операции, undo/redo). + +**Текущая логика в компонентах:** + +- `TextManager` → `textsState.addText()` / `removeText()` +- `PreviewManager` → читает `textsState.texts` для навигации +- `Preview` → читает `textsState.texts[current]` + `textConfigState` + `imageConfigState` + +Это **достаточно** для MVP. + +#### 2.2.3 storageService.ts + +**Ответственность:** Сохранение/загрузка проектов. + +**API:** + +```typescript +interface Project { + id: string; + name: string; + createdAt: Date; + updatedAt: Date; + panels: Panel[]; + globalConfig: { + textConfig: TextConfig; + imageConfig: ImageConfig | null; + }; +} + +interface StorageService { + saveProject(project: Project): Promise; + loadProject(id: string): Project | null; + listProjects(): ProjectInfo[]; + deleteProject(id: string): void; + autoSave(project: Project): void; // debounced +} +``` + +**Реализация:** + +- localStorage с ключом `twitch-panels-projects` +- JSON сериализация +- Обработка квот localStorage (5-10MB) +- Восстановление при старте + +#### 2.2.4 exportService.ts (расширить downloadService) + +**Ответственность:** Экспорт в разные форматы. + +**API:** + +```typescript +interface ExportService { + exportPanel(panel: Panel, format: "png" | "jpeg" | "webp", quality?: number): Promise; + exportAll(panels: Panel[], format: "png" | "jpeg" | "webp", quality?: number): Promise; + exportPDF(panels: Panel[]): Promise; +} +``` + +**Реализация:** + +- Использовать canvas.toBlob с качеством +- PDF через библиотеку (pdf-lib или jsPDF) + +--- + +### 2.3 Вынести общие UI-компоненты в библиотеку (Medium Priority) + +**Проблема:** Компоненты в `src/components/ui/` и `src/components/layout/` используются везде, но: + +- Нет единого стиля (разные отступы, размеры) +- Нет документации +- Сложно переиспользовать в других проектах + +**Решение:** + +1. Создать `src/lib/components/` (или отдельный пакет) +2. Документировать API каждого компонента +3. Стандартизировать пропсы (size, variant, color scheme) +4. Добавить темы (dark/light уже есть, но нужно формализовать) + +**Компоненты для выноса:** + +- Button (уже хорош, но добавить sizes: sm, md, lg) +- RangeSlider (добавить label, unit) +- ColorPicker (добавить preset colors) +- Card (упростить API, убрать snippets) + +--- + +### 2.4 Оптимизировать работу с Konva (High Priority) + +**Проблемы:** + +1. **Утечка памяти:** + - [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts):28-33 есть cleanup, но вызывается только при новой загрузке + - Stages не уничтожаются при удалении панелей + - konvaAllStages растет бесконечно + +2. **Нет управления жизненным циклом:** + - Stages создаются в Preview.svelte, но не уничтожаются + - Нет вызова `stage.destroy()` при unmount + +**Решение:** + +1. **Добавить destroy в Preview.svelte:** + +```typescript +import { onDestroy } from "svelte"; +onDestroy(() => { + if (stage?.node) { + stage.node.destroy(); + } +}); +``` + +2. **Очищать konvaAllStages при удалении панелей:** + - В `textsState.removeText()` → удалять соответствующий stage из konvaAllStagesState + - Вызывать `stage.destroy()` + +3. **Лимитировать количество stages:** + - Виртуализация: рендерить только видимые панели + - Или: рендерить все, но в hidden container (уже есть PreviewAll), но stages все равно в памяти + +**Рекомендация:** Реализовать virtual scrolling для PreviewAll. + +--- + +## 3. Стратегия тестирования + +### 3.1 Текущее покрытие + +**Анализ тестов:** + +- ✅ Unit тесты для states (texts, imageConfig, konvaStage, konvaAllStages, textConfig, theme) +- ✅ Unit тесты для компонентов (Preview, PreviewAll, PreviewControls, PreviewManager, TextConfig, TextInlineEdit, TextInput, TextManager, CropInline, ImageManager, Card, InputGroup, SettingsGrid, SettingsRow, TextBar, AppHeader, Alignment, Badge, Button, ColorPicker, RangeSlider, SelectFont) +- ✅ Unit тесты для сервисов (downloadService) +- ✅ Unit тесты для утилит (errorUtils) +- ✅ Unit тесты для констант и типов + +**Проблемы:** + +- ❌ Нет интеграционных тестов (связь компонентов + состояния) +- ❌ Нет E2E тестов (полные user flows) +- ❌ Нет тестов для imageService (его нет) +- ❌ Нет тестов для storageService (его нет) +- ❌ Нет тестов для контрактов между компонентами + +**Оценка покрытия:** ~40-50% (только unit, нет интеграций) + +--- + +### 3.2 Оптимальный баланс тестов + +**Целевое распределение:** + +- **Unit:** 40% - изолированные компоненты, утилиты, состояния +- **Integration:** 40% - взаимодействие компонентов, сервисов, состояний +- **E2E:** 20% - полные user flows (создание проекта → экспорт) + +--- + +### 3.3 Критические сценарии для тестирования + +#### HIGH PRIORITY (Интеграционные тесты) + +**1. Текстовый flow:** + +``` +TextManager → textsState → PreviewManager → Preview → downloadService +``` + +**Тест:** [`tests/integration/text-flow.test.ts`](tests/integration/text-flow.test.ts) + +```typescript +it("should create panel with text and export it", async () => { + // 1. Добавить текст через TextManager + // 2. Проверить textsState.texts + // 3. Проверить PreviewManager отображает текст + // 4. Нажать "Скачать" + // 5. Проверить downloadService.downloadPanel вызван с правильным именем +}); +``` + +**2. Изображение flow:** + +``` +ImageManager → imageService → imageConfigState → Preview → export +``` + +**Тест:** [`tests/integration/image-flow.test.ts`](tests/integration/image-flow.test.ts) + +```typescript +it("should upload image, crop it, and apply to panel", async () => { + // 1. Загрузить изображение (mock file) + // 2. Проверить imageConfigState.image + // 3. Установить crop values + // 4. Проверить Preview отображает обрезанное изображение +}); +``` + +**3. Навигация flow:** + +``` +textsState → PreviewControls → PreviewManager → Preview +``` + +**Тест:** [`tests/integration/navigation-flow.test.ts`](tests/integration/navigation-flow.test.ts) + +```typescript +it("should navigate between panels correctly", async () => { + // 1. Добавить 3 текста + // 2. Проверить PreviewControls показывает "1 / 3" + // 3. Клик next → проверяем PreviewManager.current = 1 + // 4. Клик prev → проверяем PreviewManager.current = 0 + // 5. Проверить transition direction +}); +``` + +**4. Project persistence flow:** + +``` +storageService ↔ localStorage ↔ app state +``` + +**Тест:** [`tests/integration/storage-flow.test.ts`](tests/integration/storage-flow.test.ts) + +```typescript +it("should save and load project", async () => { + // 1. Создать проект (несколько панелей) + // 2. Вызвать storageService.saveProject() + // 3. Проверить localStorage содержит данные + // 4. Перезагрузить страницу (или создать новый инстанс) + // 5. Вызвать storageService.loadProject() + // 6. Проверить состояние восстановлено +}); +``` + +--- + +#### MEDIUM PRIORITY (E2E тесты) + +**1. Полный user flow:** + +- Открыть страницу +- Добавить 3 текста +- Загрузить изображение +- Настроить текст (размер, цвет, выравнивание) +- Настроить изображение (яркость, контраст, crop) +- Экспортировать все панели в ZIP +- Проверить ZIP содержит 3 файла + +**Тест:** [`tests/e2e/create-panels.spec.ts`](tests/e2e/create-panels.spec.ts) (Playwright) + +**2. Project management flow:** + +- Создать проект +- Сохранить +- Создать второй проект +- Переключиться между проектами +- Удалить проект + +**Тест:** [`tests/e2e/projects.spec.ts`](tests/e2e/projects.spec.ts) + +--- + +### 3.4 Тесты для новых сервисов + +**imageService.test.ts:** + +- uploadFromFile (валидный/невалидный файл) +- uploadFromURL (успех/ошибка) +- crop (граничные случаи) +- applyFilters (brightness, contrast) +- validateFormat/validateSize + +**textsState.test.ts** (уже есть, расширить): + +- Тесты на синхронизацию с konvaAllStages +- Тесты на очистку stages при удалении текста + +- createPanel +- updatePanel +- deletePanel +- validatePanel (текст слишком длинный, нет изображения и т.д.) + +**storageService.test.ts:** + +- saveProject (квоты localStorage) +- loadProject (несуществующий, поврежденный JSON) +- listProjects +- autoSave (debounce) + +--- + +### 3.5 Улучшение существующих тестов + +**Проблемы в текущих тестах:** + +1. **tests/unit/states/texts.test.ts:70-100** - хорошие интеграционные тесты внутри state, но: + - Нет тестов на edge cases (clear после удаления всех) + - Нет тестов на валидацию (длина текста) + +2. **tests/unit/services/downloadService.test.ts** - хорошие моки, но: + - Нет теста на `downloadAll` с пустым массивом + - Нет теста на ошибку при создании ZIP + +3. **Компонентные тесты:** + - Большинство - "should render without crashing" + - Нет тестов на взаимодействие (клики, ввод) + - Нет тестов на пропсы + +**Рекомендация:** + +- Добавить тесты на взаимодействие для всех компонентов +- Тесты на edge cases (empty state, error state, loading state) +- Тесты на accessibility (aria-labels, keyboard navigation) + +--- + +## 4. Приоритизация улучшений + +### Фаза 1: Стабилизация (2-3 недели) - HIGH PRIORITY + +#### 1.1 Рефакторинг состояний (High) + +**Задача:** Привести все состояния к фабричному паттерну. + +**Файлы:** + +- [`src/states/konvaAllStages.svelte.ts`](src/states/konvaAllStages.svelte.ts) - переделать в фабрику +- [`src/states/imageConfig.svelte.ts`](src/states/imageConfig.svelte.ts) - переделать в фабрику, убрать класс +- [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts) - упростить геттеры/сеттеры (использовать $state напрямую) + +**Тесты:** Обновить существующие тесты под новые API. + +**Критерий успеха:** Все состояния имеют одинаковую структуру. + +--- + +#### 1.2 Создание сервисного слоя (High) + +**Задача:** Вынести всю бизнес-логику в сервисы. + +**Файлы:** + +- [`src/services/imageService.ts`](src/services/imageService.ts) - новая +- [`src/services/storageService.ts`](src/services/storageService.ts) - новая +- [`src/services/exportService.ts`](src/services/exportService.ts) - новая (расширить downloadService) + +**Миграция:** + +1. Создать imageService +2. Переместить логику загрузки из ImageManager в imageService +3. Интегрировать в ImageManager +4. Аналогично для storageService + +**Тесты:** Unit тесты для каждого сервиса (80%+ покрытия). + +**Критерий успеха:** Компоненты только для UI, вся логика в сервисах. + +--- + +#### 1.3 Интеграционные тесты (High) + +**Задача:** Покрыть критические user flows. + +**Файлы:** + +- [`tests/integration/text-flow.test.ts`](tests/integration/text-flow.test.ts) +- [`tests/integration/image-flow.test.ts`](tests/integration/image-flow.test.ts) +- [`tests/integration/navigation-flow.test.ts`](tests/integration/navigation-flow.test.ts) +- [`tests/integration/storage-flow.test.ts`](tests/integration/storage-flow.test.ts) + +**Технологии:** Vitest + Testing Library (не Playwright, это не E2E). + +**Критерий успеха:** 100% покрытия критических flows. + +--- + +#### 1.4 Управление памятью Konva (High) + +**Задача:** Предотвратить утечки памяти. + +**Файлы:** + +- [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte) - добавить onDestroy +- [`src/states/konvaAllStages.svelte.ts`](src/states/konvaAllStages.svelte.ts) - добавить очистку +- [`src/states/texts.svelte.ts`](src/states/texts.svelte.ts) - интегрировать очистку stages в removeText() + +**Тесты:** Тест на отсутствие утечек (Jest memory leak detection). + +**Критерий успеха:** stages уничтожаются при удалении панелей. + +--- + +### Фаза 2: Улучшение качества (3-4 недели) - MEDIUM PRIORITY + +#### 2.1 Улучшение UI-компонентов (Medium) + +**Задача:** Стандартизировать и вынести в библиотеку. + +**Файлы:** + +- [`src/lib/components/Button.svelte`](src/lib/components/Button.svelte) - добавить sizes +- [`src/lib/components/RangeSlider.svelte`](src/lib/components/RangeSlider.svelte) - добавить label, unit +- [`src/lib/components/ColorPicker.svelte`](src/lib/components/ColorPicker.svelte) - preset colors + +**Документация:** Storybook или doc comments. + +--- + +#### 2.2 Оптимизация производительности (Medium) + +**Задача:** Ускорить рендеринг при большом количестве панелей. + +**Задачи:** + +1. Debounce для слайдеров (в TextConfig и ImageManager) +2. Virtual scrolling для PreviewAll (использовать svelte-virtual-list) +3. Ленивая загрузка изображений (IntersectionObserver) + +**Файлы:** + +- [`src/components/text/TextConfig.svelte`](src/components/text/TextConfig.svelte) - debounce +- [`src/components/image/ImageManager.svelte`](src/components/image/ImageManager.svelte) - debounce +- [`src/components/panel/PreviewAll.svelte`](src/components/panel/PreviewAll.svelte) - virtual scroll + +**Тесты:** Бенчмарки рендеринга (vitest-bench). + +--- + +#### 2.3 Улучшение доступности (Medium) + +**Задача:** WCAG 2.1 AA compliance. + +**Файлы:** + +- Все компоненты: добавить `role`, `aria-*`, `tabindex` +- Клавиатурная навигация (Enter, Space, Arrow keys) +- Фокусные состояния (визуальные) +- Контрастность (проверить цвета) + +**Тесты:** axe-core, Playwright accessibility audits. + +--- + +#### 2.4 Расширенные тесты (Medium) + +**Задача:** Покрытие 80%+ кода. + +**Файлы:** + +- Тесты для всех сервисов (imageService, storageService, exportService) +- Тесты для всех компонентов (интеграционные) +- E2E тесты (Playwright) + +**Критерий успеха:** Coverage >80%. + +--- + +### Фаза 3: Продвинутые фичи (4+ недели) - LOW PRIORITY + +#### 3.1 Расширенные настройки текста (Low) + +**Задача:** Тени, градиенты, прозрачность, несколько блоков. + +**Файлы:** + +- [`src/states/textConfig.svelte.ts`](src/states/textConfig.svelte.ts) - добавить shadow, opacity +- [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte) - применить настройки + +**Сложность:** Высокая (Konva Text ограничен). + +--- + +#### 3.2 Расширенные настройки изображений (Low) + +**Задача:** Фильтры, слои, blend modes. + +**Файлы:** + +- [`src/services/imageService.ts`](src/services/imageService.ts) - фильтры +- [`src/components/panel/Preview.svelte`](src/components/panel/Preview.svelte) - отображение + +--- + +#### 3.3 Пресеты и шаблоны (Low) + +**Задача:** Шаблоны для быстрого старта. + +**Файлы:** + +- [`src/services/templateService.ts`](src/services/templateService.ts) +- [`src/components/template/TemplateGallery.svelte`](src/components/template/TemplateGallery.svelte) + +--- + +## 5. Безопасность + +### 5.1 Валидация входных данных (High) + +**Текущее состояние:** Почти отсутствует. + +**Требования:** + +1. **Тексты:** + - [`src/states/texts.svelte.ts`](src/states/texts.svelte.ts):17 - есть `trim().length === 0`, но нет max length + - Добавить: `if (text.length > TYPOGRAPHY.MAX_TEXT_LENGTH) throw new TextError(...)` + +2. **Изображения:** + - [`src/services/imageService.ts`](src/services/imageService.ts) - валидация: + - Формат: `file.type in IMAGE_SETTINGS.SUPPORTED_FORMATS` + - Размер: `file.size <= IMAGE_SETTINGS.MAX_FILE_SIZE` + - URL: валидация схемы (http/https), нет XSS + +3. **URL изображений:** + - Защита от XSS: не допускать `javascript:` URLs + - Проверка CORS (crossOrigin="anonymous" уже есть) + +--- + +### 5.2 Защита от XSS (High) + +**Риски:** + +- Текст рендерится в Konva Text - безопасно (не innerHTML) +- Но если добавить HTML в будущем - нужно санитизировать + +**Решение:** Использовать DOMPurify для любого HTML. + +--- + +### 5.3 Безопасная работа с localStorage (Medium) + +**Риски:** + +- localStorage не изолирован (другие скрипты на домене) +- Квоты (5-10MB) +- Поврежденные данные + +**Решение:** + +- Валидация при загрузке: `JSON.parse` в try-catch, проверка схемы +- Ограничение размера: `JSON.stringify(project).length < 5_000_000` +- Fallback: если localStorage полон/поврежден - предложить экспорт JSON + +**Файл:** [`src/services/storageService.ts`](src/services/storageService.ts) + +--- + +## 6. Мониторинг и observability + +### 6.1 Логирование ошибок (Medium) + +**Текущее состояние:** [`src/lib/utils/errorUtils.ts`](src/lib/utils/errorUtils.ts):19-26 - console.error + +**Улучшение:** + +- Отправлять ошибки в external service (Sentry, LogRocket) +- Добавить контекст (user id, project id, action) +- User-friendly сообщения (не показывать stack trace) + +**Файл:** [`src/services/errorReportingService.ts`](src/services/errorReportingService.ts) + +--- + +### 6.2 Метрики производительности (Low) + +**Что измерять:** + +- Время загрузки страницы +- Время рендеринга панелей (per panel) +- Размер памяти (heap size) +- Количество stages + +**Инструменты:** Web Vitals, Chrome DevTools Performance. + +--- + +## 7. CI/CD + +### 7.1 Текущее состояние + +**package.json scripts:** + +- `test` - vitest +- `test:unit` - unit tests +- `test:integration` - нет такой папки +- `test:e2e` - playwright test (но playwright не в devDependencies? ✅ есть) + +**Проблемы:** + +- Нет папки `tests/integration/` +- Нет папки `tests/e2e/` +- Нет GitHub Actions / CI pipeline + +--- + +### 7.2 Настроить CI (Medium Priority) + +**Цель:** Автоматические тесты на каждом PR. + +**GitHub Actions:** + +```yaml +# .github/workflows/ci.yml +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: npm ci + - run: npm run check + - run: npm run test:unit + - run: npm run test:integration + - run: npm run test:e2e + - run: npm run build +``` + +**Критерий успеха:** Все тесты проходят на CI. + +--- + +## 8. Документация + +### 8.1 Кодовая документация (Medium) + +**Текущее состояние:** Нет JSDoc/TSDoc комментариев. + +**Рекомендация:** + +- Добавить JSDoc для всех публичных API (сервисы, состояния, компоненты) +- Использовать `/** */` с `@param`, `@returns`, `@throws` +- Генерация docs через TypeDoc + +**Пример:** + +```typescript +/** + * Uploads an image from a File object. + * @param file - The File object to upload + * @returns Promise - The processed image data + * @throws {ImageError} If file format or size is invalid + */ +uploadFromFile(file: File): Promise { ... } +``` + +--- + +### 8.2 Architecture Decision Records (ADR) (Low) + +**Цель:** Зафиксировать ключевые архитектурные решения. + +**Файлы:** `docs/adr/` (или `plans/adr/`) + +**Примеры:** + +- ADR-001: Выбор Svelte 5 с runes +- ADR-002: Паттерн фабрики для состояний +- ADR-003: Сервисный слой для бизнес-логики +- ADR-004: Konva.js для рендеринга + +--- + +## 9. Приоритизация по фазам + +### Фаза 1: Стабилизация (2-3 недели) - КРИТИЧНО + +**Цель:** Устранить архитектурные проблемы, создать сервисный слой, добавить тесты. + +**Задачи:** + +1. Рефакторинг состояний (1.1) - 2 дня +2. Создание imageService (2.2.1) - 3 дня +3. Создание storageService (2.2.2) - 2 дня +4. Интеграционные тесты (3.3) - 3 дня +5. Управление памятью Konva (2.4) - 2 дня + +**Итого:** ~15 рабочих дней + +**Критерий успеха:** + +- Все состояния используют фабричный паттерн +- Сервисы покрыты unit тестами (>80%) +- Интеграционные тесты покрывают критические flows +- Нет утечек памяти при создании/удалении панелей + +--- + +### Фаза 2: Улучшение качества (3-4 недели) - ВАЖНО + +**Цель:** Повысить поддерживаемость, производительность, доступность. + +**Задачи:** + +1. Стандартизация UI-компонентов (2.3) - 3 дня +2. Оптимизация производительности (2.2) - 4 дня +3. Улучшение доступности (2.4) - 3 дня +4. Расширенные тесты (3.4) - 5 дней +5. CI/CD настройка (7.2) - 2 дня +6. Документация (8.1) - 2 дня + +**Итого:** ~19 рабочих дней + +**Критерий успеха:** + +- Покрытие тестами >80% +- Все компоненты доступны (WCAG AA) +- Производительность: рендеринг 50 панелей < 1s +- CI паsses на всех PR + +--- + +### Фаза 3: Продвинутые фичи (4+ недели) - НИЗКИЙ + +**Цель:** Добавить удобные фичи для power users. + +**Задачи:** + +1. Расширенные настройки текста (3.1) - 1 неделя +2. Расширенные настройки изображений (3.2) - 1 неделя +3. Пресеты и шаблоны (3.3) - 5 дней +4. История изменений (4.4) - 3 дня +5. Экспорт в PDF (4.3) - 4 дня + +**Итого:** ~4 недели + +--- + +## 10. Метрики технического успеха + +- **Покрытие тестами:** >80% (критические paths: 100%) +- **Производительность:** FCP < 1s, LCP < 2.5s, рендеринг 50 панелей < 1s +- **Утечки памяти:** 0 (проверка через Chrome DevTools) +- **Доступность:** WCAG 2.1 AA compliance (axe-score > 90) +- **TypeScript ошибки:** 0 (strict mode) +- **Lint:** 0 ошибок (eslint + svelte-check) +- **CI:** 100% pass rate + +--- + +## 11. Риски и митигация + +| Риск | Вероятность | Влияние | Митигация | +| ---------------------------------- | ----------- | ------- | --------------------------------------------------- | +| Сложность Konva | Medium | High | Документировать API, добавить абстракции | +| Утечки памяти | High | High | Приоритет 1.4, тесты на память | +| Нет эксперта по Svelte 5 | Medium | Medium | Изучить документацию, использовать best practices | +| Cropper.js интеграция | Medium | Medium | Выделить отдельный сервис, тестировать изолированно | +| localStorage квоты | Low | Medium | Валидация размера, fallback на JSON export | +| Производительность при 50+ панелях | High | Medium | Виртуализация (Фаза 2) | + +--- + +## 12. Следующие шаги + +1. Утвердить технический план +2. Начать с **Фазы 1**: + - День 1-2: Рефакторинг konvaAllStages и imageConfig + - День 3-5: Создание imageService + - День 6-7: Создание storageService + - День 11-13: Интеграционные тесты + - День 14-15: Управление памятью Konva +3. Запустить CI после Фазы 1 +4. Продолжить Фазу 2 + +--- + +**Примечание:** Этот план должен обновляться по мере реализации. Каждая фаза заканчивается review и корректировкой последующих фаз. diff --git a/reference/index.html b/reference/index.html index ca6d3ec..13d1022 100644 --- a/reference/index.html +++ b/reference/index.html @@ -1,250 +1,288 @@ - - - - - - Twitch Panels Creator - - - -
-
-
-

Twitch Panels

- -
-
- -
- -
-
-

Тексты панелей

- -
- - -
- -
-
- -
-

Настройки текста

- -
-
- -
- - 18 -
-
- -
- - -
- -
- -
- - #ffffff -
-
- -
- -
- - - -
-
- -
- -
- - 10 -
-
- -
- -
- - 0 -
-
-
-
-
- - -
-
-

Фоновое изображение

- -
-
- -
-
-
-
-
-
-
-
-
-
-
- -
- - - - -
- -
-
- -
- - 100 -
-
- -
- -
- - 100 -
-
-
-
-
- -
-
-

Панели 0

-
- - 0 / 0 - -
-
- -
-
-
- - - - -

Добавьте тексты для создания панелей

-
-
- -
- - -
-
-
-
-
-
- - - - + + + + + + Twitch Panels Creator + + + +
+
+
+

Twitch Panels

+ +
+
+ +
+ +
+
+

Тексты панелей

+ +
+ + +
+ +
+
+ +
+

Настройки текста

+ +
+
+ +
+ + 18 +
+
+ +
+ + +
+ +
+ +
+ + #ffffff +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + 10 +
+
+ +
+ +
+ + 0 +
+
+
+
+
+ + +
+
+

Фоновое изображение

+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + +
+ +
+
+ +
+ + 100 +
+
+ +
+ +
+ + 100 +
+
+
+
+
+ +
+
+

Панели 0

+
+ + 0 / 0 + +
+
+ +
+
+
+ + + + +

Добавьте тексты для создания панелей

+
+
+ +
+ + +
+
+
+
+
+
+ + + + diff --git a/reference/script.js b/reference/script.js index 8955e59..ba342db 100644 --- a/reference/script.js +++ b/reference/script.js @@ -1,570 +1,625 @@ -// State -let texts = []; -let panels = []; -let currentPanelIndex = 0; -let backgroundImage = null; -let originalImage = null; -let settings = { - fontSize: 18, - fontFamily: "Arial", - textColor: "#ffffff", - alignment: "left", - sidePadding: 10, - centerOffset: 0, - bgBrightness: 100, - bgContrast: 100, -}; - -// Crop state -let cropBox = { - x: 0, - y: 0, - width: 0, - height: 0, -}; -let isDragging = false; -let isResizing = false; -let resizeHandle = null; -let dragStart = { x: 0, y: 0 }; -let cropBoxStart = { x: 0, y: 0, width: 0, height: 0 }; - -// Initialize -document.addEventListener("DOMContentLoaded", () => { - initializeTheme(); - initializeEventListeners(); - loadDefaultBackground(); - addDefaultTexts(); -}); - -// Theme Management -function initializeTheme() { - const savedTheme = localStorage.getItem("theme") || "light"; - document.documentElement.setAttribute("data-theme", savedTheme); -} - -function toggleTheme() { - const currentTheme = document.documentElement.getAttribute("data-theme"); - const newTheme = currentTheme === "light" ? "dark" : "light"; - document.documentElement.setAttribute("data-theme", newTheme); - localStorage.setItem("theme", newTheme); -} - -// Event Listeners -function initializeEventListeners() { - // Theme toggle - document.getElementById("themeToggle").addEventListener("click", toggleTheme); - - // Add text - document.getElementById("addTextBtn").addEventListener("click", addText); - document.getElementById("panelTextInput").addEventListener("keypress", (e) => { - if (e.key === "Enter") addText(); - }); - - // Settings - document.getElementById("fontSize").addEventListener("input", (e) => { - settings.fontSize = parseInt(e.target.value); - document.getElementById("fontSizeValue").textContent = e.target.value; - updateAllPanels(); - }); - - document.getElementById("fontFamily").addEventListener("change", (e) => { - settings.fontFamily = e.target.value; - updateAllPanels(); - }); - - document.getElementById("textColor").addEventListener("input", (e) => { - settings.textColor = e.target.value; - document.getElementById("textColorValue").textContent = e.target.value; - updateAllPanels(); - }); - - document.querySelectorAll(".align-btn").forEach((btn) => { - btn.addEventListener("click", (e) => { - document.querySelectorAll(".align-btn").forEach((b) => b.classList.remove("active")); - btn.classList.add("active"); - settings.alignment = btn.dataset.align; - updateAllPanels(); - }); - }); - - document.getElementById("sidePadding").addEventListener("input", (e) => { - settings.sidePadding = parseInt(e.target.value); - document.getElementById("sidePaddingValue").textContent = e.target.value; - updateAllPanels(); - }); - - document.getElementById("centerOffset").addEventListener("input", (e) => { - settings.centerOffset = parseInt(e.target.value); - document.getElementById("centerOffsetValue").textContent = e.target.value; - updateAllPanels(); - }); - - // Background controls - document.getElementById("uploadBgBtn").addEventListener("click", () => { - document.getElementById("bgImageInput").click(); - }); - - document.getElementById("bgImageInput").addEventListener("change", handleBackgroundUpload); - - document.getElementById("bgBrightness").addEventListener("input", (e) => { - settings.bgBrightness = parseInt(e.target.value); - document.getElementById("bgBrightnessValue").textContent = e.target.value; - drawCropCanvas(); - updateAllPanels(); - }); - - document.getElementById("bgContrast").addEventListener("input", (e) => { - settings.bgContrast = parseInt(e.target.value); - document.getElementById("bgContrastValue").textContent = e.target.value; - drawCropCanvas(); - updateAllPanels(); - }); - - document.getElementById("resetCropBtn").addEventListener("click", resetCrop); - - // Crop canvas events - const canvas = document.getElementById("cropCanvas"); - canvas.addEventListener("mousedown", handleCropMouseDown); - canvas.addEventListener("mousemove", handleCropMouseMove); - canvas.addEventListener("mouseup", handleCropMouseUp); - canvas.addEventListener("mouseleave", handleCropMouseUp); - - // Panel navigation - document.getElementById("prevPanel").addEventListener("click", () => { - if (currentPanelIndex > 0) { - currentPanelIndex--; - displayCurrentPanel(); - } - }); - - document.getElementById("nextPanel").addEventListener("click", () => { - if (currentPanelIndex < panels.length - 1) { - currentPanelIndex++; - displayCurrentPanel(); - } - }); - - document.getElementById("downloadCurrentBtn").addEventListener("click", downloadCurrentPanel); - document.getElementById("downloadAllBtn").addEventListener("click", downloadAllPanels); -} - -// Load default background -function loadDefaultBackground() { - const img = new Image(); - img.crossOrigin = "anonymous"; - img.onload = () => { - originalImage = img; - backgroundImage = img; - initializeCropBox(); - drawCropCanvas(); - updateAllPanels(); - }; - img.src = "https://images.unsplash.com/photo-1579546929518-9e396f3cc809?w=800&h=250&fit=crop"; -} - -// Add default texts -function addDefaultTexts() { - const defaultTexts = ["links", "about me", "projects"]; - defaultTexts.forEach((text) => { - texts.push({ id: Date.now() + Math.random(), text }); - }); - renderTexts(); - updateAllPanels(); -} - -// Add text -function addText() { - const input = document.getElementById("panelTextInput"); - const text = input.value.trim(); - - if (!text) return; - - texts.push({ id: Date.now(), text }); - input.value = ""; - - renderTexts(); - updateAllPanels(); -} - -// Render texts list -function renderTexts() { - const container = document.getElementById("textsList"); - - if (texts.length === 0) { - container.innerHTML = ` -
- - - -

Добавьте тексты

-
- `; - return; - } - - container.innerHTML = texts - .map( - (item) => ` -
- - -
- `, - ) - .join(""); -} - -// Update text -window.updateText = function (id, newText) { - const item = texts.find((t) => t.id === id); - if (item) { - item.text = newText; - updateAllPanels(); - } -}; - -// Delete text -window.deleteText = function (id) { - texts = texts.filter((t) => t.id !== id); - renderTexts(); - updateAllPanels(); -}; -// Handle background upload -function handleBackgroundUpload(e) { - const file = e.target.files[0]; - if (!file) return; - - const reader = new FileReader(); - reader.onload = (event) => { - const img = new Image(); - img.onload = () => { - originalImage = img; - backgroundImage = img; - initializeCropBox(); - drawCropCanvas(); - updateAllPanels(); - }; - img.src = event.target.result; - }; - reader.readAsDataURL(file); -} - -// Initialize crop box -function initializeCropBox() { - const canvas = document.getElementById("cropCanvas"); - const container = document.getElementById("cropCanvasContainer"); - const rect = container.getBoundingClientRect(); - - canvas.width = rect.width; - canvas.height = rect.height; - - // Set crop box to full canvas initially - cropBox = { - x: 0, - y: 0, - width: canvas.width, - height: canvas.height, - }; - - updateCropBoxElement(); - document.getElementById("cropBox").classList.add("active"); -} - -// Draw crop canvas -function drawCropCanvas() { - const canvas = document.getElementById("cropCanvas"); - const ctx = canvas.getContext("2d"); - - if (!originalImage) return; - - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Apply filters - ctx.filter = `brightness(${settings.bgBrightness}%) contrast(${settings.bgContrast}%)`; - - // Draw image to fit canvas - const scale = Math.max(canvas.width / originalImage.width, canvas.height / originalImage.height); - const scaledWidth = originalImage.width * scale; - const scaledHeight = originalImage.height * scale; - const x = (canvas.width - scaledWidth) / 2; - const y = (canvas.height - scaledHeight) / 2; - - ctx.drawImage(originalImage, x, y, scaledWidth, scaledHeight); - ctx.filter = "none"; -} - -// Update crop box element -function updateCropBoxElement() { - const element = document.getElementById("cropBox"); - element.style.left = cropBox.x + "px"; - element.style.top = cropBox.y + "px"; - element.style.width = cropBox.width + "px"; - element.style.height = cropBox.height + "px"; -} - -// Crop mouse handlers -function handleCropMouseDown(e) { - const rect = e.target.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - - // Check if clicking on a handle - const handles = document.querySelectorAll(".crop-handle"); - let clickedHandle = null; - - handles.forEach((handle) => { - const handleRect = handle.getBoundingClientRect(); - const handleX = handleRect.left - rect.left + handleRect.width / 2; - const handleY = handleRect.top - rect.top + handleRect.height / 2; - - if (Math.abs(x - handleX) < 10 && Math.abs(y - handleY) < 10) { - clickedHandle = handle.classList[1]; // Get handle class (nw, ne, etc.) - } - }); - - if (clickedHandle) { - isResizing = true; - resizeHandle = clickedHandle; - } else if (x >= cropBox.x && x <= cropBox.x + cropBox.width && y >= cropBox.y && y <= cropBox.y + cropBox.height) { - isDragging = true; - } - - dragStart = { x, y }; - cropBoxStart = { ...cropBox }; -} - -function handleCropMouseMove(e) { - if (!isDragging && !isResizing) return; - - const rect = e.target.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - const dx = x - dragStart.x; - const dy = y - dragStart.y; - - if (isDragging) { - cropBox.x = Math.max(0, Math.min(cropBoxStart.x + dx, rect.width - cropBox.width)); - cropBox.y = Math.max(0, Math.min(cropBoxStart.y + dy, rect.height - cropBox.height)); - } else if (isResizing) { - const minSize = 50; - - switch (resizeHandle) { - case "nw": - cropBox.x = Math.max(0, Math.min(cropBoxStart.x + dx, cropBoxStart.x + cropBoxStart.width - minSize)); - cropBox.y = Math.max(0, Math.min(cropBoxStart.y + dy, cropBoxStart.y + cropBoxStart.height - minSize)); - cropBox.width = cropBoxStart.width - (cropBox.x - cropBoxStart.x); - cropBox.height = cropBoxStart.height - (cropBox.y - cropBoxStart.y); - break; - case "ne": - cropBox.y = Math.max(0, Math.min(cropBoxStart.y + dy, cropBoxStart.y + cropBoxStart.height - minSize)); - cropBox.width = Math.max(minSize, Math.min(cropBoxStart.width + dx, rect.width - cropBoxStart.x)); - cropBox.height = cropBoxStart.height - (cropBox.y - cropBoxStart.y); - break; - case "sw": - cropBox.x = Math.max(0, Math.min(cropBoxStart.x + dx, cropBoxStart.x + cropBoxStart.width - minSize)); - cropBox.width = cropBoxStart.width - (cropBox.x - cropBoxStart.x); - cropBox.height = Math.max(minSize, Math.min(cropBoxStart.height + dy, rect.height - cropBoxStart.y)); - break; - case "se": - cropBox.width = Math.max(minSize, Math.min(cropBoxStart.width + dx, rect.width - cropBoxStart.x)); - cropBox.height = Math.max(minSize, Math.min(cropBoxStart.height + dy, rect.height - cropBoxStart.y)); - break; - case "n": - cropBox.y = Math.max(0, Math.min(cropBoxStart.y + dy, cropBoxStart.y + cropBoxStart.height - minSize)); - cropBox.height = cropBoxStart.height - (cropBox.y - cropBoxStart.y); - break; - case "s": - cropBox.height = Math.max(minSize, Math.min(cropBoxStart.height + dy, rect.height - cropBoxStart.y)); - break; - case "w": - cropBox.x = Math.max(0, Math.min(cropBoxStart.x + dx, cropBoxStart.x + cropBoxStart.width - minSize)); - cropBox.width = cropBoxStart.width - (cropBox.x - cropBoxStart.x); - break; - case "e": - cropBox.width = Math.max(minSize, Math.min(cropBoxStart.width + dx, rect.width - cropBoxStart.x)); - break; - } - } - - updateCropBoxElement(); -} - -function handleCropMouseUp() { - if (isDragging || isResizing) { - applyCrop(); - } - isDragging = false; - isResizing = false; - resizeHandle = null; -} - -// Apply crop -function applyCrop() { - if (!originalImage) return; - - const canvas = document.getElementById("cropCanvas"); - const tempCanvas = document.createElement("canvas"); - const tempCtx = tempCanvas.getContext("2d"); - - // Calculate scale factor - const scale = Math.max(canvas.width / originalImage.width, canvas.height / originalImage.height); - const scaledWidth = originalImage.width * scale; - const scaledHeight = originalImage.height * scale; - const offsetX = (canvas.width - scaledWidth) / 2; - const offsetY = (canvas.height - scaledHeight) / 2; - - // Calculate crop area in original image coordinates - const cropX = (cropBox.x - offsetX) / scale; - const cropY = (cropBox.y - offsetY) / scale; - const cropWidth = cropBox.width / scale; - const cropHeight = cropBox.height / scale; - - tempCanvas.width = cropWidth; - tempCanvas.height = cropHeight; - - tempCtx.drawImage(originalImage, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight); - - const img = new Image(); - img.onload = () => { - backgroundImage = img; - updateAllPanels(); - }; - img.src = tempCanvas.toDataURL(); -} - -// Reset crop -function resetCrop() { - loadDefaultBackground(); -} -// Update all panels -function updateAllPanels() { - panels = texts.map((item) => ({ - id: item.id, - text: item.text, - })); - - currentPanelIndex = Math.min(currentPanelIndex, Math.max(0, panels.length - 1)); - displayCurrentPanel(); - updatePanelNavigation(); -} - -// Display current panel -function displayCurrentPanel() { - const container = document.getElementById("panelDisplay"); - - if (panels.length === 0) { - container.innerHTML = ` -
- - - - -

Добавьте тексты для создания панелей

-
- `; - return; - } - - const panel = panels[currentPanelIndex]; - container.innerHTML = ``; - - setTimeout(() => { - drawPanel(panel, "currentPanelCanvas"); - }, 0); -} - -// Update panel navigation -function updatePanelNavigation() { - document.getElementById("panelCount").textContent = panels.length; - document.getElementById("panelIndicator").textContent = - panels.length > 0 ? `${currentPanelIndex + 1} / ${panels.length}` : "0 / 0"; - - document.getElementById("prevPanel").disabled = currentPanelIndex === 0 || panels.length === 0; - document.getElementById("nextPanel").disabled = currentPanelIndex >= panels.length - 1 || panels.length === 0; - document.getElementById("downloadCurrentBtn").disabled = panels.length === 0; - document.getElementById("downloadAllBtn").disabled = panels.length === 0; -} - -// Draw panel -function drawPanel(panel, canvasId) { - const canvas = document.getElementById(canvasId); - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - canvas.width = 320; - canvas.height = 100; - - // Draw background - if (backgroundImage) { - ctx.filter = `brightness(${settings.bgBrightness}%) contrast(${settings.bgContrast}%)`; - - const scale = Math.max(canvas.width / backgroundImage.width, canvas.height / backgroundImage.height); - const scaledWidth = backgroundImage.width * scale; - const scaledHeight = backgroundImage.height * scale; - const x = (canvas.width - scaledWidth) / 2; - const y = (canvas.height - scaledHeight) / 2; - - ctx.drawImage(backgroundImage, x, y, scaledWidth, scaledHeight); - ctx.filter = "none"; - } else { - ctx.fillStyle = "#667eea"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - } - - // Draw text - ctx.font = `${settings.fontSize}px ${settings.fontFamily}`; - ctx.fillStyle = settings.textColor; - ctx.textBaseline = "middle"; - - const text = panel.text; - - let x; - if (settings.alignment === "left") { - x = settings.sidePadding; - ctx.textAlign = "left"; - } else if (settings.alignment === "center") { - x = canvas.width / 2 + settings.centerOffset; - ctx.textAlign = "center"; - } else { - x = canvas.width - settings.sidePadding; - ctx.textAlign = "right"; - } - - const y = canvas.height / 2; - ctx.fillText(text, x, y); -} - -// Download current panel -function downloadCurrentPanel() { - if (panels.length === 0) return; - - const canvas = document.getElementById("currentPanelCanvas"); - const panel = panels[currentPanelIndex]; - - if (canvas && panel) { - const link = document.createElement("a"); - link.download = `${panel.text}.png`; - link.href = canvas.toDataURL(); - link.click(); - } -} - -// Download all panels -function downloadAllPanels() { - panels.forEach((panel, index) => { - setTimeout(() => { - const tempCanvas = document.createElement("canvas"); - tempCanvas.id = `temp-canvas-${panel.id}`; - document.body.appendChild(tempCanvas); - - drawPanel(panel, tempCanvas.id); - - const link = document.createElement("a"); - link.download = `${panel.text}.png`; - link.href = tempCanvas.toDataURL(); - link.click(); - - document.body.removeChild(tempCanvas); - }, 100 * index); - }); -} +// State +let texts = []; +let panels = []; +let currentPanelIndex = 0; +let backgroundImage = null; +let originalImage = null; +let settings = { + fontSize: 18, + fontFamily: "Arial", + textColor: "#ffffff", + alignment: "left", + sidePadding: 10, + centerOffset: 0, + bgBrightness: 100, + bgContrast: 100, +}; + +// Crop state +let cropBox = { + x: 0, + y: 0, + width: 0, + height: 0, +}; +let isDragging = false; +let isResizing = false; +let resizeHandle = null; +let dragStart = { x: 0, y: 0 }; +let cropBoxStart = { x: 0, y: 0, width: 0, height: 0 }; + +// Initialize +document.addEventListener("DOMContentLoaded", () => { + initializeTheme(); + initializeEventListeners(); + loadDefaultBackground(); + addDefaultTexts(); +}); + +// Theme Management +function initializeTheme() { + const savedTheme = localStorage.getItem("theme") || "light"; + document.documentElement.setAttribute("data-theme", savedTheme); +} + +function toggleTheme() { + const currentTheme = document.documentElement.getAttribute("data-theme"); + const newTheme = currentTheme === "light" ? "dark" : "light"; + document.documentElement.setAttribute("data-theme", newTheme); + localStorage.setItem("theme", newTheme); +} + +// Event Listeners +function initializeEventListeners() { + // Theme toggle + document.getElementById("themeToggle").addEventListener("click", toggleTheme); + + // Add text + document.getElementById("addTextBtn").addEventListener("click", addText); + document.getElementById("panelTextInput").addEventListener("keypress", (e) => { + if (e.key === "Enter") addText(); + }); + + // Settings + document.getElementById("fontSize").addEventListener("input", (e) => { + settings.fontSize = parseInt(e.target.value); + document.getElementById("fontSizeValue").textContent = e.target.value; + updateAllPanels(); + }); + + document.getElementById("fontFamily").addEventListener("change", (e) => { + settings.fontFamily = e.target.value; + updateAllPanels(); + }); + + document.getElementById("textColor").addEventListener("input", (e) => { + settings.textColor = e.target.value; + document.getElementById("textColorValue").textContent = e.target.value; + updateAllPanels(); + }); + + document.querySelectorAll(".align-btn").forEach((btn) => { + btn.addEventListener("click", (e) => { + document.querySelectorAll(".align-btn").forEach((b) => b.classList.remove("active")); + btn.classList.add("active"); + settings.alignment = btn.dataset.align; + updateAllPanels(); + }); + }); + + document.getElementById("sidePadding").addEventListener("input", (e) => { + settings.sidePadding = parseInt(e.target.value); + document.getElementById("sidePaddingValue").textContent = e.target.value; + updateAllPanels(); + }); + + document.getElementById("centerOffset").addEventListener("input", (e) => { + settings.centerOffset = parseInt(e.target.value); + document.getElementById("centerOffsetValue").textContent = e.target.value; + updateAllPanels(); + }); + + // Background controls + document.getElementById("uploadBgBtn").addEventListener("click", () => { + document.getElementById("bgImageInput").click(); + }); + + document.getElementById("bgImageInput").addEventListener("change", handleBackgroundUpload); + + document.getElementById("bgBrightness").addEventListener("input", (e) => { + settings.bgBrightness = parseInt(e.target.value); + document.getElementById("bgBrightnessValue").textContent = e.target.value; + drawCropCanvas(); + updateAllPanels(); + }); + + document.getElementById("bgContrast").addEventListener("input", (e) => { + settings.bgContrast = parseInt(e.target.value); + document.getElementById("bgContrastValue").textContent = e.target.value; + drawCropCanvas(); + updateAllPanels(); + }); + + document.getElementById("resetCropBtn").addEventListener("click", resetCrop); + + // Crop canvas events + const canvas = document.getElementById("cropCanvas"); + canvas.addEventListener("mousedown", handleCropMouseDown); + canvas.addEventListener("mousemove", handleCropMouseMove); + canvas.addEventListener("mouseup", handleCropMouseUp); + canvas.addEventListener("mouseleave", handleCropMouseUp); + + // Panel navigation + document.getElementById("prevPanel").addEventListener("click", () => { + if (currentPanelIndex > 0) { + currentPanelIndex--; + displayCurrentPanel(); + } + }); + + document.getElementById("nextPanel").addEventListener("click", () => { + if (currentPanelIndex < panels.length - 1) { + currentPanelIndex++; + displayCurrentPanel(); + } + }); + + document.getElementById("downloadCurrentBtn").addEventListener("click", downloadCurrentPanel); + document.getElementById("downloadAllBtn").addEventListener("click", downloadAllPanels); +} + +// Load default background +function loadDefaultBackground() { + const img = new Image(); + img.crossOrigin = "anonymous"; + img.onload = () => { + originalImage = img; + backgroundImage = img; + initializeCropBox(); + drawCropCanvas(); + updateAllPanels(); + }; + img.src = "https://images.unsplash.com/photo-1579546929518-9e396f3cc809?w=800&h=250&fit=crop"; +} + +// Add default texts +function addDefaultTexts() { + const defaultTexts = ["links", "about me", "projects"]; + defaultTexts.forEach((text) => { + texts.push({ id: Date.now() + Math.random(), text }); + }); + renderTexts(); + updateAllPanels(); +} + +// Add text +function addText() { + const input = document.getElementById("panelTextInput"); + const text = input.value.trim(); + + if (!text) return; + + texts.push({ id: Date.now(), text }); + input.value = ""; + + renderTexts(); + updateAllPanels(); +} + +// Render texts list +function renderTexts() { + const container = document.getElementById("textsList"); + + if (texts.length === 0) { + container.innerHTML = ` +
+ + + +

Добавьте тексты

+
+ `; + return; + } + + container.innerHTML = texts + .map( + (item) => ` +
+ + +
+ `, + ) + .join(""); +} + +// Update text +window.updateText = function (id, newText) { + const item = texts.find((t) => t.id === id); + if (item) { + item.text = newText; + updateAllPanels(); + } +}; + +// Delete text +window.deleteText = function (id) { + texts = texts.filter((t) => t.id !== id); + renderTexts(); + updateAllPanels(); +}; +// Handle background upload +function handleBackgroundUpload(e) { + const file = e.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (event) => { + const img = new Image(); + img.onload = () => { + originalImage = img; + backgroundImage = img; + initializeCropBox(); + drawCropCanvas(); + updateAllPanels(); + }; + img.src = event.target.result; + }; + reader.readAsDataURL(file); +} + +// Initialize crop box +function initializeCropBox() { + const canvas = document.getElementById("cropCanvas"); + const container = document.getElementById("cropCanvasContainer"); + const rect = container.getBoundingClientRect(); + + canvas.width = rect.width; + canvas.height = rect.height; + + // Set crop box to full canvas initially + cropBox = { + x: 0, + y: 0, + width: canvas.width, + height: canvas.height, + }; + + updateCropBoxElement(); + document.getElementById("cropBox").classList.add("active"); +} + +// Draw crop canvas +function drawCropCanvas() { + const canvas = document.getElementById("cropCanvas"); + const ctx = canvas.getContext("2d"); + + if (!originalImage) return; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Apply filters + ctx.filter = `brightness(${settings.bgBrightness}%) contrast(${settings.bgContrast}%)`; + + // Draw image to fit canvas + const scale = Math.max(canvas.width / originalImage.width, canvas.height / originalImage.height); + const scaledWidth = originalImage.width * scale; + const scaledHeight = originalImage.height * scale; + const x = (canvas.width - scaledWidth) / 2; + const y = (canvas.height - scaledHeight) / 2; + + ctx.drawImage(originalImage, x, y, scaledWidth, scaledHeight); + ctx.filter = "none"; +} + +// Update crop box element +function updateCropBoxElement() { + const element = document.getElementById("cropBox"); + element.style.left = cropBox.x + "px"; + element.style.top = cropBox.y + "px"; + element.style.width = cropBox.width + "px"; + element.style.height = cropBox.height + "px"; +} + +// Crop mouse handlers +function handleCropMouseDown(e) { + const rect = e.target.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + // Check if clicking on a handle + const handles = document.querySelectorAll(".crop-handle"); + let clickedHandle = null; + + handles.forEach((handle) => { + const handleRect = handle.getBoundingClientRect(); + const handleX = handleRect.left - rect.left + handleRect.width / 2; + const handleY = handleRect.top - rect.top + handleRect.height / 2; + + if (Math.abs(x - handleX) < 10 && Math.abs(y - handleY) < 10) { + clickedHandle = handle.classList[1]; // Get handle class (nw, ne, etc.) + } + }); + + if (clickedHandle) { + isResizing = true; + resizeHandle = clickedHandle; + } else if ( + x >= cropBox.x && + x <= cropBox.x + cropBox.width && + y >= cropBox.y && + y <= cropBox.y + cropBox.height + ) { + isDragging = true; + } + + dragStart = { x, y }; + cropBoxStart = { ...cropBox }; +} + +function handleCropMouseMove(e) { + if (!isDragging && !isResizing) return; + + const rect = e.target.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const dx = x - dragStart.x; + const dy = y - dragStart.y; + + if (isDragging) { + cropBox.x = Math.max(0, Math.min(cropBoxStart.x + dx, rect.width - cropBox.width)); + cropBox.y = Math.max(0, Math.min(cropBoxStart.y + dy, rect.height - cropBox.height)); + } else if (isResizing) { + const minSize = 50; + + switch (resizeHandle) { + case "nw": + cropBox.x = Math.max( + 0, + Math.min(cropBoxStart.x + dx, cropBoxStart.x + cropBoxStart.width - minSize), + ); + cropBox.y = Math.max( + 0, + Math.min(cropBoxStart.y + dy, cropBoxStart.y + cropBoxStart.height - minSize), + ); + cropBox.width = cropBoxStart.width - (cropBox.x - cropBoxStart.x); + cropBox.height = cropBoxStart.height - (cropBox.y - cropBoxStart.y); + break; + case "ne": + cropBox.y = Math.max( + 0, + Math.min(cropBoxStart.y + dy, cropBoxStart.y + cropBoxStart.height - minSize), + ); + cropBox.width = Math.max( + minSize, + Math.min(cropBoxStart.width + dx, rect.width - cropBoxStart.x), + ); + cropBox.height = cropBoxStart.height - (cropBox.y - cropBoxStart.y); + break; + case "sw": + cropBox.x = Math.max( + 0, + Math.min(cropBoxStart.x + dx, cropBoxStart.x + cropBoxStart.width - minSize), + ); + cropBox.width = cropBoxStart.width - (cropBox.x - cropBoxStart.x); + cropBox.height = Math.max( + minSize, + Math.min(cropBoxStart.height + dy, rect.height - cropBoxStart.y), + ); + break; + case "se": + cropBox.width = Math.max( + minSize, + Math.min(cropBoxStart.width + dx, rect.width - cropBoxStart.x), + ); + cropBox.height = Math.max( + minSize, + Math.min(cropBoxStart.height + dy, rect.height - cropBoxStart.y), + ); + break; + case "n": + cropBox.y = Math.max( + 0, + Math.min(cropBoxStart.y + dy, cropBoxStart.y + cropBoxStart.height - minSize), + ); + cropBox.height = cropBoxStart.height - (cropBox.y - cropBoxStart.y); + break; + case "s": + cropBox.height = Math.max( + minSize, + Math.min(cropBoxStart.height + dy, rect.height - cropBoxStart.y), + ); + break; + case "w": + cropBox.x = Math.max( + 0, + Math.min(cropBoxStart.x + dx, cropBoxStart.x + cropBoxStart.width - minSize), + ); + cropBox.width = cropBoxStart.width - (cropBox.x - cropBoxStart.x); + break; + case "e": + cropBox.width = Math.max( + minSize, + Math.min(cropBoxStart.width + dx, rect.width - cropBoxStart.x), + ); + break; + } + } + + updateCropBoxElement(); +} + +function handleCropMouseUp() { + if (isDragging || isResizing) { + applyCrop(); + } + isDragging = false; + isResizing = false; + resizeHandle = null; +} + +// Apply crop +function applyCrop() { + if (!originalImage) return; + + const canvas = document.getElementById("cropCanvas"); + const tempCanvas = document.createElement("canvas"); + const tempCtx = tempCanvas.getContext("2d"); + + // Calculate scale factor + const scale = Math.max(canvas.width / originalImage.width, canvas.height / originalImage.height); + const scaledWidth = originalImage.width * scale; + const scaledHeight = originalImage.height * scale; + const offsetX = (canvas.width - scaledWidth) / 2; + const offsetY = (canvas.height - scaledHeight) / 2; + + // Calculate crop area in original image coordinates + const cropX = (cropBox.x - offsetX) / scale; + const cropY = (cropBox.y - offsetY) / scale; + const cropWidth = cropBox.width / scale; + const cropHeight = cropBox.height / scale; + + tempCanvas.width = cropWidth; + tempCanvas.height = cropHeight; + + tempCtx.drawImage( + originalImage, + cropX, + cropY, + cropWidth, + cropHeight, + 0, + 0, + cropWidth, + cropHeight, + ); + + const img = new Image(); + img.onload = () => { + backgroundImage = img; + updateAllPanels(); + }; + img.src = tempCanvas.toDataURL(); +} + +// Reset crop +function resetCrop() { + loadDefaultBackground(); +} +// Update all panels +function updateAllPanels() { + panels = texts.map((item) => ({ + id: item.id, + text: item.text, + })); + + currentPanelIndex = Math.min(currentPanelIndex, Math.max(0, panels.length - 1)); + displayCurrentPanel(); + updatePanelNavigation(); +} + +// Display current panel +function displayCurrentPanel() { + const container = document.getElementById("panelDisplay"); + + if (panels.length === 0) { + container.innerHTML = ` +
+ + + + +

Добавьте тексты для создания панелей

+
+ `; + return; + } + + const panel = panels[currentPanelIndex]; + container.innerHTML = ``; + + setTimeout(() => { + drawPanel(panel, "currentPanelCanvas"); + }, 0); +} + +// Update panel navigation +function updatePanelNavigation() { + document.getElementById("panelCount").textContent = panels.length; + document.getElementById("panelIndicator").textContent = + panels.length > 0 ? `${currentPanelIndex + 1} / ${panels.length}` : "0 / 0"; + + document.getElementById("prevPanel").disabled = currentPanelIndex === 0 || panels.length === 0; + document.getElementById("nextPanel").disabled = + currentPanelIndex >= panels.length - 1 || panels.length === 0; + document.getElementById("downloadCurrentBtn").disabled = panels.length === 0; + document.getElementById("downloadAllBtn").disabled = panels.length === 0; +} + +// Draw panel +function drawPanel(panel, canvasId) { + const canvas = document.getElementById(canvasId); + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + canvas.width = 320; + canvas.height = 100; + + // Draw background + if (backgroundImage) { + ctx.filter = `brightness(${settings.bgBrightness}%) contrast(${settings.bgContrast}%)`; + + const scale = Math.max( + canvas.width / backgroundImage.width, + canvas.height / backgroundImage.height, + ); + const scaledWidth = backgroundImage.width * scale; + const scaledHeight = backgroundImage.height * scale; + const x = (canvas.width - scaledWidth) / 2; + const y = (canvas.height - scaledHeight) / 2; + + ctx.drawImage(backgroundImage, x, y, scaledWidth, scaledHeight); + ctx.filter = "none"; + } else { + ctx.fillStyle = "#667eea"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + + // Draw text + ctx.font = `${settings.fontSize}px ${settings.fontFamily}`; + ctx.fillStyle = settings.textColor; + ctx.textBaseline = "middle"; + + const text = panel.text; + + let x; + if (settings.alignment === "left") { + x = settings.sidePadding; + ctx.textAlign = "left"; + } else if (settings.alignment === "center") { + x = canvas.width / 2 + settings.centerOffset; + ctx.textAlign = "center"; + } else { + x = canvas.width - settings.sidePadding; + ctx.textAlign = "right"; + } + + const y = canvas.height / 2; + ctx.fillText(text, x, y); +} + +// Download current panel +function downloadCurrentPanel() { + if (panels.length === 0) return; + + const canvas = document.getElementById("currentPanelCanvas"); + const panel = panels[currentPanelIndex]; + + if (canvas && panel) { + const link = document.createElement("a"); + link.download = `${panel.text}.png`; + link.href = canvas.toDataURL(); + link.click(); + } +} + +// Download all panels +function downloadAllPanels() { + panels.forEach((panel, index) => { + setTimeout(() => { + const tempCanvas = document.createElement("canvas"); + tempCanvas.id = `temp-canvas-${panel.id}`; + document.body.appendChild(tempCanvas); + + drawPanel(panel, tempCanvas.id); + + const link = document.createElement("a"); + link.download = `${panel.text}.png`; + link.href = tempCanvas.toDataURL(); + link.click(); + + document.body.removeChild(tempCanvas); + }, 100 * index); + }); +} diff --git a/reference/styles.css b/reference/styles.css index 13b8474..415a5b5 100644 --- a/reference/styles.css +++ b/reference/styles.css @@ -1,763 +1,763 @@ -:root { - --bg-primary: #ffffff; - --bg-secondary: #fafafa; - --bg-card: #ffffff; - --bg-hover: #f5f5f5; - - --text-primary: #1a1a1a; - --text-secondary: #666666; - --text-tertiary: #999999; - - --border-color: #e5e5e5; - --border-hover: #d4d4d4; - - --accent-primary: #6366f1; - --accent-hover: #4f46e5; - --accent-light: #eef2ff; - - --danger: #ef4444; - - --shadow: 0 1px 3px rgba(0, 0, 0, 0.08); - --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1); - - --radius: 8px; - --transition: all 0.15s ease; -} - -[data-theme="dark"] { - --bg-primary: #0a0a0a; - --bg-secondary: #1a1a1a; - --bg-card: #1a1a1a; - --bg-hover: #2a2a2a; - - --text-primary: #f5f5f5; - --text-secondary: #a3a3a3; - --text-tertiary: #737373; - - --border-color: #2a2a2a; - --border-hover: #3a3a3a; - - --accent-primary: #818cf8; - --accent-hover: #6366f1; - --accent-light: #312e81; - - --shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4); -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; - background: var(--bg-primary); - color: var(--text-primary); - min-height: 100vh; - padding: 16px; - transition: var(--transition); - line-height: 1.5; -} - -.container { - max-width: 1400px; - margin: 0 auto; -} - -/* Header */ -.header { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 16px 20px; - border-radius: var(--radius); - margin-bottom: 16px; -} - -.header-content { - display: flex; - justify-content: space-between; - align-items: center; -} - -.header h1 { - font-size: 20px; - font-weight: 600; -} - -.theme-toggle { - width: 36px; - height: 36px; - border-radius: 6px; - border: none; - background: rgba(255, 255, 255, 0.2); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition); - position: relative; -} - -.theme-toggle:hover { - background: rgba(255, 255, 255, 0.3); -} - -.theme-toggle svg { - width: 18px; - height: 18px; - position: absolute; - transition: var(--transition); -} - -.sun-icon { - opacity: 1; - transform: rotate(0deg); -} - -.moon-icon { - opacity: 0; - transform: rotate(90deg); -} - -[data-theme="dark"] .sun-icon { - opacity: 0; - transform: rotate(90deg); -} - -[data-theme="dark"] .moon-icon { - opacity: 1; - transform: rotate(0deg); -} - -/* Main Grid */ -.main-grid { - display: grid; - grid-template-columns: 1fr; - gap: 16px; -} - -@media (min-width: 1024px) { - .main-grid { - grid-template-columns: 1fr 1fr; - } -} - -/* Card */ -.card { - background: var(--bg-card); - border: 1px solid var(--border-color); - border-radius: var(--radius); - padding: 16px; - margin-bottom: 16px; - transition: var(--transition); -} - -.card-title { - font-size: 15px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 12px; - display: flex; - align-items: center; - gap: 8px; -} - -.card-header-row { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; -} - -/* Badge */ -.badge { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 20px; - height: 20px; - padding: 0 6px; - background: var(--accent-light); - color: var(--accent-primary); - border-radius: 10px; - font-size: 11px; - font-weight: 600; -} - -/* Input Group */ -.input-group { - display: flex; - gap: 8px; - margin-bottom: 12px; -} - -.text-input { - flex: 1; - padding: 10px 12px; - border: 1px solid var(--border-color); - border-radius: var(--radius); - font-size: 14px; - background: var(--bg-primary); - color: var(--text-primary); - transition: var(--transition); - font-family: inherit; -} - -.text-input:focus { - outline: none; - border-color: var(--accent-primary); -} - -.text-input::placeholder { - color: var(--text-tertiary); -} - -/* Buttons */ -.btn { - padding: 10px 16px; - border: none; - border-radius: var(--radius); - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: var(--transition); - display: inline-flex; - align-items: center; - gap: 6px; - font-family: inherit; - white-space: nowrap; -} - -.btn svg { - width: 16px; - height: 16px; -} - -.btn-primary { - background: var(--accent-primary); - color: white; -} - -.btn-primary:hover:not(:disabled) { - background: var(--accent-hover); -} - -.btn-primary:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.btn-secondary { - background: var(--bg-secondary); - color: var(--text-primary); - border: 1px solid var(--border-color); -} - -.btn-secondary:hover { - background: var(--bg-hover); -} - -.btn-outline { - background: transparent; - color: var(--text-secondary); - border: 1px solid var(--border-color); -} - -.btn-outline:hover:not(:disabled) { - background: var(--bg-hover); - border-color: var(--accent-primary); - color: var(--accent-primary); -} - -.btn-outline:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.btn-delete { - background: var(--danger); - color: white; - padding: 6px 10px; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 14px; - font-weight: 600; - transition: var(--transition); - min-width: 32px; - display: flex; - align-items: center; - justify-content: center; -} - -.btn-delete:hover { - background: #dc2626; -} - -/* Texts List */ -.texts-list { - display: flex; - flex-direction: column; - gap: 8px; -} - -.text-item { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 10px; - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: var(--radius); - transition: var(--transition); -} - -.text-item:hover { - border-color: var(--border-hover); -} - -.text-item input { - flex: 1; - padding: 6px 10px; - border: 1px solid var(--border-color); - border-radius: 4px; - font-size: 14px; - background: var(--bg-primary); - color: var(--text-primary); - transition: var(--transition); - font-family: inherit; -} - -.text-item input:focus { - outline: none; - border-color: var(--accent-primary); -} - -/* Settings */ -.settings-grid { - display: flex; - flex-direction: column; - gap: 12px; -} - -.setting-row { - display: grid; - grid-template-columns: 100px 1fr; - align-items: center; - gap: 12px; -} - -.setting-label { - color: var(--text-secondary); - font-size: 13px; - font-weight: 500; -} - -.setting-control { - display: flex; - align-items: center; - gap: 10px; -} - -.slider { - flex: 1; - height: 4px; - border-radius: 2px; - background: var(--bg-secondary); - outline: none; - -webkit-appearance: none; - appearance: none; -} - -.slider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--accent-primary); - cursor: pointer; - transition: var(--transition); -} - -.slider::-webkit-slider-thumb:hover { - transform: scale(1.1); -} - -.slider::-moz-range-thumb { - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--accent-primary); - cursor: pointer; - border: none; - transition: var(--transition); -} - -.slider::-moz-range-thumb:hover { - transform: scale(1.1); -} - -.value-display { - color: var(--text-primary); - font-weight: 500; - min-width: 40px; - text-align: right; - font-size: 13px; - padding: 4px 8px; - background: var(--bg-secondary); - border-radius: 4px; -} - -.select-input { - padding: 8px 10px; - border: 1px solid var(--border-color); - border-radius: var(--radius); - background: var(--bg-primary); - color: var(--text-primary); - font-size: 13px; - cursor: pointer; - transition: var(--transition); - font-family: inherit; - width: 100%; -} - -.select-input:focus { - outline: none; - border-color: var(--accent-primary); -} - -.color-input { - width: 40px; - height: 32px; - border: 1px solid var(--border-color); - border-radius: var(--radius); - cursor: pointer; - transition: var(--transition); - background: var(--bg-primary); -} - -.color-input:hover { - border-color: var(--accent-primary); -} - -.color-value { - font-family: 'Courier New', monospace; - font-size: 12px; - font-weight: 500; - color: var(--text-secondary); - padding: 4px 8px; - background: var(--bg-secondary); - border-radius: 4px; -} - -.alignment-buttons { - display: flex; - gap: 6px; -} - -.align-btn { - flex: 1; - padding: 8px; - border: 1px solid var(--border-color); - background: var(--bg-primary); - border-radius: var(--radius); - cursor: pointer; - transition: var(--transition); - display: flex; - align-items: center; - justify-content: center; -} - -.align-btn svg { - width: 16px; - height: 16px; - stroke: var(--text-secondary); - transition: var(--transition); -} - -.align-btn:hover { - background: var(--bg-hover); - border-color: var(--accent-primary); -} - -.align-btn:hover svg { - stroke: var(--accent-primary); -} - -.align-btn.active { - background: var(--accent-primary); - border-color: var(--accent-primary); -} - -.align-btn.active svg { - stroke: white; -} - -/* Crop Editor */ -.crop-editor { - display: flex; - flex-direction: column; - gap: 12px; -} - -.crop-canvas-container { - position: relative; - width: 100%; - aspect-ratio: 16 / 5; - background: var(--bg-secondary); - border-radius: var(--radius); - overflow: hidden; - cursor: crosshair; -} - -.crop-canvas { - width: 100%; - height: 100%; - display: block; -} - -.crop-box { - position: absolute; - border: 2px solid var(--accent-primary); - box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); - cursor: move; - display: none; -} - -.crop-box.active { - display: block; -} - -.crop-handle { - position: absolute; - width: 10px; - height: 10px; - background: white; - border: 2px solid var(--accent-primary); - border-radius: 50%; -} - -.crop-handle.nw { - top: -5px; - left: -5px; - cursor: nw-resize; -} - -.crop-handle.ne { - top: -5px; - right: -5px; - cursor: ne-resize; -} - -.crop-handle.sw { - bottom: -5px; - left: -5px; - cursor: sw-resize; -} - -.crop-handle.se { - bottom: -5px; - right: -5px; - cursor: se-resize; -} - -.crop-handle.n { - top: -5px; - left: 50%; - transform: translateX(-50%); - cursor: n-resize; -} - -.crop-handle.s { - bottom: -5px; - left: 50%; - transform: translateX(-50%); - cursor: s-resize; -} - -.crop-handle.w { - left: -5px; - top: 50%; - transform: translateY(-50%); - cursor: w-resize; -} - -.crop-handle.e { - right: -5px; - top: 50%; - transform: translateY(-50%); - cursor: e-resize; -} - -.crop-controls { - display: flex; - gap: 8px; - flex-wrap: wrap; -} - -/* Panel Viewer */ -.panel-nav { - display: flex; - align-items: center; - gap: 8px; -} - -.nav-btn { - width: 32px; - height: 32px; - padding: 0; - border: 1px solid var(--border-color); - background: var(--bg-primary); - border-radius: var(--radius); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition); -} - -.nav-btn:hover:not(:disabled) { - background: var(--bg-hover); - border-color: var(--accent-primary); -} - -.nav-btn:hover:not(:disabled) svg { - stroke: var(--accent-primary); -} - -.nav-btn:disabled { - opacity: 0.3; - cursor: not-allowed; -} - -.nav-btn svg { - width: 16px; - height: 16px; - stroke: var(--text-secondary); -} - -.panel-indicator { - font-size: 13px; - color: var(--text-secondary); - font-weight: 500; - min-width: 50px; - text-align: center; -} - -.panel-viewer { - display: flex; - flex-direction: column; - gap: 12px; -} - -.panel-display { - background: var(--bg-secondary); - border-radius: var(--radius); - padding: 20px; - display: flex; - justify-content: center; - align-items: center; - min-height: 150px; -} - -.panel-canvas { - border-radius: var(--radius); - display: block; - max-width: 100%; - box-shadow: var(--shadow-md); -} - -.panel-actions { - display: flex; - gap: 8px; -} - -.panel-actions .btn { - flex: 1; - justify-content: center; -} - -/* Empty State */ -.empty-state { - text-align: center; - padding: 32px 16px; - color: var(--text-tertiary); -} - -.empty-state svg { - width: 48px; - height: 48px; - margin: 0 auto 12px; - opacity: 0.5; -} - -.empty-state p { - font-size: 14px; -} - -/* Responsive */ -@media (max-width: 768px) { - body { - padding: 12px; - } - - .header { - padding: 12px 16px; - } - - .header h1 { - font-size: 18px; - } - - .setting-row { - grid-template-columns: 80px 1fr; - gap: 10px; - } - - .crop-controls { - flex-direction: column; - } - - .crop-controls .btn { - width: 100%; - justify-content: center; - } -} - -/* Animations */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(4px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.text-item { - animation: fadeIn 0.2s ease-out; -} - -/* Scrollbar */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: var(--bg-secondary); -} - -::-webkit-scrollbar-thumb { - background: var(--border-color); - border-radius: 4px; -} - -::-webkit-scrollbar-thumb:hover { - background: var(--border-hover); -} +:root { + --bg-primary: #ffffff; + --bg-secondary: #fafafa; + --bg-card: #ffffff; + --bg-hover: #f5f5f5; + + --text-primary: #1a1a1a; + --text-secondary: #666666; + --text-tertiary: #999999; + + --border-color: #e5e5e5; + --border-hover: #d4d4d4; + + --accent-primary: #6366f1; + --accent-hover: #4f46e5; + --accent-light: #eef2ff; + + --danger: #ef4444; + + --shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1); + + --radius: 8px; + --transition: all 0.15s ease; +} + +[data-theme="dark"] { + --bg-primary: #0a0a0a; + --bg-secondary: #1a1a1a; + --bg-card: #1a1a1a; + --bg-hover: #2a2a2a; + + --text-primary: #f5f5f5; + --text-secondary: #a3a3a3; + --text-tertiary: #737373; + + --border-color: #2a2a2a; + --border-hover: #3a3a3a; + + --accent-primary: #818cf8; + --accent-hover: #6366f1; + --accent-light: #312e81; + + --shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + min-height: 100vh; + padding: 16px; + transition: var(--transition); + line-height: 1.5; +} + +.container { + max-width: 1400px; + margin: 0 auto; +} + +/* Header */ +.header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 16px 20px; + border-radius: var(--radius); + margin-bottom: 16px; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; +} + +.header h1 { + font-size: 20px; + font-weight: 600; +} + +.theme-toggle { + width: 36px; + height: 36px; + border-radius: 6px; + border: none; + background: rgba(255, 255, 255, 0.2); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: var(--transition); + position: relative; +} + +.theme-toggle:hover { + background: rgba(255, 255, 255, 0.3); +} + +.theme-toggle svg { + width: 18px; + height: 18px; + position: absolute; + transition: var(--transition); +} + +.sun-icon { + opacity: 1; + transform: rotate(0deg); +} + +.moon-icon { + opacity: 0; + transform: rotate(90deg); +} + +[data-theme="dark"] .sun-icon { + opacity: 0; + transform: rotate(90deg); +} + +[data-theme="dark"] .moon-icon { + opacity: 1; + transform: rotate(0deg); +} + +/* Main Grid */ +.main-grid { + display: grid; + grid-template-columns: 1fr; + gap: 16px; +} + +@media (min-width: 1024px) { + .main-grid { + grid-template-columns: 1fr 1fr; + } +} + +/* Card */ +.card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius); + padding: 16px; + margin-bottom: 16px; + transition: var(--transition); +} + +.card-title { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 12px; + display: flex; + align-items: center; + gap: 8px; +} + +.card-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +/* Badge */ +.badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + background: var(--accent-light); + color: var(--accent-primary); + border-radius: 10px; + font-size: 11px; + font-weight: 600; +} + +/* Input Group */ +.input-group { + display: flex; + gap: 8px; + margin-bottom: 12px; +} + +.text-input { + flex: 1; + padding: 10px 12px; + border: 1px solid var(--border-color); + border-radius: var(--radius); + font-size: 14px; + background: var(--bg-primary); + color: var(--text-primary); + transition: var(--transition); + font-family: inherit; +} + +.text-input:focus { + outline: none; + border-color: var(--accent-primary); +} + +.text-input::placeholder { + color: var(--text-tertiary); +} + +/* Buttons */ +.btn { + padding: 10px 16px; + border: none; + border-radius: var(--radius); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + display: inline-flex; + align-items: center; + gap: 6px; + font-family: inherit; + white-space: nowrap; +} + +.btn svg { + width: 16px; + height: 16px; +} + +.btn-primary { + background: var(--accent-primary); + color: white; +} + +.btn-primary:hover:not(:disabled) { + background: var(--accent-hover); +} + +.btn-primary:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-secondary { + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.btn-secondary:hover { + background: var(--bg-hover); +} + +.btn-outline { + background: transparent; + color: var(--text-secondary); + border: 1px solid var(--border-color); +} + +.btn-outline:hover:not(:disabled) { + background: var(--bg-hover); + border-color: var(--accent-primary); + color: var(--accent-primary); +} + +.btn-outline:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-delete { + background: var(--danger); + color: white; + padding: 6px 10px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 600; + transition: var(--transition); + min-width: 32px; + display: flex; + align-items: center; + justify-content: center; +} + +.btn-delete:hover { + background: #dc2626; +} + +/* Texts List */ +.texts-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.text-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius); + transition: var(--transition); +} + +.text-item:hover { + border-color: var(--border-hover); +} + +.text-item input { + flex: 1; + padding: 6px 10px; + border: 1px solid var(--border-color); + border-radius: 4px; + font-size: 14px; + background: var(--bg-primary); + color: var(--text-primary); + transition: var(--transition); + font-family: inherit; +} + +.text-item input:focus { + outline: none; + border-color: var(--accent-primary); +} + +/* Settings */ +.settings-grid { + display: flex; + flex-direction: column; + gap: 12px; +} + +.setting-row { + display: grid; + grid-template-columns: 100px 1fr; + align-items: center; + gap: 12px; +} + +.setting-label { + color: var(--text-secondary); + font-size: 13px; + font-weight: 500; +} + +.setting-control { + display: flex; + align-items: center; + gap: 10px; +} + +.slider { + flex: 1; + height: 4px; + border-radius: 2px; + background: var(--bg-secondary); + outline: none; + -webkit-appearance: none; + appearance: none; +} + +.slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--accent-primary); + cursor: pointer; + transition: var(--transition); +} + +.slider::-webkit-slider-thumb:hover { + transform: scale(1.1); +} + +.slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--accent-primary); + cursor: pointer; + border: none; + transition: var(--transition); +} + +.slider::-moz-range-thumb:hover { + transform: scale(1.1); +} + +.value-display { + color: var(--text-primary); + font-weight: 500; + min-width: 40px; + text-align: right; + font-size: 13px; + padding: 4px 8px; + background: var(--bg-secondary); + border-radius: 4px; +} + +.select-input { + padding: 8px 10px; + border: 1px solid var(--border-color); + border-radius: var(--radius); + background: var(--bg-primary); + color: var(--text-primary); + font-size: 13px; + cursor: pointer; + transition: var(--transition); + font-family: inherit; + width: 100%; +} + +.select-input:focus { + outline: none; + border-color: var(--accent-primary); +} + +.color-input { + width: 40px; + height: 32px; + border: 1px solid var(--border-color); + border-radius: var(--radius); + cursor: pointer; + transition: var(--transition); + background: var(--bg-primary); +} + +.color-input:hover { + border-color: var(--accent-primary); +} + +.color-value { + font-family: "Courier New", monospace; + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); + padding: 4px 8px; + background: var(--bg-secondary); + border-radius: 4px; +} + +.alignment-buttons { + display: flex; + gap: 6px; +} + +.align-btn { + flex: 1; + padding: 8px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + border-radius: var(--radius); + cursor: pointer; + transition: var(--transition); + display: flex; + align-items: center; + justify-content: center; +} + +.align-btn svg { + width: 16px; + height: 16px; + stroke: var(--text-secondary); + transition: var(--transition); +} + +.align-btn:hover { + background: var(--bg-hover); + border-color: var(--accent-primary); +} + +.align-btn:hover svg { + stroke: var(--accent-primary); +} + +.align-btn.active { + background: var(--accent-primary); + border-color: var(--accent-primary); +} + +.align-btn.active svg { + stroke: white; +} + +/* Crop Editor */ +.crop-editor { + display: flex; + flex-direction: column; + gap: 12px; +} + +.crop-canvas-container { + position: relative; + width: 100%; + aspect-ratio: 16 / 5; + background: var(--bg-secondary); + border-radius: var(--radius); + overflow: hidden; + cursor: crosshair; +} + +.crop-canvas { + width: 100%; + height: 100%; + display: block; +} + +.crop-box { + position: absolute; + border: 2px solid var(--accent-primary); + box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); + cursor: move; + display: none; +} + +.crop-box.active { + display: block; +} + +.crop-handle { + position: absolute; + width: 10px; + height: 10px; + background: white; + border: 2px solid var(--accent-primary); + border-radius: 50%; +} + +.crop-handle.nw { + top: -5px; + left: -5px; + cursor: nw-resize; +} + +.crop-handle.ne { + top: -5px; + right: -5px; + cursor: ne-resize; +} + +.crop-handle.sw { + bottom: -5px; + left: -5px; + cursor: sw-resize; +} + +.crop-handle.se { + bottom: -5px; + right: -5px; + cursor: se-resize; +} + +.crop-handle.n { + top: -5px; + left: 50%; + transform: translateX(-50%); + cursor: n-resize; +} + +.crop-handle.s { + bottom: -5px; + left: 50%; + transform: translateX(-50%); + cursor: s-resize; +} + +.crop-handle.w { + left: -5px; + top: 50%; + transform: translateY(-50%); + cursor: w-resize; +} + +.crop-handle.e { + right: -5px; + top: 50%; + transform: translateY(-50%); + cursor: e-resize; +} + +.crop-controls { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +/* Panel Viewer */ +.panel-nav { + display: flex; + align-items: center; + gap: 8px; +} + +.nav-btn { + width: 32px; + height: 32px; + padding: 0; + border: 1px solid var(--border-color); + background: var(--bg-primary); + border-radius: var(--radius); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: var(--transition); +} + +.nav-btn:hover:not(:disabled) { + background: var(--bg-hover); + border-color: var(--accent-primary); +} + +.nav-btn:hover:not(:disabled) svg { + stroke: var(--accent-primary); +} + +.nav-btn:disabled { + opacity: 0.3; + cursor: not-allowed; +} + +.nav-btn svg { + width: 16px; + height: 16px; + stroke: var(--text-secondary); +} + +.panel-indicator { + font-size: 13px; + color: var(--text-secondary); + font-weight: 500; + min-width: 50px; + text-align: center; +} + +.panel-viewer { + display: flex; + flex-direction: column; + gap: 12px; +} + +.panel-display { + background: var(--bg-secondary); + border-radius: var(--radius); + padding: 20px; + display: flex; + justify-content: center; + align-items: center; + min-height: 150px; +} + +.panel-canvas { + border-radius: var(--radius); + display: block; + max-width: 100%; + box-shadow: var(--shadow-md); +} + +.panel-actions { + display: flex; + gap: 8px; +} + +.panel-actions .btn { + flex: 1; + justify-content: center; +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: 32px 16px; + color: var(--text-tertiary); +} + +.empty-state svg { + width: 48px; + height: 48px; + margin: 0 auto 12px; + opacity: 0.5; +} + +.empty-state p { + font-size: 14px; +} + +/* Responsive */ +@media (max-width: 768px) { + body { + padding: 12px; + } + + .header { + padding: 12px 16px; + } + + .header h1 { + font-size: 18px; + } + + .setting-row { + grid-template-columns: 80px 1fr; + gap: 10px; + } + + .crop-controls { + flex-direction: column; + } + + .crop-controls .btn { + width: 100%; + justify-content: center; + } +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.text-item { + animation: fadeIn 0.2s ease-out; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--border-hover); +} diff --git a/scripts/css-vars.js b/scripts/css-vars.js index 7a393a3..d512288 100644 --- a/scripts/css-vars.js +++ b/scripts/css-vars.js @@ -1,61 +1,61 @@ -import { readFileSync } from "fs"; -import { globSync } from "glob"; - -const includePatterns = ["src/**/*.svelte", "src/**/*.css"]; - -const files = globSync(includePatterns); - -const defined = new Set(); -const used = new Set(); -const usageLocations = []; - -files.forEach((file) => { - const content = readFileSync(file, "utf-8"); - const lines = content.split("\n"); - - const defMatches = content.matchAll(/--[\w-]+(?=\s*:)/g); - for (const m of defMatches) { - defined.add(m[0]); - } - - lines.forEach((line, idx) => { - const varMatches = line.matchAll(/var\(\s*--[\w-]+\s*\)/g); - for (const match of varMatches) { - const varName = match[0].slice(4, -1).trim(); - used.add(varName); - - usageLocations.push({ - file, - line: idx + 1, - column: match.index + 1, - varName, - }); - } - }); -}); - -let hasUndefined = false; -const undefinedErrors = usageLocations.filter((loc) => !defined.has(loc.varName)); - -undefinedErrors.forEach(({ file, line, column, varName }) => { - console.error(`❌ ${file}:${line}:${column} — не определена CSS-переменная "${varName}"`); - hasUndefined = true; -}); - -const unused = [...defined].filter((varName) => !used.has(varName)); -if (unused.length > 0) { - console.warn("\n⚠️ Объявлены, но нигде не используются:"); - unused.forEach((varName) => console.warn(` ${varName}`)); -} - -if (hasUndefined) { - console.error("\n❌ Найдены неопределённые переменные. Исправьте их."); - process.exit(1); -} else { - if (unused.length === 0) { - console.log("\n✅ Все CSS-переменные определены и используются."); - } else { - console.log("\n✅ Неопределённых переменных нет, но есть неиспользуемые (см. предупреждения)."); - } - process.exit(0); -} +import { readFileSync } from "fs"; +import { globSync } from "glob"; + +const includePatterns = ["src/**/*.svelte", "src/**/*.css"]; + +const files = globSync(includePatterns); + +const defined = new Set(); +const used = new Set(); +const usageLocations = []; + +files.forEach((file) => { + const content = readFileSync(file, "utf-8"); + const lines = content.split("\n"); + + const defMatches = content.matchAll(/--[\w-]+(?=\s*:)/g); + for (const m of defMatches) { + defined.add(m[0]); + } + + lines.forEach((line, idx) => { + const varMatches = line.matchAll(/var\(\s*--[\w-]+\s*\)/g); + for (const match of varMatches) { + const varName = match[0].slice(4, -1).trim(); + used.add(varName); + + usageLocations.push({ + file, + line: idx + 1, + column: match.index + 1, + varName, + }); + } + }); +}); + +let hasUndefined = false; +const undefinedErrors = usageLocations.filter((loc) => !defined.has(loc.varName)); + +undefinedErrors.forEach(({ file, line, column, varName }) => { + console.error(`❌ ${file}:${line}:${column} — не определена CSS-переменная "${varName}"`); + hasUndefined = true; +}); + +const unused = [...defined].filter((varName) => !used.has(varName)); +if (unused.length > 0) { + console.warn("\n⚠️ Объявлены, но нигде не используются:"); + unused.forEach((varName) => console.warn(` ${varName}`)); +} + +if (hasUndefined) { + console.error("\n❌ Найдены неопределённые переменные. Исправьте их."); + process.exit(1); +} else { + if (unused.length === 0) { + console.log("\n✅ Все CSS-переменные определены и используются."); + } else { + console.log("\n✅ Неопределённых переменных нет, но есть неиспользуемые (см. предупреждения)."); + } + process.exit(0); +} diff --git a/src/app.css b/src/app.css index 65cf94d..d127ba2 100644 --- a/src/app.css +++ b/src/app.css @@ -1,79 +1,79 @@ -:root { - --brand-main: #86efac; - --brand-alt: oklch(from var(--brand-main) l c calc(h + 36)); - - --action-lightness: 0.6; - --action-chroma: 0.18; - --hover-step: -0.08; - - --danger-base: #ef4444; - - --surface-main: oklch(from var(--brand-main) 0.99 0.02 h); - --surface-subtle: oklch(from var(--brand-main) 0.95 0.1 h); - --surface-elevated: oklch(from var(--brand-main) 0.99 0.05 h); - --surface-control: oklch(from var(--brand-main) 0.85 0.04 h); - --surface-hover: oklch(from var(--brand-main) 0.9 0.1 h); - - --action-primary: oklch(from var(--brand-main) var(--action-lightness) var(--action-chroma) h); - --action-primary-hover: oklch(from var(--action-primary) calc(l - var(--hover-step)) c h); - --action-secondary: oklch(from var(--brand-alt) var(--action-lightness) var(--action-chroma) h); - --action-secondary-hover: oklch(from var(--action-secondary) calc(l - var(--hover-step)) c h); - - --text-main: oklch(from var(--brand-main) 0.25 0.02 h); - --text-muted: oklch(from var(--brand-main) 0.45 0.02 h); - --text-action: oklch(from var(--brand-main) 0.99 0.02 h); - - --border-main: oklch(from var(--brand-main) 0.9 0.04 h); - --border-strong: oklch(from var(--brand-main) 0.8 0.06 h); - - --shadow: 0 1px 3px oklch(from var(--brand-main) 0.2 0.05 h / 0.1); - --shadow-md: 0 4px 12px oklch(from var(--brand-main) 0.2 0.05 h / 0.1); - - --radius: 8px; - --transition: all 0.3s ease; -} - -[data-theme="dark"] { - --action-lightness: 0.5; - --action-chroma: 0.18; - - --surface-main: oklch(from var(--brand-main) 0.12 0.02 h); - --surface-subtle: oklch(from var(--brand-main) 0.16 0.03 h); - --surface-elevated: oklch(from var(--brand-main) 0.2 0.03 h); - --surface-control: oklch(from var(--brand-main) 0.4 0.06 h); - --surface-hover: oklch(from var(--brand-main) 0.24 0.04 h); - - --action-primary-hover: oklch(from var(--action-primary) calc(l + var(--hover-step)) c h); - --action-secondary-hover: oklch(from var(--action-secondary) calc(l + var(--hover-step)) c h); - - --text-main: oklch(from var(--brand-main) 0.94 0.01 h); - --text-muted: oklch(from var(--brand-main) 0.75 0.02 h); - --text-action: oklch(from var(--brand-main) 0.12 0.02 h); - - --border-main: oklch(from var(--brand-main) 0.22 0.04 h); - --border-strong: oklch(from var(--brand-main) 0.3 0.06 h); - - --shadow: 0 1px 3px rgba(0, 0, 0, 0.5); - --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5); -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -ul { - list-style: none; - text-indent: 0; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif; - background: var(--surface-main); - color: var(--text-main); - min-height: 100vh; - padding: 16px; - transition: var(--transition); - line-height: 1.5; -} +:root { + --brand-main: #86efac; + --brand-alt: oklch(from var(--brand-main) l c calc(h + 36)); + + --action-lightness: 0.6; + --action-chroma: 0.18; + --hover-step: -0.08; + + --danger-base: #ef4444; + + --surface-main: oklch(from var(--brand-main) 0.99 0.02 h); + --surface-subtle: oklch(from var(--brand-main) 0.95 0.1 h); + --surface-elevated: oklch(from var(--brand-main) 0.99 0.05 h); + --surface-control: oklch(from var(--brand-main) 0.85 0.04 h); + --surface-hover: oklch(from var(--brand-main) 0.9 0.1 h); + + --action-primary: oklch(from var(--brand-main) var(--action-lightness) var(--action-chroma) h); + --action-primary-hover: oklch(from var(--action-primary) calc(l - var(--hover-step)) c h); + --action-secondary: oklch(from var(--brand-alt) var(--action-lightness) var(--action-chroma) h); + --action-secondary-hover: oklch(from var(--action-secondary) calc(l - var(--hover-step)) c h); + + --text-main: oklch(from var(--brand-main) 0.25 0.02 h); + --text-muted: oklch(from var(--brand-main) 0.45 0.02 h); + --text-action: oklch(from var(--brand-main) 0.99 0.02 h); + + --border-main: oklch(from var(--brand-main) 0.9 0.04 h); + --border-strong: oklch(from var(--brand-main) 0.8 0.06 h); + + --shadow: 0 1px 3px oklch(from var(--brand-main) 0.2 0.05 h / 0.1); + --shadow-md: 0 4px 12px oklch(from var(--brand-main) 0.2 0.05 h / 0.1); + + --radius: 8px; + --transition: all 0.3s ease; +} + +[data-theme="dark"] { + --action-lightness: 0.5; + --action-chroma: 0.18; + + --surface-main: oklch(from var(--brand-main) 0.12 0.02 h); + --surface-subtle: oklch(from var(--brand-main) 0.16 0.03 h); + --surface-elevated: oklch(from var(--brand-main) 0.2 0.03 h); + --surface-control: oklch(from var(--brand-main) 0.4 0.06 h); + --surface-hover: oklch(from var(--brand-main) 0.24 0.04 h); + + --action-primary-hover: oklch(from var(--action-primary) calc(l + var(--hover-step)) c h); + --action-secondary-hover: oklch(from var(--action-secondary) calc(l + var(--hover-step)) c h); + + --text-main: oklch(from var(--brand-main) 0.94 0.01 h); + --text-muted: oklch(from var(--brand-main) 0.75 0.02 h); + /* --text-action: oklch(from var(--brand-main) 0.12 0.02 h); */ + + --border-main: oklch(from var(--brand-main) 0.22 0.04 h); + --border-strong: oklch(from var(--brand-main) 0.3 0.06 h); + + --shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +ul { + list-style: none; + text-indent: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif; + background: var(--surface-main); + color: var(--text-main); + min-height: 100vh; + padding: 16px; + transition: var(--transition); + line-height: 1.5; +} diff --git a/src/components/image/CropInline.svelte b/src/components/image/CropInline.svelte index 447fbee..1e4e63a 100644 --- a/src/components/image/CropInline.svelte +++ b/src/components/image/CropInline.svelte @@ -1,108 +1,108 @@ - - -
- -
-
-
-
-
-
-
-
-
-
-
- - + + +
+ +
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/src/components/image/ImageManager.svelte b/src/components/image/ImageManager.svelte index ed9f239..0996c3d 100644 --- a/src/components/image/ImageManager.svelte +++ b/src/components/image/ImageManager.svelte @@ -1,52 +1,52 @@ - - - -
- - -
-
- - - - - - - - - -
-
- - + + + +
+ + +
+
+ + + + + + + + + +
+
+ + diff --git a/src/components/layout/AppHeader.svelte b/src/components/layout/AppHeader.svelte index 6bd90d0..8586718 100644 --- a/src/components/layout/AppHeader.svelte +++ b/src/components/layout/AppHeader.svelte @@ -1,86 +1,86 @@ - - -
-
-

Twitch Panels

- -
-
- - + + +
+
+

Twitch Panels

+ +
+
+ + diff --git a/src/components/layout/Card.svelte b/src/components/layout/Card.svelte index 780fe62..61e3c55 100644 --- a/src/components/layout/Card.svelte +++ b/src/components/layout/Card.svelte @@ -1,68 +1,67 @@ - - - -{#snippet emptySnippet()}{/snippet} - -
-
-

- {#if typeof title === "string"} - {title} - {:else} - {@render title()} - {/if} -

-
- {@render titleSnippet()} -
-
-
- {@render children()} -
-
- - + + +{#snippet emptySnippet()}{/snippet} + +
+
+

+ {#if typeof title === "string"} + {title} + {:else} + {@render title()} + {/if} +

+
+ {@render titleSnippet()} +
+
+
+ {@render children()} +
+
+ + diff --git a/src/components/layout/InputGroup.svelte b/src/components/layout/InputGroup.svelte index 49bc066..a819ae9 100644 --- a/src/components/layout/InputGroup.svelte +++ b/src/components/layout/InputGroup.svelte @@ -1,21 +1,21 @@ - - -
- {@render children()} -
- - + + +
+ {@render children()} +
+ + diff --git a/src/components/layout/PanelBar.svelte b/src/components/layout/PanelBar.svelte index bee1ccd..d29c1be 100644 --- a/src/components/layout/PanelBar.svelte +++ b/src/components/layout/PanelBar.svelte @@ -1,9 +1,9 @@ - - -
- - -
+ + +
+ + +
diff --git a/src/components/layout/SettingsGrid.svelte b/src/components/layout/SettingsGrid.svelte index 0f19c6e..e5b6115 100644 --- a/src/components/layout/SettingsGrid.svelte +++ b/src/components/layout/SettingsGrid.svelte @@ -1,21 +1,21 @@ - - -
- {@render children()} -
- - + + +
+ {@render children()} +
+ + diff --git a/src/components/layout/SettingsRow.svelte b/src/components/layout/SettingsRow.svelte index 5150a7f..8f2f99c 100644 --- a/src/components/layout/SettingsRow.svelte +++ b/src/components/layout/SettingsRow.svelte @@ -1,41 +1,41 @@ - - - -
{label}
-
- {@render children()} -
-
- - + + + +
{label}
+
+ {@render children()} +
+
+ + diff --git a/src/components/layout/TextBar.svelte b/src/components/layout/TextBar.svelte index 8597b4b..6282c95 100644 --- a/src/components/layout/TextBar.svelte +++ b/src/components/layout/TextBar.svelte @@ -1,9 +1,9 @@ - - -
- - -
+ + +
+ + +
diff --git a/src/components/panel/Preview.svelte b/src/components/panel/Preview.svelte index 6d2bf34..f3a3308 100644 --- a/src/components/panel/Preview.svelte +++ b/src/components/panel/Preview.svelte @@ -1,37 +1,37 @@ - - - - - - - - + + + + + + + + diff --git a/src/components/panel/PreviewAll.svelte b/src/components/panel/PreviewAll.svelte index dc3d1ad..2f9da94 100644 --- a/src/components/panel/PreviewAll.svelte +++ b/src/components/panel/PreviewAll.svelte @@ -1,22 +1,22 @@ - - -
- {#each textsState.texts as { text, id }, idx (id)} - - {:else} -

No texts to preview

- {/each} -
- - + + +
+ {#each textsState.texts as { text, id }, idx (id)} + + {:else} +

No texts to preview

+ {/each} +
+ + diff --git a/src/components/panel/PreviewControls.svelte b/src/components/panel/PreviewControls.svelte index 427ca51..e9f8b8d 100644 --- a/src/components/panel/PreviewControls.svelte +++ b/src/components/panel/PreviewControls.svelte @@ -1,40 +1,46 @@ - - - - - + + + + + diff --git a/src/components/ui/ColorPicker.svelte b/src/components/ui/ColorPicker.svelte index b487725..ad9e731 100644 --- a/src/components/ui/ColorPicker.svelte +++ b/src/components/ui/ColorPicker.svelte @@ -1,40 +1,40 @@ - - - -{value} - - + + + +{value} + + diff --git a/src/components/ui/RangeSlider.svelte b/src/components/ui/RangeSlider.svelte index 08b2362..06b149d 100644 --- a/src/components/ui/RangeSlider.svelte +++ b/src/components/ui/RangeSlider.svelte @@ -1,68 +1,68 @@ - - - -{value} - - + + + +{value} + + diff --git a/src/components/ui/SelectFont.svelte b/src/components/ui/SelectFont.svelte index c4f2956..2fd848b 100644 --- a/src/components/ui/SelectFont.svelte +++ b/src/components/ui/SelectFont.svelte @@ -1,38 +1,38 @@ - - - - - + + + + + diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 82064b8..94811b8 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,77 +1,77 @@ -// Application Constants - -// ===== PANEL SETTINGS ===== -export const PANEL_SETTINGS = { - PANEL_WIDTH: 320, - PANEL_HEIGHT_DEFAULT: 100, - PANEL_HEIGHT_MAX: 200, - - DEFAULT_BACKGROUND_IMAGE: "./backgrounds/b1.jpg", -} as const; - -// ===== TYPOGRAPHY ===== -export const TYPOGRAPHY = { - FONT_FAMILY_DEFAULT: "Arial", - FONT_FAMILIES: [ - "Arial", - "Verdana", - "Georgia", - "Times New Roman", - "Courier New", - "Impact", - "Comic Sans MS", - "Trebuchet MS", - ], - - // Font sizes for range inputs - FONT_SIZE_MIN: 10, - FONT_SIZE_MAX: 72, - FONT_SIZE_DEFAULT: 32, - - // Text limits - MAX_TEXT_LENGTH: 100, - - // Padding for range inputs - PADDING_X_DEFAULT: 10, - PADDING_X_MAX: 100, - - // Vertical offset for range inputs - VERTICAL_OFFSET_MAX: 100, - VERTICAL_OFFSET_MIN: -100, - - // Colors - TEXT_COLOR_DEFAULT: "#ffffff", -} as const; - -// ===== IMAGE SETTINGS ===== -export const IMAGE_SETTINGS = { - MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB - - SUPPORTED_FORMATS: ["image/jpeg", "image/jpg", "image/png", "image/webp", "image/gif"] as const, -} as const; - -export const SlideDirection = { - NEXT: "next", - PREV: "prev", -} as const; - -export type SlideDirectionType = (typeof SlideDirection)[keyof typeof SlideDirection]; - -export const TextAlign = { - LEFT: "left", - CENTER: "center", - RIGHT: "right", -} as const; - -export type TextAlignType = (typeof TextAlign)[keyof typeof TextAlign]; - -export const DEFAULT_TEXT_ALIGN: TextAlignType = TextAlign.CENTER; - -export const Theme = { - DARK: "dark", - LIGHT: "light", -} as const; - -export type ThemeType = (typeof Theme)[keyof typeof Theme]; - -export const TRANSITION_DURATION = import.meta.env.MODE === "test" ? 0 : 300; +// Application Constants + +// ===== PANEL SETTINGS ===== +export const PANEL_SETTINGS = { + PANEL_WIDTH: 320, + PANEL_HEIGHT_DEFAULT: 100, + PANEL_HEIGHT_MAX: 200, + + DEFAULT_BACKGROUND_IMAGE: "./backgrounds/b1.jpg", +} as const; + +// ===== TYPOGRAPHY ===== +export const TYPOGRAPHY = { + FONT_FAMILY_DEFAULT: "Arial", + FONT_FAMILIES: [ + "Arial", + "Verdana", + "Georgia", + "Times New Roman", + "Courier New", + "Impact", + "Comic Sans MS", + "Trebuchet MS", + ], + + // Font sizes for range inputs + FONT_SIZE_MIN: 10, + FONT_SIZE_MAX: 72, + FONT_SIZE_DEFAULT: 32, + + // Text limits + MAX_TEXT_LENGTH: 100, + + // Padding for range inputs + PADDING_X_DEFAULT: 10, + PADDING_X_MAX: 100, + + // Vertical offset for range inputs + VERTICAL_OFFSET_MAX: 100, + VERTICAL_OFFSET_MIN: -100, + + // Colors + TEXT_COLOR_DEFAULT: "#ffffff", +} as const; + +// ===== IMAGE SETTINGS ===== +export const IMAGE_SETTINGS = { + MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB + + SUPPORTED_FORMATS: ["image/jpeg", "image/jpg", "image/png", "image/webp", "image/gif"] as const, +} as const; + +export const SlideDirection = { + NEXT: "next", + PREV: "prev", +} as const; + +export type SlideDirectionType = (typeof SlideDirection)[keyof typeof SlideDirection]; + +export const TextAlign = { + LEFT: "left", + CENTER: "center", + RIGHT: "right", +} as const; + +export type TextAlignType = (typeof TextAlign)[keyof typeof TextAlign]; + +export const DEFAULT_TEXT_ALIGN: TextAlignType = TextAlign.CENTER; + +export const Theme = { + DARK: "dark", + LIGHT: "light", +} as const; + +export type ThemeType = (typeof Theme)[keyof typeof Theme]; + +export const TRANSITION_DURATION = import.meta.env.MODE === "test" ? 0 : 300; diff --git a/src/lib/error.types.ts b/src/lib/error.types.ts index 8da5c58..9b00b5c 100644 --- a/src/lib/error.types.ts +++ b/src/lib/error.types.ts @@ -1,37 +1,37 @@ -export class AppError extends Error { - constructor( - message: string, - public code: string, - public details?: unknown, - ) { - super(message); - this.name = "AppError"; - if (details) this.details = details; - } -} - -export class ImageError extends AppError { - constructor(message: string) { - super(message, "IMAGE_ERROR"); - } -} - -export class TextError extends AppError { - constructor(message: string) { - super(message, "TEXT_ERROR"); - } -} - -export class CanvasError extends AppError { - constructor(message: string) { - super(message, "CANVAS_ERROR"); - } -} - -export class StorageError extends AppError { - constructor(message: string) { - super(message, "STORAGE_ERROR"); - } -} - -export type ErrorType = AppError | ImageError | TextError | CanvasError | StorageError; +export class AppError extends Error { + constructor( + message: string, + public code: string, + public details?: unknown, + ) { + super(message); + this.name = "AppError"; + if (details) this.details = details; + } +} + +export class ImageError extends AppError { + constructor(message: string) { + super(message, "IMAGE_ERROR"); + } +} + +export class TextError extends AppError { + constructor(message: string) { + super(message, "TEXT_ERROR"); + } +} + +export class CanvasError extends AppError { + constructor(message: string) { + super(message, "CANVAS_ERROR"); + } +} + +export class StorageError extends AppError { + constructor(message: string) { + super(message, "STORAGE_ERROR"); + } +} + +export type ErrorType = AppError | ImageError | TextError | CanvasError | StorageError; diff --git a/src/lib/types.ts b/src/lib/types.ts index 492798f..1756285 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1 +1 @@ -export type HexColor = `#${string}`; +export type HexColor = `#${string}`; diff --git a/src/lib/utils/errorUtils.ts b/src/lib/utils/errorUtils.ts index 3fe3593..5631c3f 100644 --- a/src/lib/utils/errorUtils.ts +++ b/src/lib/utils/errorUtils.ts @@ -1,26 +1,26 @@ -import { AppError } from "$lib/error.types"; - -export function formatError(error: unknown, defaultMessage: string = "Произошла ошибка"): string { - if (error instanceof AppError || error instanceof Error) { - return `${defaultMessage}: ${error.message}`; - } - - if (typeof error === "string") { - return `${defaultMessage}: ${error}`; - } - - return `${defaultMessage}: Произошла неизвестная ошибка`; -} - -export function createError(message: string, code: string, details?: unknown): AppError { - return new AppError(message, code, details); -} - -export function logError(error: unknown, context?: string): void { - console.error("Error occurred:", { - error, - context, - timestamp: new Date().toISOString(), - stack: error instanceof Error ? error.stack : undefined, - }); -} +import { AppError } from "$lib/error.types"; + +export function formatError(error: unknown, defaultMessage: string = "Произошла ошибка"): string { + if (error instanceof AppError || error instanceof Error) { + return `${defaultMessage}: ${error.message}`; + } + + if (typeof error === "string") { + return `${defaultMessage}: ${error}`; + } + + return `${defaultMessage}: Произошла неизвестная ошибка`; +} + +export function createError(message: string, code: string, details?: unknown): AppError { + return new AppError(message, code, details); +} + +export function logError(error: unknown, context?: string): void { + console.error("Error occurred:", { + error, + context, + timestamp: new Date().toISOString(), + stack: error instanceof Error ? error.stack : undefined, + }); +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 92e907f..5a7d9e3 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,35 +1,35 @@ - - -
- - {@render children()} -
- - + + +
+ + {@render children()} +
+ + diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index e9e504a..89da957 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -1,2 +1,2 @@ -export const ssr = false; -export const prerender = true; +export const ssr = false; +export const prerender = true; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 2bf7e31..d0ed365 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,23 +1,23 @@ - - -
- - -
- - + + +
+ + +
+ + diff --git a/src/services/downloadService.ts b/src/services/downloadService.ts index c3f2bd0..842a8a9 100644 --- a/src/services/downloadService.ts +++ b/src/services/downloadService.ts @@ -1,83 +1,83 @@ -import { ImageError } from "$lib/error.types"; -import { formatError, logError } from "$lib/utils/errorUtils"; -import { saveAs } from "file-saver"; -import JSZip from "jszip"; -import { Stage } from "konva/lib/Stage"; - -export type DownloadResult = - | { - success: true; - } - | { - success: false; - error: string; - }; - -export interface DownloadItem { - filename: string; - stage: Stage; -} - -export class DownloadService { - async downloadPanel(konvaStage: Stage, label: string): Promise { - try { - const blob = await this.stageToBlob(konvaStage); - - const filename = `${label}.png`; - saveAs(blob, filename); - - return { - success: true, - }; - } catch (error) { - logError(error, "Ошибка сохранения панели"); - return { - success: false, - error: formatError(error, "Ошибка сохранения панели"), - }; - } - } - - async downloadAll(panels: Array): Promise { - try { - const zip = new JSZip(); - for (let panel of panels) { - const blob = await this.stageToBlob(panel.stage); - zip.file(`${panel.filename}.png`, blob); - } - - const zipBlob = await zip.generateAsync({ type: "blob" }); - saveAs(zipBlob, "panels.zip"); - - return { - success: true, - }; - } catch (error) { - logError(error, "Ошибка сохранения архива"); - return { - success: false, - error: formatError(error, "Ошибка сохранения архива"), - }; - } - } - - private async stageToBlob(konvaStage: Stage): Promise { - if (!konvaStage || typeof konvaStage.toBlob !== "function") { - throw new ImageError("Konva Stage не найден или не поддерживает toBlob"); - } - - return new Promise((resolve, reject) => { - konvaStage.toBlob({ - callback: (blob: Blob | null) => { - if (blob) { - resolve(blob); - } else { - reject(new ImageError("Не удалось создать изображение из Konva Stage")); - } - }, - }); - }); - } -} - -export const downloadService = new DownloadService(); +import { ImageError } from "$lib/error.types"; +import { formatError, logError } from "$lib/utils/errorUtils"; +import { saveAs } from "file-saver"; +import JSZip from "jszip"; +import { Stage } from "konva/lib/Stage"; + +export type DownloadResult = + | { + success: true; + } + | { + success: false; + error: string; + }; + +export interface DownloadItem { + filename: string; + stage: Stage; +} + +export class DownloadService { + async downloadPanel(konvaStage: Stage, label: string): Promise { + try { + const blob = await this.stageToBlob(konvaStage); + + const filename = `${label}.png`; + saveAs(blob, filename); + + return { + success: true, + }; + } catch (error) { + logError(error, "Ошибка сохранения панели"); + return { + success: false, + error: formatError(error, "Ошибка сохранения панели"), + }; + } + } + + async downloadAll(panels: Array): Promise { + try { + const zip = new JSZip(); + for (const panel of panels) { + const blob = await this.stageToBlob(panel.stage); + zip.file(`${panel.filename}.png`, blob); + } + + const zipBlob = await zip.generateAsync({ type: "blob" }); + saveAs(zipBlob, "panels.zip"); + + return { + success: true, + }; + } catch (error) { + logError(error, "Ошибка сохранения архива"); + return { + success: false, + error: formatError(error, "Ошибка сохранения архива"), + }; + } + } + + private async stageToBlob(konvaStage: Stage): Promise { + if (!konvaStage || typeof konvaStage.toBlob !== "function") { + throw new ImageError("Konva Stage не найден или не поддерживает toBlob"); + } + + return new Promise((resolve, reject) => { + konvaStage.toBlob({ + callback: (blob: Blob | null) => { + if (blob) { + resolve(blob); + } else { + reject(new ImageError("Не удалось создать изображение из Konva Stage")); + } + }, + }); + }); + } +} + +export const downloadService = new DownloadService(); diff --git a/src/states/imageConfig.svelte.ts b/src/states/imageConfig.svelte.ts index 48b3dfe..0cccfea 100644 --- a/src/states/imageConfig.svelte.ts +++ b/src/states/imageConfig.svelte.ts @@ -1,98 +1,98 @@ -export type ImageConfig = { - image: HTMLImageElement | undefined; - imageLink: string; - imageReady: boolean; - cropLeft: number; - cropTop: number; - cropRight: number; - cropBottom: number; -}; - -export class ImageConfigState { - image = $state(undefined); - imageLink = $state(""); - imageReady = $state(false); - cropLeft = $state(0); - cropTop = $state(0); - cropRight = $state(0); - cropBottom = $state(0); - - #currentAbortController: AbortController | null = null; - - private cleanup() { - if (this.#currentAbortController) { - this.#currentAbortController.abort(); - this.#currentAbortController = null; - } - - if (this.image) { - this.image.onload = null; - this.image.onerror = null; - this.image.src = ""; - this.image = undefined; - } - } - - async uploadImageByLink(link: string): Promise { - this.cleanup(); - - this.imageReady = false; - this.imageLink = link; - - this.#currentAbortController = new AbortController(); - const { signal } = this.#currentAbortController; - - return new Promise((resolve, reject) => { - const img = new Image(); - img.crossOrigin = "anonymous"; - - const onFinished = () => { - img.onload = null; - img.onerror = null; - }; - - img.onload = () => { - if (signal.aborted) return; - onFinished(); - this.image = img; - this.imageReady = true; - resolve(); - }; - - img.onerror = () => { - if (signal.aborted) return; - onFinished(); - this.imageReady = false; - reject(new Error(`Failed to load image: ${link}`)); - }; - - signal.addEventListener( - "abort", - () => { - onFinished(); - img.src = ""; - reject(new DOMException("Aborted", "AbortError")); - }, - { once: true }, - ); - - img.src = link; - }); - } - - reset() { - this.cleanup(); - this.imageLink = ""; - this.imageReady = false; - this.cropLeft = 0; - this.cropTop = 0; - this.cropRight = 0; - this.cropBottom = 0; - } - - destroy() { - this.cleanup(); - } -} - -export const imageConfigState = new ImageConfigState(); +export type ImageConfig = { + image: HTMLImageElement | undefined; + imageLink: string; + imageReady: boolean; + cropLeft: number; + cropTop: number; + cropRight: number; + cropBottom: number; +}; + +export class ImageConfigState { + image = $state(undefined); + imageLink = $state(""); + imageReady = $state(false); + cropLeft = $state(0); + cropTop = $state(0); + cropRight = $state(0); + cropBottom = $state(0); + + #currentAbortController: AbortController | null = null; + + private cleanup() { + if (this.#currentAbortController) { + this.#currentAbortController.abort(); + this.#currentAbortController = null; + } + + if (this.image) { + this.image.onload = null; + this.image.onerror = null; + this.image.src = ""; + this.image = undefined; + } + } + + async uploadImageByLink(link: string): Promise { + this.cleanup(); + + this.imageReady = false; + this.imageLink = link; + + this.#currentAbortController = new AbortController(); + const { signal } = this.#currentAbortController; + + return new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = "anonymous"; + + const onFinished = () => { + img.onload = null; + img.onerror = null; + }; + + img.onload = () => { + if (signal.aborted) return; + onFinished(); + this.image = img; + this.imageReady = true; + resolve(); + }; + + img.onerror = () => { + if (signal.aborted) return; + onFinished(); + this.imageReady = false; + reject(new Error(`Failed to load image: ${link}`)); + }; + + signal.addEventListener( + "abort", + () => { + onFinished(); + img.src = ""; + reject(new DOMException("Aborted", "AbortError")); + }, + { once: true }, + ); + + img.src = link; + }); + } + + reset() { + this.cleanup(); + this.imageLink = ""; + this.imageReady = false; + this.cropLeft = 0; + this.cropTop = 0; + this.cropRight = 0; + this.cropBottom = 0; + } + + destroy() { + this.cleanup(); + } +} + +export const imageConfigState = new ImageConfigState(); diff --git a/src/states/konvaAllStages.svelte.ts b/src/states/konvaAllStages.svelte.ts index 915f31d..ea55a00 100644 --- a/src/states/konvaAllStages.svelte.ts +++ b/src/states/konvaAllStages.svelte.ts @@ -1,3 +1,3 @@ -import type { Stage } from "svelte-konva"; - -export const konvaAllStagesState: Array = $state([]); +import type { Stage } from "svelte-konva"; + +export const konvaAllStagesState: Array = $state([]); diff --git a/src/states/konvaStage.svelte.ts b/src/states/konvaStage.svelte.ts index 9ef2781..0738e38 100644 --- a/src/states/konvaStage.svelte.ts +++ b/src/states/konvaStage.svelte.ts @@ -1,16 +1,16 @@ -import type { Stage } from "svelte-konva"; - -function createState() { - let stage: Stage | undefined = $state(undefined); - - return { - get stage(): Stage | undefined { - return stage; - }, - set stage(newStage: Stage) { - stage = newStage; - }, - }; -} - -export const konvaStageState = createState(); +import type { Stage } from "svelte-konva"; + +function createState() { + let stage: Stage | undefined = $state(undefined); + + return { + get stage(): Stage | undefined { + return stage; + }, + set stage(newStage: Stage) { + stage = newStage; + }, + }; +} + +export const konvaStageState = createState(); diff --git a/src/states/persisted.svelte.ts b/src/states/persisted.svelte.ts index 5d590c8..9165bc7 100644 --- a/src/states/persisted.svelte.ts +++ b/src/states/persisted.svelte.ts @@ -1,48 +1,44 @@ -import { browser } from "$app/environment"; - -export const STATE_DATA = Symbol("state-data"); -export const DEBOUNCE_DURATION = import.meta.env.MODE === "test" ? 0 : 500; - -type OnlyData = { - [K in keyof T as T[K] extends Function ? never : K]: T[K]; -}; - -export interface Persistable { - [STATE_DATA]: D; -} - -export function withPersistence>( - key: string, - state: T, - debounceMs = DEBOUNCE_DURATION, -): T { - if (!browser) return state; - - const saved = localStorage.getItem(key); - if (saved) { - try { - const parsed = JSON.parse(saved); - - if (parsed) { - state[STATE_DATA] = parsed; - } - } catch (e) { - console.error(`Error repairing state for ${key}`, e); - } - } - - $effect.root(() => { - $effect(() => { - const data = JSON.stringify($state.snapshot(state[STATE_DATA])); - - if (debounceMs > 0) { - const timeout = setTimeout(() => localStorage.setItem(key, data), debounceMs); - return () => clearTimeout(timeout); - } else { - localStorage.setItem(key, data); - } - }); - }); - - return state; -} +import { browser } from "$app/environment"; + +export const STATE_DATA = Symbol("state-data"); +export const DEBOUNCE_DURATION = import.meta.env.MODE === "test" ? 0 : 500; + +export interface Persistable { + [STATE_DATA]: D; +} + +export function withPersistence>( + key: string, + state: T, + debounceMs = DEBOUNCE_DURATION, +): T { + if (!browser) return state; + + const saved = localStorage.getItem(key); + if (saved) { + try { + const parsed = JSON.parse(saved); + + if (parsed) { + state[STATE_DATA] = parsed; + } + } catch (e) { + console.error(`Error repairing state for ${key}`, e); + } + } + + $effect.root(() => { + $effect(() => { + const data = JSON.stringify($state.snapshot(state[STATE_DATA])); + + if (debounceMs > 0) { + const timeout = setTimeout(() => localStorage.setItem(key, data), debounceMs); + return () => clearTimeout(timeout); + } else { + localStorage.setItem(key, data); + } + }); + }); + + return state; +} diff --git a/src/states/textConfig.svelte.ts b/src/states/textConfig.svelte.ts index 8002220..fb1c2b7 100644 --- a/src/states/textConfig.svelte.ts +++ b/src/states/textConfig.svelte.ts @@ -1,83 +1,83 @@ -import { type TextAlignType } from "$lib/constants"; -import type { HexColor } from "$lib/types"; -import { STATE_DATA, withPersistence } from "./persisted.svelte"; - -export type TextConfig = { - fontSize: number; - fontFamily: string; - color: HexColor; - align: TextAlignType; - paddingX: number; - offsetY: number; -}; - -function createState() { - const defaults: TextConfig = { - fontSize: 24, - fontFamily: "Arial", - color: "#ffffff", - align: "center", - paddingX: 10, - offsetY: 0, - }; - let state: TextConfig = $state({ ...defaults }); - - return { - get fontSize() { - return state.fontSize; - }, - set fontSize(value: number) { - state.fontSize = value; - }, - get fontFamily() { - return state.fontFamily; - }, - set fontFamily(fontFamily: string) { - state.fontFamily = fontFamily; - }, - get color() { - return state.color; - }, - set color(color: HexColor) { - state.color = color; - }, - get align() { - return state.align; - }, - set align(align: TextAlignType) { - state.align = align; - }, - get paddingX() { - return state.paddingX; - }, - set paddingX(paddingX: number) { - state.paddingX = paddingX; - }, - get offsetY() { - return state.offsetY; - }, - set offsetY(offsetY: number) { - state.offsetY = offsetY; - }, - get [STATE_DATA]() { - return { - fontSize: state.fontSize, - fontFamily: state.fontFamily, - color: state.color, - align: state.align, - paddingX: state.paddingX, - offsetY: state.offsetY, - }; - }, - set [STATE_DATA](newConfig: TextConfig) { - state.fontSize = newConfig.fontSize; - state.fontFamily = newConfig.fontFamily; - state.color = newConfig.color; - state.align = newConfig.align; - state.paddingX = newConfig.paddingX; - state.offsetY = newConfig.offsetY; - }, - }; -} - -export const textConfigState = withPersistence("text-config", createState()); +import { type TextAlignType } from "$lib/constants"; +import type { HexColor } from "$lib/types"; +import { STATE_DATA, withPersistence } from "./persisted.svelte"; + +export type TextConfig = { + fontSize: number; + fontFamily: string; + color: HexColor; + align: TextAlignType; + paddingX: number; + offsetY: number; +}; + +function createState() { + const defaults: TextConfig = { + fontSize: 24, + fontFamily: "Arial", + color: "#ffffff", + align: "center", + paddingX: 10, + offsetY: 0, + }; + const state: TextConfig = $state({ ...defaults }); + + return { + get fontSize() { + return state.fontSize; + }, + set fontSize(value: number) { + state.fontSize = value; + }, + get fontFamily() { + return state.fontFamily; + }, + set fontFamily(fontFamily: string) { + state.fontFamily = fontFamily; + }, + get color() { + return state.color; + }, + set color(color: HexColor) { + state.color = color; + }, + get align() { + return state.align; + }, + set align(align: TextAlignType) { + state.align = align; + }, + get paddingX() { + return state.paddingX; + }, + set paddingX(paddingX: number) { + state.paddingX = paddingX; + }, + get offsetY() { + return state.offsetY; + }, + set offsetY(offsetY: number) { + state.offsetY = offsetY; + }, + get [STATE_DATA]() { + return { + fontSize: state.fontSize, + fontFamily: state.fontFamily, + color: state.color, + align: state.align, + paddingX: state.paddingX, + offsetY: state.offsetY, + }; + }, + set [STATE_DATA](newConfig: TextConfig) { + state.fontSize = newConfig.fontSize; + state.fontFamily = newConfig.fontFamily; + state.color = newConfig.color; + state.align = newConfig.align; + state.paddingX = newConfig.paddingX; + state.offsetY = newConfig.offsetY; + }, + }; +} + +export const textConfigState = withPersistence("text-config", createState()); diff --git a/src/states/texts.svelte.ts b/src/states/texts.svelte.ts index 8978d6a..143db00 100644 --- a/src/states/texts.svelte.ts +++ b/src/states/texts.svelte.ts @@ -1,40 +1,43 @@ -import { STATE_DATA, withPersistence } from "./persisted.svelte"; - -export interface TextItem { - text: string; - id: number; -} - -const defaultTexts: Array = ["About me", "Links", "Projects"].map((text, idx) => ({ text, id: idx })); - -export function createState() { - let texts: Array = $state(defaultTexts); - let nextId = $state(defaultTexts.length); - - return { - get texts() { - return texts; - }, - addText(text: string) { - if (text.trim().length === 0) return; - texts.push({ text, id: nextId }); - nextId++; - }, - removeText(id: number) { - texts = texts.filter((textItem) => textItem.id !== id); - }, - clear() { - texts = []; - nextId = 0; - }, - get [STATE_DATA]() { - return texts.map(({ text }) => text); - }, - set [STATE_DATA](newTexts: Array) { - texts = newTexts.map((text, idx) => ({ text, id: idx })); - nextId = newTexts.length; - }, - }; -} - -export const textsState = withPersistence("texts", createState()); +import { STATE_DATA, withPersistence } from "./persisted.svelte"; + +export interface TextItem { + text: string; + id: number; +} + +const defaultTexts: Array = ["About me", "Links", "Projects"].map((text, idx) => ({ + text, + id: idx, +})); + +export function createState() { + let texts: Array = $state(defaultTexts); + let nextId = $state(defaultTexts.length); + + return { + get texts() { + return texts; + }, + addText(text: string) { + if (text.trim().length === 0) return; + texts.push({ text, id: nextId }); + nextId++; + }, + removeText(id: number) { + texts = texts.filter((textItem) => textItem.id !== id); + }, + clear() { + texts = []; + nextId = 0; + }, + get [STATE_DATA]() { + return texts.map(({ text }) => text); + }, + set [STATE_DATA](newTexts: Array) { + texts = newTexts.map((text, idx) => ({ text, id: idx })); + nextId = newTexts.length; + }, + }; +} + +export const textsState = withPersistence("texts", createState()); diff --git a/src/states/theme.svelte.ts b/src/states/theme.svelte.ts index 6e9440c..7f391e4 100644 --- a/src/states/theme.svelte.ts +++ b/src/states/theme.svelte.ts @@ -1,28 +1,28 @@ -import { Theme, type ThemeType } from "$lib/constants"; -import { STATE_DATA, withPersistence } from "./persisted.svelte"; - -function createState() { - let current: ThemeType = $state(Theme.LIGHT); - - return { - get current() { - return current; - }, - set current(value: ThemeType) { - current = value; - }, - toggle() { - current = current === Theme.DARK ? Theme.LIGHT : Theme.DARK; - }, - get [STATE_DATA]() { - return { - current, - }; - }, - set [STATE_DATA](data: { current: ThemeType }) { - current = data.current; - }, - }; -} - -export const themeState = withPersistence("theme", createState()); +import { Theme, type ThemeType } from "$lib/constants"; +import { STATE_DATA, withPersistence } from "./persisted.svelte"; + +function createState() { + let current: ThemeType = $state(Theme.LIGHT); + + return { + get current() { + return current; + }, + set current(value: ThemeType) { + current = value; + }, + toggle() { + current = current === Theme.DARK ? Theme.LIGHT : Theme.DARK; + }, + get [STATE_DATA]() { + return { + current, + }; + }, + set [STATE_DATA](data: { current: ThemeType }) { + current = data.current; + }, + }; +} + +export const themeState = withPersistence("theme", createState()); diff --git a/tests/setup.ts b/tests/setup.ts index c16b423..1a30197 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,3 +1,3 @@ -import "@testing-library/jest-dom/vitest"; -import "vitest-canvas-mock"; -import "web-animations-js"; +import "@testing-library/jest-dom/vitest"; +import "vitest-canvas-mock"; +import "web-animations-js"; diff --git a/tests/unit/components/image/CropInline.test.ts b/tests/unit/components/image/CropInline.test.ts index 2fe67c6..1078d3f 100644 --- a/tests/unit/components/image/CropInline.test.ts +++ b/tests/unit/components/image/CropInline.test.ts @@ -1,10 +1,10 @@ -import CropInline from "$components/image/CropInline.svelte"; -import { render } from "@testing-library/svelte"; -import { describe, expect, it } from "vitest"; - -describe("CropInline.svelte", () => { - it("should render without crashing", () => { - const { container } = render(CropInline); - expect(container).toBeInTheDocument(); - }); -}); +import CropInline from "$components/image/CropInline.svelte"; +import { render } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; + +describe("CropInline.svelte", () => { + it("should render without crashing", () => { + const { container } = render(CropInline); + expect(container).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/image/ImageManager.test.ts b/tests/unit/components/image/ImageManager.test.ts index 7322ccd..bc3665b 100644 --- a/tests/unit/components/image/ImageManager.test.ts +++ b/tests/unit/components/image/ImageManager.test.ts @@ -1,9 +1,9 @@ -import ImageManager from "$components/image/ImageManager.svelte"; -import { render } from "@testing-library/svelte"; -import { describe, it } from "vitest"; - -describe("ImageManager.svelte", () => { - it("should render without crashing", () => { - render(ImageManager); - }); -}); +import ImageManager from "$components/image/ImageManager.svelte"; +import { render } from "@testing-library/svelte"; +import { describe, it } from "vitest"; + +describe("ImageManager.svelte", () => { + it("should render without crashing", () => { + render(ImageManager); + }); +}); diff --git a/tests/unit/components/layout/AppHeader.test.ts b/tests/unit/components/layout/AppHeader.test.ts index 2d3363b..b52c1d6 100644 --- a/tests/unit/components/layout/AppHeader.test.ts +++ b/tests/unit/components/layout/AppHeader.test.ts @@ -1,25 +1,25 @@ -import AppHeader from "$components/layout/AppHeader.svelte"; -import { themeState } from "$states/theme.svelte"; -import { render, screen } from "@testing-library/svelte"; -import userEvent from "@testing-library/user-event"; -import { beforeEach, describe, expect, it } from "vitest"; - -describe("AppHeader.svelte", () => { - beforeEach(() => { - themeState.current = "dark"; - }); - - it("should toggle theme on button click", async () => { - const user = userEvent.setup(); - render(AppHeader); - - const toggleButton = screen.getByRole("button", { name: /toggle theme/i }); - - themeState.current = "dark"; - await user.click(toggleButton); - expect(themeState.current).toBe("light"); - - await user.click(toggleButton); - expect(themeState.current).toBe("dark"); - }); -}); +import AppHeader from "$components/layout/AppHeader.svelte"; +import { themeState } from "$states/theme.svelte"; +import { render, screen } from "@testing-library/svelte"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it } from "vitest"; + +describe("AppHeader.svelte", () => { + beforeEach(() => { + themeState.current = "dark"; + }); + + it("should toggle theme on button click", async () => { + const user = userEvent.setup(); + render(AppHeader); + + const toggleButton = screen.getByRole("button", { name: /toggle theme/i }); + + themeState.current = "dark"; + await user.click(toggleButton); + expect(themeState.current).toBe("light"); + + await user.click(toggleButton); + expect(themeState.current).toBe("dark"); + }); +}); diff --git a/tests/unit/components/layout/Card.test.ts b/tests/unit/components/layout/Card.test.ts index 6b8efe2..982e302 100644 --- a/tests/unit/components/layout/Card.test.ts +++ b/tests/unit/components/layout/Card.test.ts @@ -1,11 +1,11 @@ -import { render, screen } from "@testing-library/svelte"; -import { describe, expect, it } from "vitest"; -import CardTest from "./CardTest.svelte"; - -describe("Card.svelte", () => { - it("should render without crashing", () => { - render(CardTest); - expect(screen.getByText("Test Title")).toBeInTheDocument(); - expect(screen.getByTestId("card-content")).toBeInTheDocument(); - }); -}); +import { render, screen } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; +import CardTest from "./CardTest.svelte"; + +describe("Card.svelte", () => { + it("should render without crashing", () => { + render(CardTest); + expect(screen.getByText("Test Title")).toBeInTheDocument(); + expect(screen.getByTestId("card-content")).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/layout/CardTest.svelte b/tests/unit/components/layout/CardTest.svelte index 32a9277..45fe2cc 100644 --- a/tests/unit/components/layout/CardTest.svelte +++ b/tests/unit/components/layout/CardTest.svelte @@ -1,7 +1,7 @@ - - - -
Test content
-
+ + + +
Test content
+
diff --git a/tests/unit/components/layout/InputGroup.test.ts b/tests/unit/components/layout/InputGroup.test.ts index 2e05e72..5fb276a 100644 --- a/tests/unit/components/layout/InputGroup.test.ts +++ b/tests/unit/components/layout/InputGroup.test.ts @@ -1,12 +1,12 @@ -import { render, screen } from "@testing-library/svelte"; -import { describe, expect, it } from "vitest"; -import InputGroupTest from "./InputGroupTest.svelte"; - -describe("InputGroup.svelte", () => { - it("should render without crashing", () => { - render(InputGroupTest); - expect(screen.getByTestId("input1")).toBeInTheDocument(); - expect(screen.getByTestId("input2")).toBeInTheDocument(); - expect(screen.getByTestId("button")).toBeInTheDocument(); - }); -}); +import { render, screen } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; +import InputGroupTest from "./InputGroupTest.svelte"; + +describe("InputGroup.svelte", () => { + it("should render without crashing", () => { + render(InputGroupTest); + expect(screen.getByTestId("input1")).toBeInTheDocument(); + expect(screen.getByTestId("input2")).toBeInTheDocument(); + expect(screen.getByTestId("button")).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/layout/InputGroupTest.svelte b/tests/unit/components/layout/InputGroupTest.svelte index 974d2bd..25476c9 100644 --- a/tests/unit/components/layout/InputGroupTest.svelte +++ b/tests/unit/components/layout/InputGroupTest.svelte @@ -1,9 +1,9 @@ - - - - - - - + + + + + + + diff --git a/tests/unit/components/layout/PanelBar.test.ts b/tests/unit/components/layout/PanelBar.test.ts index 2ad5535..a32e907 100644 --- a/tests/unit/components/layout/PanelBar.test.ts +++ b/tests/unit/components/layout/PanelBar.test.ts @@ -1,10 +1,10 @@ -import PanelBar from "$components/layout/PanelBar.svelte"; -import { render } from "@testing-library/svelte"; -import { describe, expect, it } from "vitest"; - -describe("PanelBar.svelte", () => { - it("should render without crashing", () => { - const { container } = render(PanelBar); - expect(container).toBeInTheDocument(); - }); -}); +import PanelBar from "$components/layout/PanelBar.svelte"; +import { render } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; + +describe("PanelBar.svelte", () => { + it("should render without crashing", () => { + const { container } = render(PanelBar); + expect(container).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/layout/SettingsGrid.test.ts b/tests/unit/components/layout/SettingsGrid.test.ts index 1a35ced..24b0611 100644 --- a/tests/unit/components/layout/SettingsGrid.test.ts +++ b/tests/unit/components/layout/SettingsGrid.test.ts @@ -1,12 +1,12 @@ -import { render, screen } from "@testing-library/svelte"; -import { describe, expect, it } from "vitest"; -import SettingsGridTest from "./SettingsGridTest.svelte"; - -describe("SettingsGrid.svelte", () => { - it("should render without crashing", () => { - render(SettingsGridTest); - expect(screen.getByTestId("item1")).toBeInTheDocument(); - expect(screen.getByTestId("item2")).toBeInTheDocument(); - expect(screen.getByTestId("item3")).toBeInTheDocument(); - }); -}); +import { render, screen } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; +import SettingsGridTest from "./SettingsGridTest.svelte"; + +describe("SettingsGrid.svelte", () => { + it("should render without crashing", () => { + render(SettingsGridTest); + expect(screen.getByTestId("item1")).toBeInTheDocument(); + expect(screen.getByTestId("item2")).toBeInTheDocument(); + expect(screen.getByTestId("item3")).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/layout/SettingsGridTest.svelte b/tests/unit/components/layout/SettingsGridTest.svelte index b7e2681..09b5193 100644 --- a/tests/unit/components/layout/SettingsGridTest.svelte +++ b/tests/unit/components/layout/SettingsGridTest.svelte @@ -1,9 +1,9 @@ - - - -
Item 1
-
Item 2
-
Item 3
-
+ + + +
Item 1
+
Item 2
+
Item 3
+
diff --git a/tests/unit/components/layout/SettingsRow.test.ts b/tests/unit/components/layout/SettingsRow.test.ts index 2cdd607..fe24193 100644 --- a/tests/unit/components/layout/SettingsRow.test.ts +++ b/tests/unit/components/layout/SettingsRow.test.ts @@ -1,11 +1,11 @@ -import { render, screen } from "@testing-library/svelte"; -import { describe, expect, it } from "vitest"; -import SettingsRowTest from "./SettingsRowTest.svelte"; - -describe("SettingsRow.svelte", () => { - it("should render without crashing", () => { - render(SettingsRowTest); - expect(screen.getByText("Test Label")).toBeInTheDocument(); - expect(screen.getByTestId("test-input")).toBeInTheDocument(); - }); -}); +import { render, screen } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; +import SettingsRowTest from "./SettingsRowTest.svelte"; + +describe("SettingsRow.svelte", () => { + it("should render without crashing", () => { + render(SettingsRowTest); + expect(screen.getByText("Test Label")).toBeInTheDocument(); + expect(screen.getByTestId("test-input")).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/layout/SettingsRowTest.svelte b/tests/unit/components/layout/SettingsRowTest.svelte index 2d0ec26..8b71387 100644 --- a/tests/unit/components/layout/SettingsRowTest.svelte +++ b/tests/unit/components/layout/SettingsRowTest.svelte @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/tests/unit/components/layout/TextBar.test.ts b/tests/unit/components/layout/TextBar.test.ts index f758e88..9d8557a 100644 --- a/tests/unit/components/layout/TextBar.test.ts +++ b/tests/unit/components/layout/TextBar.test.ts @@ -1,10 +1,10 @@ -import TextBar from "$components/layout/TextBar.svelte"; -import { render } from "@testing-library/svelte"; -import { describe, expect, it } from "vitest"; - -describe("TextBar.svelte", () => { - it("should render without crashing", () => { - const { container } = render(TextBar); - expect(container).toBeInTheDocument(); - }); -}); +import TextBar from "$components/layout/TextBar.svelte"; +import { render } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; + +describe("TextBar.svelte", () => { + it("should render without crashing", () => { + const { container } = render(TextBar); + expect(container).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/panel/Preview.test.ts b/tests/unit/components/panel/Preview.test.ts index 5a60bb5..33aa2f1 100644 --- a/tests/unit/components/panel/Preview.test.ts +++ b/tests/unit/components/panel/Preview.test.ts @@ -1,32 +1,32 @@ -import Preview from "$components/panel/Preview.svelte"; -import { cleanup, render } from "@testing-library/svelte"; -import { afterEach, describe, expect, it } from "vitest"; - -afterEach(() => { - cleanup(); -}); - -describe("Preview.svelte", () => { - it("should render without crashing", () => { - const { container } = render(Preview, { - props: { - text: "Test text", - stage: undefined, - }, - }); - - expect(container).toBeInTheDocument(); - }); - - it("should have stage element", () => { - const { container } = render(Preview, { - props: { - text: "Test", - stage: undefined, - }, - }); - - const stage = container.querySelector("canvas"); - expect(stage).toBeInTheDocument(); - }); -}); +import Preview from "$components/panel/Preview.svelte"; +import { cleanup, render } from "@testing-library/svelte"; +import { afterEach, describe, expect, it } from "vitest"; + +afterEach(() => { + cleanup(); +}); + +describe("Preview.svelte", () => { + it("should render without crashing", () => { + const { container } = render(Preview, { + props: { + text: "Test text", + stage: undefined, + }, + }); + + expect(container).toBeInTheDocument(); + }); + + it("should have stage element", () => { + const { container } = render(Preview, { + props: { + text: "Test", + stage: undefined, + }, + }); + + const stage = container.querySelector("canvas"); + expect(stage).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/panel/PreviewAll.test.ts b/tests/unit/components/panel/PreviewAll.test.ts index 2015704..5ed9ba6 100644 --- a/tests/unit/components/panel/PreviewAll.test.ts +++ b/tests/unit/components/panel/PreviewAll.test.ts @@ -1,14 +1,14 @@ -import PreviewAll from "$components/panel/PreviewAll.svelte"; -import { textsState } from "$states/texts.svelte"; -import { render } from "@testing-library/svelte"; -import { beforeEach, describe, it } from "vitest"; - -describe("PreviewAll.svelte", () => { - beforeEach(() => { - textsState.texts.length = 0; - }); - - it("should render without crashing", () => { - render(PreviewAll); - }); -}); +import PreviewAll from "$components/panel/PreviewAll.svelte"; +import { textsState } from "$states/texts.svelte"; +import { render } from "@testing-library/svelte"; +import { beforeEach, describe, it } from "vitest"; + +describe("PreviewAll.svelte", () => { + beforeEach(() => { + textsState.texts.length = 0; + }); + + it("should render without crashing", () => { + render(PreviewAll); + }); +}); diff --git a/tests/unit/components/panel/PreviewControls.test.ts b/tests/unit/components/panel/PreviewControls.test.ts index 0055d5f..ba674aa 100644 --- a/tests/unit/components/panel/PreviewControls.test.ts +++ b/tests/unit/components/panel/PreviewControls.test.ts @@ -1,46 +1,46 @@ -import { SlideDirection } from "$lib/constants"; -import { render, screen } from "@testing-library/svelte"; -import userEvent from "@testing-library/user-event"; -import { describe, expect, it } from "vitest"; -import PreviewControlsTest from "./PreviewControlsTest.svelte"; - -describe("PreviewControls logic", () => { - it("should increment current and update direction on next click", async () => { - const user = userEvent.setup(); - render(PreviewControlsTest, { props: { current: 0, max: 3 } }); - - const nextBtn = screen.getByRole("button", { name: /next slide/i }); - - await user.click(nextBtn); - - expect(screen.getByTestId("current").textContent).toBe("1"); - expect(screen.getByTestId("direction").textContent).toBe(SlideDirection.NEXT); - }); - - it("should decrement current on prev click", async () => { - const user = userEvent.setup(); - render(PreviewControlsTest, { props: { current: 2, max: 3 } }); - - const prevBtn = screen.getByRole("button", { name: /previous slide/i }); - - await user.click(prevBtn); - - expect(screen.getByTestId("current").textContent).toBe("1"); - }); - - it("should handle boundaries and disable buttons", async () => { - const user = userEvent.setup(); - render(PreviewControlsTest, { props: { current: 0, max: 2 } }); - - const prevBtn = screen.getByRole("button", { name: /previous slide/i }); - const nextBtn = screen.getByRole("button", { name: /next slide/i }); - - expect(prevBtn).toBeDisabled(); - - await user.click(nextBtn); - - expect(screen.getByTestId("current").textContent).toBe("1"); - expect(nextBtn).toBeDisabled(); - expect(prevBtn).not.toBeDisabled(); - }); -}); +import { SlideDirection } from "$lib/constants"; +import { render, screen } from "@testing-library/svelte"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it } from "vitest"; +import PreviewControlsTest from "./PreviewControlsTest.svelte"; + +describe("PreviewControls logic", () => { + it("should increment current and update direction on next click", async () => { + const user = userEvent.setup(); + render(PreviewControlsTest, { props: { current: 0, max: 3 } }); + + const nextBtn = screen.getByRole("button", { name: /next slide/i }); + + await user.click(nextBtn); + + expect(screen.getByTestId("current").textContent).toBe("1"); + expect(screen.getByTestId("direction").textContent).toBe(SlideDirection.NEXT); + }); + + it("should decrement current on prev click", async () => { + const user = userEvent.setup(); + render(PreviewControlsTest, { props: { current: 2, max: 3 } }); + + const prevBtn = screen.getByRole("button", { name: /previous slide/i }); + + await user.click(prevBtn); + + expect(screen.getByTestId("current").textContent).toBe("1"); + }); + + it("should handle boundaries and disable buttons", async () => { + const user = userEvent.setup(); + render(PreviewControlsTest, { props: { current: 0, max: 2 } }); + + const prevBtn = screen.getByRole("button", { name: /previous slide/i }); + const nextBtn = screen.getByRole("button", { name: /next slide/i }); + + expect(prevBtn).toBeDisabled(); + + await user.click(nextBtn); + + expect(screen.getByTestId("current").textContent).toBe("1"); + expect(nextBtn).toBeDisabled(); + expect(prevBtn).not.toBeDisabled(); + }); +}); diff --git a/tests/unit/components/panel/PreviewControlsTest.svelte b/tests/unit/components/panel/PreviewControlsTest.svelte index f482049..d6de611 100644 --- a/tests/unit/components/panel/PreviewControlsTest.svelte +++ b/tests/unit/components/panel/PreviewControlsTest.svelte @@ -1,13 +1,13 @@ - - - - -
{current}
-
{direction}
+ + + + +
{current}
+
{direction}
diff --git a/tests/unit/components/panel/PreviewManager.test.ts b/tests/unit/components/panel/PreviewManager.test.ts index 96fb84e..0d9ea66 100644 --- a/tests/unit/components/panel/PreviewManager.test.ts +++ b/tests/unit/components/panel/PreviewManager.test.ts @@ -1,102 +1,103 @@ -import PreviewManager from "$components/panel/PreviewManager.svelte"; -import { downloadService } from "$services/downloadService"; -import { konvaStageState } from "$states/konvaStage.svelte"; -import { STATE_DATA } from "$states/persisted.svelte"; -import { textsState } from "$states/texts.svelte"; -import { render, screen, waitFor } from "@testing-library/svelte"; -import userEvent from "@testing-library/user-event"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -vi.mock("$services/downloadService", () => ({ - downloadService: { - downloadAll: vi.fn(), - downloadPanel: vi.fn(), - }, -})); - -vi.mock("./Preview.svelte", () => ({ - default: { render: () => ({}) }, -})); -describe("PreviewManager Integration", () => { - beforeEach(() => { - vi.clearAllMocks(); - textsState[STATE_DATA] = []; - }); - - it("should show empty state by aria-label when no texts", () => { - render(PreviewManager); - expect(screen.getByLabelText(/Empty texts info/i)).toBeInTheDocument(); - }); - - it("should toggle navigation buttons availability based on texts length", async () => { - textsState[STATE_DATA] = ["1", "2"]; - render(PreviewManager); - - const nextBtn = screen.getByRole("button", { name: /next slide/i }); - const prevBtn = screen.getByRole("button", { name: /previous slide/i }); - - expect(nextBtn).not.toBeDisabled(); - expect(prevBtn).toBeDisabled(); - }); - - it("should send correct data to downloadAll service", async () => { - const user = userEvent.setup(); - textsState[STATE_DATA] = ["Apple", "Banana"]; - render(PreviewManager); - - await user.click(screen.getByRole("button", { name: /download all/i })); - - expect(downloadService.downloadAll).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ filename: "Apple" }), - expect.objectContaining({ filename: "Banana" }), - ]), - ); - }); - - it("should call downloadPanel with current active text", async () => { - const user = userEvent.setup(); - textsState[STATE_DATA] = ["First", "Second"]; - konvaStageState.stage = { node: { id: "stage-ref" } } as any; - - render(PreviewManager); - - // Переходим на второй слайд - await user.click(screen.getByRole("button", { name: /next slide/i })); - await user.click(screen.getByRole("button", { name: /download current/i })); - - expect(downloadService.downloadPanel).toHaveBeenCalledWith(expect.anything(), "Second"); - }); - - it("should return to empty state when texts are removed", async () => { - textsState[STATE_DATA] = ["Temp"]; - render(PreviewManager); - - expect(screen.queryByLabelText(/Empty texts info/i)).not.toBeInTheDocument(); - - textsState[STATE_DATA] = []; - - await waitFor(() => { - expect(screen.getByLabelText(/Empty texts info/i)).toBeInTheDocument(); - }); - }); - - it("should automatically correct current index on items deletion", async () => { - const user = userEvent.setup(); - textsState[STATE_DATA] = ["1", "2", "3"]; - render(PreviewManager); - - // Уходим на последний слайд - await user.click(screen.getByRole("button", { name: /next slide/i })); - await user.click(screen.getByRole("button", { name: /next slide/i })); - - // Удаляем элементы. Индекс должен упасть с 2 до 0 - textsState[STATE_DATA] = ["Only one left"]; - - await waitFor(() => { - // Проверяем, что кнопка "Next" заблокировалась (значит мы на последнем/единственном) - expect(screen.getByRole("button", { name: /next slide/i })).toBeDisabled(); - expect(screen.getByRole("button", { name: /previous slide/i })).toBeDisabled(); - }); - }); -}); +import PreviewManager from "$components/panel/PreviewManager.svelte"; +import { downloadService } from "$services/downloadService"; +import { konvaStageState } from "$states/konvaStage.svelte"; +import { STATE_DATA } from "$states/persisted.svelte"; +import { textsState } from "$states/texts.svelte"; +import { render, screen, waitFor } from "@testing-library/svelte"; +import userEvent from "@testing-library/user-event"; +import type { Stage } from "svelte-konva"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("$services/downloadService", () => ({ + downloadService: { + downloadAll: vi.fn(), + downloadPanel: vi.fn(), + }, +})); + +vi.mock("./Preview.svelte", () => ({ + default: { render: () => ({}) }, +})); +describe("PreviewManager Integration", () => { + beforeEach(() => { + vi.clearAllMocks(); + textsState[STATE_DATA] = []; + }); + + it("should show empty state by aria-label when no texts", () => { + render(PreviewManager); + expect(screen.getByLabelText(/Empty texts info/i)).toBeInTheDocument(); + }); + + it("should toggle navigation buttons availability based on texts length", async () => { + textsState[STATE_DATA] = ["1", "2"]; + render(PreviewManager); + + const nextBtn = screen.getByRole("button", { name: /next slide/i }); + const prevBtn = screen.getByRole("button", { name: /previous slide/i }); + + expect(nextBtn).not.toBeDisabled(); + expect(prevBtn).toBeDisabled(); + }); + + it("should send correct data to downloadAll service", async () => { + const user = userEvent.setup(); + textsState[STATE_DATA] = ["Apple", "Banana"]; + render(PreviewManager); + + await user.click(screen.getByRole("button", { name: /download all/i })); + + expect(downloadService.downloadAll).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ filename: "Apple" }), + expect.objectContaining({ filename: "Banana" }), + ]), + ); + }); + + it("should call downloadPanel with current active text", async () => { + const user = userEvent.setup(); + textsState[STATE_DATA] = ["First", "Second"]; + konvaStageState.stage = { node: { id: "stage-ref" } } as unknown as Stage; + + render(PreviewManager); + + // Переходим на второй слайд + await user.click(screen.getByRole("button", { name: /next slide/i })); + await user.click(screen.getByRole("button", { name: /download current/i })); + + expect(downloadService.downloadPanel).toHaveBeenCalledWith(expect.anything(), "Second"); + }); + + it("should return to empty state when texts are removed", async () => { + textsState[STATE_DATA] = ["Temp"]; + render(PreviewManager); + + expect(screen.queryByLabelText(/Empty texts info/i)).not.toBeInTheDocument(); + + textsState[STATE_DATA] = []; + + await waitFor(() => { + expect(screen.getByLabelText(/Empty texts info/i)).toBeInTheDocument(); + }); + }); + + it("should automatically correct current index on items deletion", async () => { + const user = userEvent.setup(); + textsState[STATE_DATA] = ["1", "2", "3"]; + render(PreviewManager); + + // Уходим на последний слайд + await user.click(screen.getByRole("button", { name: /next slide/i })); + await user.click(screen.getByRole("button", { name: /next slide/i })); + + // Удаляем элементы. Индекс должен упасть с 2 до 0 + textsState[STATE_DATA] = ["Only one left"]; + + await waitFor(() => { + // Проверяем, что кнопка "Next" заблокировалась (значит мы на последнем/единственном) + expect(screen.getByRole("button", { name: /next slide/i })).toBeDisabled(); + expect(screen.getByRole("button", { name: /previous slide/i })).toBeDisabled(); + }); + }); +}); diff --git a/tests/unit/components/text/TextConfig.test.ts b/tests/unit/components/text/TextConfig.test.ts index d7f9b1f..4c6c06a 100644 --- a/tests/unit/components/text/TextConfig.test.ts +++ b/tests/unit/components/text/TextConfig.test.ts @@ -1,13 +1,13 @@ -import TextConfig from "$components/text/TextConfig.svelte"; -import { cleanup, render } from "@testing-library/svelte"; -import { afterEach, describe, it } from "vitest"; - -afterEach(() => { - cleanup(); -}); - -describe("TextConfig.svelte", () => { - it("should render without crashing", () => { - render(TextConfig); - }); -}); +import TextConfig from "$components/text/TextConfig.svelte"; +import { cleanup, render } from "@testing-library/svelte"; +import { afterEach, describe, it } from "vitest"; + +afterEach(() => { + cleanup(); +}); + +describe("TextConfig.svelte", () => { + it("should render without crashing", () => { + render(TextConfig); + }); +}); diff --git a/tests/unit/components/text/TextInlineEdit.test.ts b/tests/unit/components/text/TextInlineEdit.test.ts index 0f731f3..1ea1daa 100644 --- a/tests/unit/components/text/TextInlineEdit.test.ts +++ b/tests/unit/components/text/TextInlineEdit.test.ts @@ -1,36 +1,36 @@ -import TextInlineEdit from "$components/text/TextInlineEdit.svelte"; -import { cleanup, render, screen } from "@testing-library/svelte"; -import { afterEach, describe, expect, it } from "vitest"; - -afterEach(() => { - cleanup(); -}); - -describe("TextInlineEdit.svelte", () => { - it("should render with initial text", () => { - render(TextInlineEdit, { - props: { - id: 1, - text: "Test text", - ondelete: () => {}, - }, - }); - - const input = screen.getByRole("textbox"); - expect(input).toBeInTheDocument(); - expect(input).toHaveValue("Test text"); - }); - - it("should render with empty text", () => { - render(TextInlineEdit, { - props: { - id: 1, - text: "", - ondelete: () => {}, - }, - }); - - const input = screen.getByRole("textbox"); - expect(input).toHaveValue(""); - }); -}); +import TextInlineEdit from "$components/text/TextInlineEdit.svelte"; +import { cleanup, render, screen } from "@testing-library/svelte"; +import { afterEach, describe, expect, it } from "vitest"; + +afterEach(() => { + cleanup(); +}); + +describe("TextInlineEdit.svelte", () => { + it("should render with initial text", () => { + render(TextInlineEdit, { + props: { + id: 1, + text: "Test text", + ondelete: () => {}, + }, + }); + + const input = screen.getByRole("textbox"); + expect(input).toBeInTheDocument(); + expect(input).toHaveValue("Test text"); + }); + + it("should render with empty text", () => { + render(TextInlineEdit, { + props: { + id: 1, + text: "", + ondelete: () => {}, + }, + }); + + const input = screen.getByRole("textbox"); + expect(input).toHaveValue(""); + }); +}); diff --git a/tests/unit/components/text/TextInput.test.ts b/tests/unit/components/text/TextInput.test.ts index 9944d89..e65e87d 100644 --- a/tests/unit/components/text/TextInput.test.ts +++ b/tests/unit/components/text/TextInput.test.ts @@ -1,61 +1,61 @@ -import TextInput from "$components/text/TextInput.svelte"; -import { render, screen } from "@testing-library/svelte"; -import userEvent from "@testing-library/user-event"; -import { describe, expect, it, vi } from "vitest"; - -describe("TextInput.svelte", () => { - it("should render with initial value", () => { - render(TextInput, { - props: { - text: "Test text", - onenter: vi.fn(), - ariaLabel: "Test input", - }, - }); - - const input = screen.getByRole("textbox", { name: /test input/i }); - expect(input).toBeInTheDocument(); - expect(input).toHaveValue("Test text"); - }); - - it("should update value on input", async () => { - const user = userEvent.setup(); - render(TextInput, { - props: { - text: "Initial", - onenter: vi.fn(), - ariaLabel: "Test input", - }, - }); - - const input = screen.getByRole("textbox", { name: /test input/i }); - await user.clear(input); - await user.type(input, "Updated text"); - - expect(input).toHaveValue("Updated text"); - }); - - it("should call onenter only on Enter key and not on others", async () => { - const user = userEvent.setup(); - const onenterSpy = vi.fn(); - - render(TextInput, { - props: { - text: "test", - onenter: onenterSpy, - ariaLabel: "Test input", - }, - }); - - const input = screen.getByRole("textbox", { name: /test input/i }); - - await user.type(input, "abc"); - expect(onenterSpy).not.toHaveBeenCalled(); - - await user.keyboard("{Escape}"); - expect(onenterSpy).not.toHaveBeenCalled(); - - await user.type(input, "{Enter}"); - expect(onenterSpy).toHaveBeenCalledTimes(1); - }); -}); +import TextInput from "$components/text/TextInput.svelte"; +import { render, screen } from "@testing-library/svelte"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; + +describe("TextInput.svelte", () => { + it("should render with initial value", () => { + render(TextInput, { + props: { + text: "Test text", + onenter: vi.fn(), + ariaLabel: "Test input", + }, + }); + + const input = screen.getByRole("textbox", { name: /test input/i }); + expect(input).toBeInTheDocument(); + expect(input).toHaveValue("Test text"); + }); + + it("should update value on input", async () => { + const user = userEvent.setup(); + render(TextInput, { + props: { + text: "Initial", + onenter: vi.fn(), + ariaLabel: "Test input", + }, + }); + + const input = screen.getByRole("textbox", { name: /test input/i }); + await user.clear(input); + await user.type(input, "Updated text"); + + expect(input).toHaveValue("Updated text"); + }); + + it("should call onenter only on Enter key and not on others", async () => { + const user = userEvent.setup(); + const onenterSpy = vi.fn(); + + render(TextInput, { + props: { + text: "test", + onenter: onenterSpy, + ariaLabel: "Test input", + }, + }); + + const input = screen.getByRole("textbox", { name: /test input/i }); + + await user.type(input, "abc"); + expect(onenterSpy).not.toHaveBeenCalled(); + + await user.keyboard("{Escape}"); + expect(onenterSpy).not.toHaveBeenCalled(); + + await user.type(input, "{Enter}"); + expect(onenterSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/tests/unit/components/text/TextManager.test.ts b/tests/unit/components/text/TextManager.test.ts index 80ffb88..9480d3a 100644 --- a/tests/unit/components/text/TextManager.test.ts +++ b/tests/unit/components/text/TextManager.test.ts @@ -1,60 +1,60 @@ -import TextManager from "$components/text/TextManager.svelte"; -import { textsState } from "$states/texts.svelte"; -import { render, screen } from "@testing-library/svelte"; -import userEvent from "@testing-library/user-event"; -import { beforeEach, describe, expect, it } from "vitest"; - -describe("TextManager", () => { - beforeEach(() => { - textsState.clear(); - }); - - it("should add text to state and clear input on button click", async () => { - const user = userEvent.setup(); - render(TextManager); - - const input = screen.getByRole("textbox", { name: /input new text/i }); - const addButton = screen.getByRole("button", { name: /add text/i }); - - await user.type(input, "New Note"); - await user.click(addButton); - - expect(textsState.texts).toHaveLength(1); - expect(textsState.texts[0].text).toBe("New Note"); - expect(input).toHaveValue(""); - }); - - it("should add text on enter key", async () => { - const user = userEvent.setup(); - render(TextManager); - - const input = screen.getByRole("textbox", { name: /input new text/i }); - - await user.type(input, "Enter Note{Enter}"); - - expect(textsState.texts).toHaveLength(1); - expect(textsState.texts[0].text).toBe("Enter Note"); - }); - - it("should display all items from state", async () => { - textsState.addText("First"); - textsState.addText("Second"); - - render(TextManager); - - const items = screen.getAllByRole("listitem"); - expect(items).toHaveLength(2); - }); - - it("should remove text from state when delete button is clicked", async () => { - const user = userEvent.setup(); - textsState.addText("To be deleted"); - - render(TextManager); - - const deleteBtn = screen.getByRole("button", { name: /delete/i }); - await user.click(deleteBtn); - - expect(textsState.texts).toHaveLength(0); - }); -}); +import TextManager from "$components/text/TextManager.svelte"; +import { textsState } from "$states/texts.svelte"; +import { render, screen } from "@testing-library/svelte"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it } from "vitest"; + +describe("TextManager", () => { + beforeEach(() => { + textsState.clear(); + }); + + it("should add text to state and clear input on button click", async () => { + const user = userEvent.setup(); + render(TextManager); + + const input = screen.getByRole("textbox", { name: /input new text/i }); + const addButton = screen.getByRole("button", { name: /add text/i }); + + await user.type(input, "New Note"); + await user.click(addButton); + + expect(textsState.texts).toHaveLength(1); + expect(textsState.texts[0].text).toBe("New Note"); + expect(input).toHaveValue(""); + }); + + it("should add text on enter key", async () => { + const user = userEvent.setup(); + render(TextManager); + + const input = screen.getByRole("textbox", { name: /input new text/i }); + + await user.type(input, "Enter Note{Enter}"); + + expect(textsState.texts).toHaveLength(1); + expect(textsState.texts[0].text).toBe("Enter Note"); + }); + + it("should display all items from state", async () => { + textsState.addText("First"); + textsState.addText("Second"); + + render(TextManager); + + const items = screen.getAllByRole("listitem"); + expect(items).toHaveLength(2); + }); + + it("should remove text from state when delete button is clicked", async () => { + const user = userEvent.setup(); + textsState.addText("To be deleted"); + + render(TextManager); + + const deleteBtn = screen.getByRole("button", { name: /delete/i }); + await user.click(deleteBtn); + + expect(textsState.texts).toHaveLength(0); + }); +}); diff --git a/tests/unit/components/ui/Alignment.test.ts b/tests/unit/components/ui/Alignment.test.ts index c512204..d6aaab3 100644 --- a/tests/unit/components/ui/Alignment.test.ts +++ b/tests/unit/components/ui/Alignment.test.ts @@ -1,44 +1,43 @@ -import { TextAlign } from "$lib/constants"; -import { render, screen } from "@testing-library/svelte"; -import userEvent from "@testing-library/user-event"; -import { describe, expect, it } from "vitest"; -import AlignmentTest from "./AlignmentTest.svelte"; - -describe("Alignment.svelte", () => { - it("should update state for every button by clicking in sequence", async () => { - const user = userEvent.setup(); - render(AlignmentTest); - - const buttons = screen.getAllByRole("radio"); - const stateDisplay = screen.getByTestId("align-value"); - - const allButtonsToClick = [...buttons, buttons[0]]; - - for (const button of allButtonsToClick) { - const label = button.getAttribute("aria-label")?.toLowerCase() || ""; - - await user.click(button); - - const finalValue = stateDisplay.textContent?.toLowerCase() || ""; - expect(label).toContain(finalValue); - } - }); - - it("should have initial state from props", () => { - render(AlignmentTest, { props: { align: TextAlign.RIGHT } }); - expect(screen.getByTestId("align-value").textContent).toBe(TextAlign.RIGHT); - }); - - it("should sync with initial state and change state", async () => { - const user = userEvent.setup(); - const { rerender } = render(AlignmentTest, { props: { align: TextAlign.RIGHT } }); - const stateDisplay = screen.getByTestId("align-value"); - - expect(stateDisplay.textContent).toBe(TextAlign.RIGHT); - - await rerender({ align: TextAlign.CENTER }); - - const centerBtn = screen.getByRole("radio", { name: new RegExp(TextAlign.CENTER, "i") }); - expect(centerBtn).toBeChecked(); - }); -}); +import { TextAlign } from "$lib/constants"; +import { render, screen } from "@testing-library/svelte"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it } from "vitest"; +import AlignmentTest from "./AlignmentTest.svelte"; + +describe("Alignment.svelte", () => { + it("should update state for every button by clicking in sequence", async () => { + const user = userEvent.setup(); + render(AlignmentTest); + + const buttons = screen.getAllByRole("radio"); + const stateDisplay = screen.getByTestId("align-value"); + + const allButtonsToClick = [...buttons, buttons[0]]; + + for (const button of allButtonsToClick) { + const label = button.getAttribute("aria-label")?.toLowerCase() || ""; + + await user.click(button); + + const finalValue = stateDisplay.textContent?.toLowerCase() || ""; + expect(label).toContain(finalValue); + } + }); + + it("should have initial state from props", () => { + render(AlignmentTest, { props: { align: TextAlign.RIGHT } }); + expect(screen.getByTestId("align-value").textContent).toBe(TextAlign.RIGHT); + }); + + it("should sync with initial state and change state", async () => { + const { rerender } = render(AlignmentTest, { props: { align: TextAlign.RIGHT } }); + const stateDisplay = screen.getByTestId("align-value"); + + expect(stateDisplay.textContent).toBe(TextAlign.RIGHT); + + await rerender({ align: TextAlign.CENTER }); + + const centerBtn = screen.getByRole("radio", { name: new RegExp(TextAlign.CENTER, "i") }); + expect(centerBtn).toBeChecked(); + }); +}); diff --git a/tests/unit/components/ui/AlignmentTest.svelte b/tests/unit/components/ui/AlignmentTest.svelte index 48b90a6..e347d80 100644 --- a/tests/unit/components/ui/AlignmentTest.svelte +++ b/tests/unit/components/ui/AlignmentTest.svelte @@ -1,11 +1,11 @@ - - - - -
{align}
+ + + + +
{align}
diff --git a/tests/unit/components/ui/Badge.test.ts b/tests/unit/components/ui/Badge.test.ts index ba2f18c..a9e8fc4 100644 --- a/tests/unit/components/ui/Badge.test.ts +++ b/tests/unit/components/ui/Badge.test.ts @@ -1,43 +1,43 @@ -import Badge from "$components/ui/Badge.svelte"; -import { render, screen } from "@testing-library/svelte"; -import { describe, expect, it } from "vitest"; - -describe("Badge.svelte", () => { - it("should render with string text", () => { - render(Badge, { - props: { - text: "Test Badge", - }, - }); - - expect(screen.getByText("Test Badge")).toBeInTheDocument(); - }); - - it("should render with number text", () => { - render(Badge, { - props: { - text: 42, - }, - }); - - expect(screen.getByText("42")).toBeInTheDocument(); - }); - - it("should render with empty string", () => { - render(Badge, { - props: { - text: "", - }, - }); - }); - - it("should render with zero", () => { - render(Badge, { - props: { - text: 0, - }, - }); - - expect(screen.getByText("0")).toBeInTheDocument(); - }); -}); +import Badge from "$components/ui/Badge.svelte"; +import { render, screen } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; + +describe("Badge.svelte", () => { + it("should render with string text", () => { + render(Badge, { + props: { + text: "Test Badge", + }, + }); + + expect(screen.getByText("Test Badge")).toBeInTheDocument(); + }); + + it("should render with number text", () => { + render(Badge, { + props: { + text: 42, + }, + }); + + expect(screen.getByText("42")).toBeInTheDocument(); + }); + + it("should render with empty string", () => { + render(Badge, { + props: { + text: "", + }, + }); + }); + + it("should render with zero", () => { + render(Badge, { + props: { + text: 0, + }, + }); + + expect(screen.getByText("0")).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/ui/Button.test.ts b/tests/unit/components/ui/Button.test.ts index ec92e5a..d843021 100644 --- a/tests/unit/components/ui/Button.test.ts +++ b/tests/unit/components/ui/Button.test.ts @@ -1,95 +1,95 @@ -import Button from "$components/ui/Button.svelte"; -import { render, screen } from "@testing-library/svelte"; -import userEvent from "@testing-library/user-event"; -import { describe, expect, it, vi } from "vitest"; -import MockIcon from "./MockIcon.svelte"; - -describe("Button.svelte", () => { - it("should render with icon and label", () => { - render(Button, { - props: { - icon: MockIcon, - label: "Test Button", - ariaLabel: "Test button", - }, - }); - - expect(screen.getByText("Test Button")).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /test button/i })).toBeInTheDocument(); - }); - - it("should render with icon only", () => { - render(Button, { - props: { - icon: MockIcon, - ariaLabel: "Test button", - }, - }); - - const button = screen.getByRole("button", { name: /test button/i }); - expect(button).toBeInTheDocument(); - expect(button.textContent.trim()).toBe(""); - }); - - it("should call onclick handler", async () => { - const onclick = vi.fn(); - const user = userEvent.setup(); - - render(Button, { - props: { - icon: MockIcon, - label: "Click me", - onclick, - ariaLabel: "Test button", - }, - }); - - const button = screen.getByRole("button", { name: /test button/i }); - await user.click(button); - - expect(onclick).toHaveBeenCalledTimes(1); - }); - - it("should not throw error when clicked without onclick prop", async () => { - const user = userEvent.setup(); - - render(Button, { - props: { - icon: MockIcon, - label: "Click me", - ariaLabel: "Test button", - }, - }); - - const button = screen.getByRole("button", { name: /test button/i }); - - expect(() => user.click(button)).not.toThrow(); - }); - - it("should be disabled when disabled prop is true", () => { - render(Button, { - props: { - icon: MockIcon, - label: "Disabled", - disabled: true, - ariaLabel: "Test button", - }, - }); - - const button = screen.getByRole("button", { name: /test button/i }); - expect(button).toBeDisabled(); - }); - - it("should not be disabled by default", () => { - render(Button, { - props: { - icon: MockIcon, - label: "Enabled", - ariaLabel: "Test button", - }, - }); - - const button = screen.getByRole("button", { name: /test button/i }); - expect(button).not.toBeDisabled(); - }); -}); +import Button from "$components/ui/Button.svelte"; +import { render, screen } from "@testing-library/svelte"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import MockIcon from "./MockIcon.svelte"; + +describe("Button.svelte", () => { + it("should render with icon and label", () => { + render(Button, { + props: { + icon: MockIcon, + label: "Test Button", + ariaLabel: "Test button", + }, + }); + + expect(screen.getByText("Test Button")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /test button/i })).toBeInTheDocument(); + }); + + it("should render with icon only", () => { + render(Button, { + props: { + icon: MockIcon, + ariaLabel: "Test button", + }, + }); + + const button = screen.getByRole("button", { name: /test button/i }); + expect(button).toBeInTheDocument(); + expect(button.textContent.trim()).toBe(""); + }); + + it("should call onclick handler", async () => { + const onclick = vi.fn(); + const user = userEvent.setup(); + + render(Button, { + props: { + icon: MockIcon, + label: "Click me", + onclick, + ariaLabel: "Test button", + }, + }); + + const button = screen.getByRole("button", { name: /test button/i }); + await user.click(button); + + expect(onclick).toHaveBeenCalledTimes(1); + }); + + it("should not throw error when clicked without onclick prop", async () => { + const user = userEvent.setup(); + + render(Button, { + props: { + icon: MockIcon, + label: "Click me", + ariaLabel: "Test button", + }, + }); + + const button = screen.getByRole("button", { name: /test button/i }); + + expect(() => user.click(button)).not.toThrow(); + }); + + it("should be disabled when disabled prop is true", () => { + render(Button, { + props: { + icon: MockIcon, + label: "Disabled", + disabled: true, + ariaLabel: "Test button", + }, + }); + + const button = screen.getByRole("button", { name: /test button/i }); + expect(button).toBeDisabled(); + }); + + it("should not be disabled by default", () => { + render(Button, { + props: { + icon: MockIcon, + label: "Enabled", + ariaLabel: "Test button", + }, + }); + + const button = screen.getByRole("button", { name: /test button/i }); + expect(button).not.toBeDisabled(); + }); +}); diff --git a/tests/unit/components/ui/ColorPicker.test.ts b/tests/unit/components/ui/ColorPicker.test.ts index 50b7203..53c4e5f 100644 --- a/tests/unit/components/ui/ColorPicker.test.ts +++ b/tests/unit/components/ui/ColorPicker.test.ts @@ -1,13 +1,13 @@ -import ColorPicker from "$components/ui/ColorPicker.svelte"; -import { render } from "@testing-library/svelte"; -import { describe, it } from "vitest"; - -describe("ColorPicker.svelte", () => { - it("should render without crashing", () => { - render(ColorPicker, { - props: { - value: "#ffffff", - }, - }); - }); -}); +import ColorPicker from "$components/ui/ColorPicker.svelte"; +import { render } from "@testing-library/svelte"; +import { describe, it } from "vitest"; + +describe("ColorPicker.svelte", () => { + it("should render without crashing", () => { + render(ColorPicker, { + props: { + value: "#ffffff", + }, + }); + }); +}); diff --git a/tests/unit/components/ui/MockIcon.svelte b/tests/unit/components/ui/MockIcon.svelte index 2785c87..8ad7227 100644 --- a/tests/unit/components/ui/MockIcon.svelte +++ b/tests/unit/components/ui/MockIcon.svelte @@ -1,3 +1,3 @@ - - - + + + diff --git a/tests/unit/components/ui/RangeSlider.test.ts b/tests/unit/components/ui/RangeSlider.test.ts index 89cd1ff..e51a6f5 100644 --- a/tests/unit/components/ui/RangeSlider.test.ts +++ b/tests/unit/components/ui/RangeSlider.test.ts @@ -1,21 +1,21 @@ -import RangeSlider from "$components/ui/RangeSlider.svelte"; -import { fireEvent, render, screen } from "@testing-library/svelte"; -import { describe, expect, it, vi } from "vitest"; - -describe("RangeSlider.svelte", () => { - it("should call onchange handler", async () => { - const onchange = vi.fn(); - - render(RangeSlider, { - props: { - value: 50, - onchange, - }, - }); - - const slider = screen.getByRole("slider"); - await fireEvent.change(slider, { target: { value: "75" } }); - - expect(onchange).toHaveBeenCalledTimes(1); - }); -}); +import RangeSlider from "$components/ui/RangeSlider.svelte"; +import { fireEvent, render, screen } from "@testing-library/svelte"; +import { describe, expect, it, vi } from "vitest"; + +describe("RangeSlider.svelte", () => { + it("should call onchange handler", async () => { + const onchange = vi.fn(); + + render(RangeSlider, { + props: { + value: 50, + onchange, + }, + }); + + const slider = screen.getByRole("slider"); + await fireEvent.change(slider, { target: { value: "75" } }); + + expect(onchange).toHaveBeenCalledTimes(1); + }); +}); diff --git a/tests/unit/components/ui/SelectFont.test.ts b/tests/unit/components/ui/SelectFont.test.ts index ae966b4..016d10e 100644 --- a/tests/unit/components/ui/SelectFont.test.ts +++ b/tests/unit/components/ui/SelectFont.test.ts @@ -1,13 +1,13 @@ -import SelectFont from "$components/ui/SelectFont.svelte"; -import { render } from "@testing-library/svelte"; -import { describe, it } from "vitest"; - -describe("SelectFont.svelte", () => { - it("should render without crashing", () => { - render(SelectFont, { - props: { - value: "Arial", - }, - }); - }); -}); +import SelectFont from "$components/ui/SelectFont.svelte"; +import { render } from "@testing-library/svelte"; +import { describe, it } from "vitest"; + +describe("SelectFont.svelte", () => { + it("should render without crashing", () => { + render(SelectFont, { + props: { + value: "Arial", + }, + }); + }); +}); diff --git a/tests/unit/constants.test.ts b/tests/unit/constants.test.ts index d2f56ea..9dd643b 100644 --- a/tests/unit/constants.test.ts +++ b/tests/unit/constants.test.ts @@ -1,68 +1,70 @@ -import * as Constants from "$lib/constants"; -import fs from "fs"; -import path from "path"; -import { describe, expect, it } from "vitest"; - -describe("Application Constants Logic", () => { - describe("Typography & Panel Constraints", () => { - it("should have default values within allowed boundaries", () => { - const { TYPOGRAPHY, PANEL_SETTINGS } = Constants; - - expect(TYPOGRAPHY.FONT_SIZE_DEFAULT).toBeGreaterThanOrEqual(TYPOGRAPHY.FONT_SIZE_MIN); - expect(TYPOGRAPHY.FONT_SIZE_DEFAULT).toBeLessThanOrEqual(TYPOGRAPHY.FONT_SIZE_MAX); - - expect(TYPOGRAPHY.FONT_FAMILIES).toContain(TYPOGRAPHY.FONT_FAMILY_DEFAULT); - - expect(PANEL_SETTINGS.PANEL_HEIGHT_DEFAULT).toBeLessThanOrEqual(PANEL_SETTINGS.PANEL_HEIGHT_MAX); - - expect(TYPOGRAPHY.VERTICAL_OFFSET_MAX).toBeGreaterThan(TYPOGRAPHY.VERTICAL_OFFSET_MIN); - }); - - it("should not have duplicate values in lists", () => { - const { TYPOGRAPHY, IMAGE_SETTINGS } = Constants; - - const uniqueFonts = new Set(TYPOGRAPHY.FONT_FAMILIES); - expect(uniqueFonts.size).toBe(TYPOGRAPHY.FONT_FAMILIES.length); - - const uniqueFormats = new Set(IMAGE_SETTINGS.SUPPORTED_FORMATS); - expect(uniqueFormats.size).toBe(IMAGE_SETTINGS.SUPPORTED_FORMATS.length); - }); - }); - - describe("Integrity & Formats", () => { - it("should have valid format patterns for colors and mime-types", () => { - const { TYPOGRAPHY, IMAGE_SETTINGS } = Constants; - - expect(TYPOGRAPHY.TEXT_COLOR_DEFAULT).toMatch(/^#[0-9a-f]{6}$/i); - - IMAGE_SETTINGS.SUPPORTED_FORMATS.forEach((format) => { - expect(format).toMatch(/^image\/(jpeg|jpg|png|webp|gif)$/); - }); - }); - }); - - describe("Environment Specifics", () => { - it("should set transition duration to 0 in test mode", () => { - expect(Constants.TRANSITION_DURATION).toBe(0); - }); - }); - - describe("Global Contract (Snapshot)", () => { - it("should match the previous configuration snapshot", () => { - expect(Constants).toMatchSnapshot(); - }); - }); - - describe("Assets Existence", () => { - it("should verify that the default background image exists", () => { - const { DEFAULT_BACKGROUND_IMAGE } = Constants.PANEL_SETTINGS; - - const relativePath = DEFAULT_BACKGROUND_IMAGE.replace(/^\.\//, ""); - const fullPath = path.resolve(process.cwd(), "static", relativePath); - - const exists = fs.existsSync(fullPath); - - expect(exists, `Image not found at: ${fullPath}`).toBe(true); - }); - }); -}); +import * as Constants from "$lib/constants"; +import fs from "fs"; +import path from "path"; +import { describe, expect, it } from "vitest"; + +describe("Application Constants Logic", () => { + describe("Typography & Panel Constraints", () => { + it("should have default values within allowed boundaries", () => { + const { TYPOGRAPHY, PANEL_SETTINGS } = Constants; + + expect(TYPOGRAPHY.FONT_SIZE_DEFAULT).toBeGreaterThanOrEqual(TYPOGRAPHY.FONT_SIZE_MIN); + expect(TYPOGRAPHY.FONT_SIZE_DEFAULT).toBeLessThanOrEqual(TYPOGRAPHY.FONT_SIZE_MAX); + + expect(TYPOGRAPHY.FONT_FAMILIES).toContain(TYPOGRAPHY.FONT_FAMILY_DEFAULT); + + expect(PANEL_SETTINGS.PANEL_HEIGHT_DEFAULT).toBeLessThanOrEqual( + PANEL_SETTINGS.PANEL_HEIGHT_MAX, + ); + + expect(TYPOGRAPHY.VERTICAL_OFFSET_MAX).toBeGreaterThan(TYPOGRAPHY.VERTICAL_OFFSET_MIN); + }); + + it("should not have duplicate values in lists", () => { + const { TYPOGRAPHY, IMAGE_SETTINGS } = Constants; + + const uniqueFonts = new Set(TYPOGRAPHY.FONT_FAMILIES); + expect(uniqueFonts.size).toBe(TYPOGRAPHY.FONT_FAMILIES.length); + + const uniqueFormats = new Set(IMAGE_SETTINGS.SUPPORTED_FORMATS); + expect(uniqueFormats.size).toBe(IMAGE_SETTINGS.SUPPORTED_FORMATS.length); + }); + }); + + describe("Integrity & Formats", () => { + it("should have valid format patterns for colors and mime-types", () => { + const { TYPOGRAPHY, IMAGE_SETTINGS } = Constants; + + expect(TYPOGRAPHY.TEXT_COLOR_DEFAULT).toMatch(/^#[0-9a-f]{6}$/i); + + IMAGE_SETTINGS.SUPPORTED_FORMATS.forEach((format) => { + expect(format).toMatch(/^image\/(jpeg|jpg|png|webp|gif)$/); + }); + }); + }); + + describe("Environment Specifics", () => { + it("should set transition duration to 0 in test mode", () => { + expect(Constants.TRANSITION_DURATION).toBe(0); + }); + }); + + describe("Global Contract (Snapshot)", () => { + it("should match the previous configuration snapshot", () => { + expect(Constants).toMatchSnapshot(); + }); + }); + + describe("Assets Existence", () => { + it("should verify that the default background image exists", () => { + const { DEFAULT_BACKGROUND_IMAGE } = Constants.PANEL_SETTINGS; + + const relativePath = DEFAULT_BACKGROUND_IMAGE.replace(/^\.\//, ""); + const fullPath = path.resolve(process.cwd(), "static", relativePath); + + const exists = fs.existsSync(fullPath); + + expect(exists, `Image not found at: ${fullPath}`).toBe(true); + }); + }); +}); diff --git a/tests/unit/errorTypes.test.ts b/tests/unit/errorTypes.test.ts index c55f03f..35b1ca3 100644 --- a/tests/unit/errorTypes.test.ts +++ b/tests/unit/errorTypes.test.ts @@ -1,207 +1,207 @@ -import { AppError, CanvasError, ImageError, StorageError, TextError } from "$lib/error.types"; -import { describe, expect, it } from "vitest"; - -describe("error.types", () => { - describe("AppError", () => { - it("should create AppError with message and code", () => { - const error = new AppError("Test error message", "TEST_CODE"); - - expect(error).toBeInstanceOf(Error); - expect(error).toBeInstanceOf(AppError); - expect(error.name).toBe("AppError"); - expect(error.message).toBe("Test error message"); - expect(error.code).toBe("TEST_CODE"); - expect(error.details).toBeUndefined(); - }); - - it("should create AppError with message, code and details", () => { - const details = { userId: 123, action: "test" }; - const error = new AppError("Test error message", "TEST_CODE", details); - - expect(error.details).toEqual(details); - expect(error.message).toBe("Test error message"); - expect(error.code).toBe("TEST_CODE"); - }); - - it("should have stack trace", () => { - const error = new AppError("Test error", "TEST_CODE"); - - expect(error.stack).toBeDefined(); - expect(typeof error.stack).toBe("string"); - }); - - it("should be throwable and catchable", () => { - expect(() => { - throw new AppError("Test error", "TEST_CODE"); - }).toThrow(AppError); - }); - - it("should be catchable as Error", () => { - expect(() => { - throw new AppError("Test error", "TEST_CODE"); - }).toThrow(Error); - }); - }); - - describe("ImageError", () => { - it("should create ImageError with message", () => { - const error = new ImageError("Image loading failed"); - - expect(error).toBeInstanceOf(Error); - expect(error).toBeInstanceOf(AppError); - expect(error).toBeInstanceOf(ImageError); - expect(error.name).toBe("AppError"); - expect(error.message).toBe("Image loading failed"); - expect(error.code).toBe("IMAGE_ERROR"); - }); - - it("should have correct error code", () => { - const error = new ImageError("Test"); - - expect(error.code).toBe("IMAGE_ERROR"); - }); - - it("should be identifiable as ImageError", () => { - const error = new ImageError("Test"); - - expect(error instanceof ImageError).toBe(true); - }); - }); - - describe("TextError", () => { - it("should create TextError with message", () => { - const error = new TextError("Text validation failed"); - - expect(error).toBeInstanceOf(Error); - expect(error).toBeInstanceOf(AppError); - expect(error).toBeInstanceOf(TextError); - expect(error.name).toBe("AppError"); - expect(error.message).toBe("Text validation failed"); - expect(error.code).toBe("TEXT_ERROR"); - }); - - it("should have correct error code", () => { - const error = new TextError("Test"); - - expect(error.code).toBe("TEXT_ERROR"); - }); - - it("should be identifiable as TextError", () => { - const error = new TextError("Test"); - - expect(error instanceof TextError).toBe(true); - }); - }); - - describe("CanvasError", () => { - it("should create CanvasError with message", () => { - const error = new CanvasError("Canvas rendering failed"); - - expect(error).toBeInstanceOf(Error); - expect(error).toBeInstanceOf(AppError); - expect(error).toBeInstanceOf(CanvasError); - expect(error.name).toBe("AppError"); - expect(error.message).toBe("Canvas rendering failed"); - expect(error.code).toBe("CANVAS_ERROR"); - }); - - it("should have correct error code", () => { - const error = new CanvasError("Test"); - - expect(error.code).toBe("CANVAS_ERROR"); - }); - - it("should be identifiable as CanvasError", () => { - const error = new CanvasError("Test"); - - expect(error instanceof CanvasError).toBe(true); - }); - }); - - describe("StorageError", () => { - it("should create StorageError with message", () => { - const error = new StorageError("Storage operation failed"); - - expect(error).toBeInstanceOf(Error); - expect(error).toBeInstanceOf(AppError); - expect(error).toBeInstanceOf(StorageError); - expect(error.name).toBe("AppError"); - expect(error.message).toBe("Storage operation failed"); - expect(error.code).toBe("STORAGE_ERROR"); - }); - - it("should have correct error code", () => { - const error = new StorageError("Test"); - - expect(error.code).toBe("STORAGE_ERROR"); - }); - - it("should be identifiable as StorageError", () => { - const error = new StorageError("Test"); - - expect(error instanceof StorageError).toBe(true); - }); - }); - - describe("ErrorType union", () => { - it("should accept all error types", () => { - const errors: Array = [ - new AppError("Test", "TEST"), - new ImageError("Test"), - new TextError("Test"), - new CanvasError("Test"), - new StorageError("Test"), - ]; - - errors.forEach((error) => { - expect(error).toBeInstanceOf(AppError); - expect(error).toBeInstanceOf(Error); - }); - }); - - it("should allow type narrowing with instanceof", () => { - const errors: Array = [ - new ImageError("Test"), - new TextError("Test"), - ]; - - const imageErrors = errors.filter((e) => e instanceof ImageError); - const textErrors = errors.filter((e) => e instanceof TextError); - - expect(imageErrors).toHaveLength(1); - expect(textErrors).toHaveLength(1); - }); - }); - - describe("Error handling patterns", () => { - it("should handle errors in try-catch blocks", () => { - let caughtError: AppError | null = null; - - try { - throw new ImageError("Image failed to load"); - } catch (error) { - if (error instanceof AppError) { - caughtError = error; - } - } - - expect(caughtError).not.toBeNull(); - expect(caughtError?.code).toBe("IMAGE_ERROR"); - }); - - it("should preserve error details through error handling", () => { - const originalError = new AppError("Test", "TEST", { id: 123 }); - let caughtError: AppError | null = null; - - try { - throw originalError; - } catch (error) { - if (error instanceof AppError) { - caughtError = error; - } - } - - expect(caughtError?.details).toEqual({ id: 123 }); - }); - }); -}); +import { AppError, CanvasError, ImageError, StorageError, TextError } from "$lib/error.types"; +import { describe, expect, it } from "vitest"; + +describe("error.types", () => { + describe("AppError", () => { + it("should create AppError with message and code", () => { + const error = new AppError("Test error message", "TEST_CODE"); + + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AppError); + expect(error.name).toBe("AppError"); + expect(error.message).toBe("Test error message"); + expect(error.code).toBe("TEST_CODE"); + expect(error.details).toBeUndefined(); + }); + + it("should create AppError with message, code and details", () => { + const details = { userId: 123, action: "test" }; + const error = new AppError("Test error message", "TEST_CODE", details); + + expect(error.details).toEqual(details); + expect(error.message).toBe("Test error message"); + expect(error.code).toBe("TEST_CODE"); + }); + + it("should have stack trace", () => { + const error = new AppError("Test error", "TEST_CODE"); + + expect(error.stack).toBeDefined(); + expect(typeof error.stack).toBe("string"); + }); + + it("should be throwable and catchable", () => { + expect(() => { + throw new AppError("Test error", "TEST_CODE"); + }).toThrow(AppError); + }); + + it("should be catchable as Error", () => { + expect(() => { + throw new AppError("Test error", "TEST_CODE"); + }).toThrow(Error); + }); + }); + + describe("ImageError", () => { + it("should create ImageError with message", () => { + const error = new ImageError("Image loading failed"); + + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AppError); + expect(error).toBeInstanceOf(ImageError); + expect(error.name).toBe("AppError"); + expect(error.message).toBe("Image loading failed"); + expect(error.code).toBe("IMAGE_ERROR"); + }); + + it("should have correct error code", () => { + const error = new ImageError("Test"); + + expect(error.code).toBe("IMAGE_ERROR"); + }); + + it("should be identifiable as ImageError", () => { + const error = new ImageError("Test"); + + expect(error instanceof ImageError).toBe(true); + }); + }); + + describe("TextError", () => { + it("should create TextError with message", () => { + const error = new TextError("Text validation failed"); + + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AppError); + expect(error).toBeInstanceOf(TextError); + expect(error.name).toBe("AppError"); + expect(error.message).toBe("Text validation failed"); + expect(error.code).toBe("TEXT_ERROR"); + }); + + it("should have correct error code", () => { + const error = new TextError("Test"); + + expect(error.code).toBe("TEXT_ERROR"); + }); + + it("should be identifiable as TextError", () => { + const error = new TextError("Test"); + + expect(error instanceof TextError).toBe(true); + }); + }); + + describe("CanvasError", () => { + it("should create CanvasError with message", () => { + const error = new CanvasError("Canvas rendering failed"); + + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AppError); + expect(error).toBeInstanceOf(CanvasError); + expect(error.name).toBe("AppError"); + expect(error.message).toBe("Canvas rendering failed"); + expect(error.code).toBe("CANVAS_ERROR"); + }); + + it("should have correct error code", () => { + const error = new CanvasError("Test"); + + expect(error.code).toBe("CANVAS_ERROR"); + }); + + it("should be identifiable as CanvasError", () => { + const error = new CanvasError("Test"); + + expect(error instanceof CanvasError).toBe(true); + }); + }); + + describe("StorageError", () => { + it("should create StorageError with message", () => { + const error = new StorageError("Storage operation failed"); + + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AppError); + expect(error).toBeInstanceOf(StorageError); + expect(error.name).toBe("AppError"); + expect(error.message).toBe("Storage operation failed"); + expect(error.code).toBe("STORAGE_ERROR"); + }); + + it("should have correct error code", () => { + const error = new StorageError("Test"); + + expect(error.code).toBe("STORAGE_ERROR"); + }); + + it("should be identifiable as StorageError", () => { + const error = new StorageError("Test"); + + expect(error instanceof StorageError).toBe(true); + }); + }); + + describe("ErrorType union", () => { + it("should accept all error types", () => { + const errors: Array = [ + new AppError("Test", "TEST"), + new ImageError("Test"), + new TextError("Test"), + new CanvasError("Test"), + new StorageError("Test"), + ]; + + errors.forEach((error) => { + expect(error).toBeInstanceOf(AppError); + expect(error).toBeInstanceOf(Error); + }); + }); + + it("should allow type narrowing with instanceof", () => { + const errors: Array = [ + new ImageError("Test"), + new TextError("Test"), + ]; + + const imageErrors = errors.filter((e) => e instanceof ImageError); + const textErrors = errors.filter((e) => e instanceof TextError); + + expect(imageErrors).toHaveLength(1); + expect(textErrors).toHaveLength(1); + }); + }); + + describe("Error handling patterns", () => { + it("should handle errors in try-catch blocks", () => { + let caughtError: AppError | null = null; + + try { + throw new ImageError("Image failed to load"); + } catch (error) { + if (error instanceof AppError) { + caughtError = error; + } + } + + expect(caughtError).not.toBeNull(); + expect(caughtError?.code).toBe("IMAGE_ERROR"); + }); + + it("should preserve error details through error handling", () => { + const originalError = new AppError("Test", "TEST", { id: 123 }); + let caughtError: AppError | null = null; + + try { + throw originalError; + } catch (error) { + if (error instanceof AppError) { + caughtError = error; + } + } + + expect(caughtError?.details).toEqual({ id: 123 }); + }); + }); +}); diff --git a/tests/unit/errorUtils.test.ts b/tests/unit/errorUtils.test.ts index cd2466d..7dc2b58 100644 --- a/tests/unit/errorUtils.test.ts +++ b/tests/unit/errorUtils.test.ts @@ -1,155 +1,155 @@ -import { AppError, CanvasError, ImageError, StorageError, TextError } from "$lib/error.types"; -import { createError, formatError, logError } from "$lib/utils/errorUtils"; -import { describe, expect, it, vi } from "vitest"; - -describe("errorUtils", () => { - describe("formatError", () => { - it("should format AppError", () => { - const error = new AppError("Test error", "TEST_CODE"); - const result = formatError(error, "Default message"); - - expect(result).toBe("Default message: Test error"); - }); - - it("should format Error", () => { - const error = new Error("Test error"); - const result = formatError(error, "Default message"); - - expect(result).toBe("Default message: Test error"); - }); - - it("should format string error", () => { - const error = "String error message"; - const result = formatError(error, "Default message"); - - expect(result).toBe("Default message: String error message"); - }); - - it("should format unknown error", () => { - const error = { custom: "object" }; - const result = formatError(error, "Default message"); - - expect(result).toBe("Default message: Произошла неизвестная ошибка"); - }); - - it("should use default message when not provided", () => { - const error = new Error("Test error"); - const result = formatError(error); - - expect(result).toBe("Произошла ошибка: Test error"); - }); - - it("should format ImageError", () => { - const error = new ImageError("Image failed"); - const result = formatError(error, "Default message"); - - expect(result).toBe("Default message: Image failed"); - }); - - it("should format TextError", () => { - const error = new TextError("Text failed"); - const result = formatError(error, "Default message"); - - expect(result).toBe("Default message: Text failed"); - }); - - it("should format CanvasError", () => { - const error = new CanvasError("Canvas failed"); - const result = formatError(error, "Default message"); - - expect(result).toBe("Default message: Canvas failed"); - }); - - it("should format StorageError", () => { - const error = new StorageError("Storage failed"); - const result = formatError(error, "Default message"); - - expect(result).toBe("Default message: Storage failed"); - }); - }); - - describe("createError", () => { - it("should create AppError with message and code", () => { - const error = createError("Test error", "TEST_CODE"); - - expect(error).toBeInstanceOf(AppError); - expect(error.message).toBe("Test error"); - expect(error.code).toBe("TEST_CODE"); - expect(error.details).toBeUndefined(); - }); - - it("should create AppError with message, code and details", () => { - const details = { userId: 123, action: "test" }; - const error = createError("Test error", "TEST_CODE", details); - - expect(error.details).toEqual(details); - expect(error.message).toBe("Test error"); - expect(error.code).toBe("TEST_CODE"); - }); - }); - - describe("logError", () => { - it("should log Error with stack", () => { - const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - const error = new Error("Test error"); - - logError(error, "Test context"); - - expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", { - error, - context: "Test context", - timestamp: expect.any(String), - stack: expect.any(String), - }); - - consoleSpy.mockRestore(); - }); - - it("should log AppError with stack", () => { - const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - const error = new AppError("Test error", "TEST_CODE"); - - logError(error, "Test context"); - - expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", { - error, - context: "Test context", - timestamp: expect.any(String), - stack: expect.any(String), - }); - - consoleSpy.mockRestore(); - }); - - it("should log string error without stack", () => { - const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - - logError("String error", "Test context"); - - expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", { - error: "String error", - context: "Test context", - timestamp: expect.any(String), - stack: undefined, - }); - - consoleSpy.mockRestore(); - }); - - it("should log error without context", () => { - const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - const error = new Error("Test error"); - - logError(error); - - expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", { - error, - context: undefined, - timestamp: expect.any(String), - stack: expect.any(String), - }); - - consoleSpy.mockRestore(); - }); - }); -}); +import { AppError, CanvasError, ImageError, StorageError, TextError } from "$lib/error.types"; +import { createError, formatError, logError } from "$lib/utils/errorUtils"; +import { describe, expect, it, vi } from "vitest"; + +describe("errorUtils", () => { + describe("formatError", () => { + it("should format AppError", () => { + const error = new AppError("Test error", "TEST_CODE"); + const result = formatError(error, "Default message"); + + expect(result).toBe("Default message: Test error"); + }); + + it("should format Error", () => { + const error = new Error("Test error"); + const result = formatError(error, "Default message"); + + expect(result).toBe("Default message: Test error"); + }); + + it("should format string error", () => { + const error = "String error message"; + const result = formatError(error, "Default message"); + + expect(result).toBe("Default message: String error message"); + }); + + it("should format unknown error", () => { + const error = { custom: "object" }; + const result = formatError(error, "Default message"); + + expect(result).toBe("Default message: Произошла неизвестная ошибка"); + }); + + it("should use default message when not provided", () => { + const error = new Error("Test error"); + const result = formatError(error); + + expect(result).toBe("Произошла ошибка: Test error"); + }); + + it("should format ImageError", () => { + const error = new ImageError("Image failed"); + const result = formatError(error, "Default message"); + + expect(result).toBe("Default message: Image failed"); + }); + + it("should format TextError", () => { + const error = new TextError("Text failed"); + const result = formatError(error, "Default message"); + + expect(result).toBe("Default message: Text failed"); + }); + + it("should format CanvasError", () => { + const error = new CanvasError("Canvas failed"); + const result = formatError(error, "Default message"); + + expect(result).toBe("Default message: Canvas failed"); + }); + + it("should format StorageError", () => { + const error = new StorageError("Storage failed"); + const result = formatError(error, "Default message"); + + expect(result).toBe("Default message: Storage failed"); + }); + }); + + describe("createError", () => { + it("should create AppError with message and code", () => { + const error = createError("Test error", "TEST_CODE"); + + expect(error).toBeInstanceOf(AppError); + expect(error.message).toBe("Test error"); + expect(error.code).toBe("TEST_CODE"); + expect(error.details).toBeUndefined(); + }); + + it("should create AppError with message, code and details", () => { + const details = { userId: 123, action: "test" }; + const error = createError("Test error", "TEST_CODE", details); + + expect(error.details).toEqual(details); + expect(error.message).toBe("Test error"); + expect(error.code).toBe("TEST_CODE"); + }); + }); + + describe("logError", () => { + it("should log Error with stack", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const error = new Error("Test error"); + + logError(error, "Test context"); + + expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", { + error, + context: "Test context", + timestamp: expect.any(String), + stack: expect.any(String), + }); + + consoleSpy.mockRestore(); + }); + + it("should log AppError with stack", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const error = new AppError("Test error", "TEST_CODE"); + + logError(error, "Test context"); + + expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", { + error, + context: "Test context", + timestamp: expect.any(String), + stack: expect.any(String), + }); + + consoleSpy.mockRestore(); + }); + + it("should log string error without stack", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + logError("String error", "Test context"); + + expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", { + error: "String error", + context: "Test context", + timestamp: expect.any(String), + stack: undefined, + }); + + consoleSpy.mockRestore(); + }); + + it("should log error without context", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const error = new Error("Test error"); + + logError(error); + + expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", { + error, + context: undefined, + timestamp: expect.any(String), + stack: expect.any(String), + }); + + consoleSpy.mockRestore(); + }); + }); +}); diff --git a/tests/unit/routes/LayoutTest.svelte b/tests/unit/routes/LayoutTest.svelte index c0ffed2..7d8a4d3 100644 --- a/tests/unit/routes/LayoutTest.svelte +++ b/tests/unit/routes/LayoutTest.svelte @@ -1,7 +1,7 @@ - - - - Hello - + + + + Hello + diff --git a/tests/unit/routes/PageTest.svelte b/tests/unit/routes/PageTest.svelte index 11cbeec..9eb282f 100644 --- a/tests/unit/routes/PageTest.svelte +++ b/tests/unit/routes/PageTest.svelte @@ -1,5 +1,5 @@ - - - + + + diff --git a/tests/unit/routes/layout.settings.test.ts b/tests/unit/routes/layout.settings.test.ts index 786bb55..86c0f11 100644 --- a/tests/unit/routes/layout.settings.test.ts +++ b/tests/unit/routes/layout.settings.test.ts @@ -1,12 +1,12 @@ -import { prerender, ssr } from "$routes/+layout"; -import { describe, expect, it } from "vitest"; - -describe("+layout.ts", () => { - it("should export ssr as false", () => { - expect(ssr).toBe(false); - }); - - it("should export prerender as true", () => { - expect(prerender).toBe(true); - }); -}); +import { prerender, ssr } from "$routes/+layout"; +import { describe, expect, it } from "vitest"; + +describe("+layout.ts", () => { + it("should export ssr as false", () => { + expect(ssr).toBe(false); + }); + + it("should export prerender as true", () => { + expect(prerender).toBe(true); + }); +}); diff --git a/tests/unit/routes/layout.test.ts b/tests/unit/routes/layout.test.ts index 1afff3a..772f9e4 100644 --- a/tests/unit/routes/layout.test.ts +++ b/tests/unit/routes/layout.test.ts @@ -1,39 +1,39 @@ -import { themeState } from "$states/theme.svelte"; -import { render, screen } from "@testing-library/svelte"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import LayoutTest from "./LayoutTest.svelte"; - -describe("+layout.svelte", () => { - beforeEach(() => { - themeState.current = "dark"; - vi.clearAllMocks(); - }); - - it("should apply dark theme", () => { - themeState.current = "dark"; - - expect(themeState.current).toBe("dark"); - }); - - it("should apply light theme", () => { - themeState.current = "light"; - - expect(themeState.current).toBe("light"); - }); - - it("should toggle theme", () => { - themeState.current = "dark"; - themeState.toggle(); - - expect(themeState.current).toBe("light"); - }); -}); - -describe("Layout Component Coverage", () => { - it("should render children snippet and initialize props", () => { - render(LayoutTest); - - expect(screen.getByTestId("test-child")).toBeInTheDocument(); - expect(screen.getByRole("banner")).toBeInTheDocument(); - }); -}); +import { themeState } from "$states/theme.svelte"; +import { render, screen } from "@testing-library/svelte"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import LayoutTest from "./LayoutTest.svelte"; + +describe("+layout.svelte", () => { + beforeEach(() => { + themeState.current = "dark"; + vi.clearAllMocks(); + }); + + it("should apply dark theme", () => { + themeState.current = "dark"; + + expect(themeState.current).toBe("dark"); + }); + + it("should apply light theme", () => { + themeState.current = "light"; + + expect(themeState.current).toBe("light"); + }); + + it("should toggle theme", () => { + themeState.current = "dark"; + themeState.toggle(); + + expect(themeState.current).toBe("light"); + }); +}); + +describe("Layout Component Coverage", () => { + it("should render children snippet and initialize props", () => { + render(LayoutTest); + + expect(screen.getByTestId("test-child")).toBeInTheDocument(); + expect(screen.getByRole("banner")).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/routes/page.test.ts b/tests/unit/routes/page.test.ts index 57eb975..97802a0 100644 --- a/tests/unit/routes/page.test.ts +++ b/tests/unit/routes/page.test.ts @@ -1,10 +1,10 @@ -import { render, screen } from "@testing-library/svelte"; -import { describe, expect, it } from "vitest"; -import PageTest from "./PageTest.svelte"; - -describe("+page.svelte", () => { - it("should render without crashing", () => { - render(PageTest); - expect(screen.getByText("Тексты панелей")).toBeInTheDocument(); - }); -}); +import { render, screen } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; +import PageTest from "./PageTest.svelte"; + +describe("+page.svelte", () => { + it("should render without crashing", () => { + render(PageTest); + expect(screen.getByText("Тексты панелей")).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/services/downloadService.test.ts b/tests/unit/services/downloadService.test.ts index 0de60f1..a457799 100644 --- a/tests/unit/services/downloadService.test.ts +++ b/tests/unit/services/downloadService.test.ts @@ -1,125 +1,131 @@ -import { DownloadService, type DownloadItem } from "$services/downloadService"; -import { saveAs } from "file-saver"; -import JSZip from "jszip"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -vi.mock("file-saver", () => { - return { - saveAs: vi.fn(), - }; -}); - -vi.mock("jszip", () => { - const mockZipInstance = { - file: vi.fn().mockReturnThis(), - generateAsync: vi.fn().mockResolvedValue(new Blob([])), - }; - - return { - default: vi.fn(function () { - return mockZipInstance; - }), - }; -}); - -describe("DownloadService", () => { - let service: DownloadService; - let mockKonvaStage: any; - - beforeEach(() => { - service = new DownloadService(); - vi.clearAllMocks(); - - mockKonvaStage = { - toBlob: vi.fn(({ callback }) => { - callback(new Blob(["test image data"], { type: "image/png" })); - }), - }; - }); - - describe("downloadPanel", () => { - it("should export panel successfully", async () => { - const result = await service.downloadPanel(mockKonvaStage, "test-panel-1"); - - const [blobArg, fileNameArg] = vi.mocked(saveAs).mock.calls[0]; - - expect(result.success).toBe(true); - expect(mockKonvaStage.toBlob).toHaveBeenCalled(); - expect(blobArg instanceof Blob).toBe(true); - expect(fileNameArg).toBe("test-panel-1.png"); - }); - - it("should handle Konva stage without toBlob method", async () => { - const invalidStage = { toBlob: undefined }; - - const result = await service.downloadPanel(invalidStage as any, "test-panel-1.png"); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error).toContain("Konva Stage не найден или не поддерживает toBlob"); - } - }); - - it("should handle blob creation failure", async () => { - mockKonvaStage.toBlob = vi.fn(({ callback }) => { - callback(null); - }); - - const result = await service.downloadPanel(mockKonvaStage, "test-panel-1.png"); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error).toContain("Не удалось создать изображение"); - } - }); - }); - - describe("downloadAllPanels", () => { - it("should handle successful download of multiple panels", async () => { - const panels: Array = [{ filename: "test-panel-1", stage: mockKonvaStage }]; - const result = await service.downloadAll(panels); - const [blobArg, fileNameArg] = vi.mocked(saveAs).mock.calls[0]; - const zipInstance = vi.mocked(JSZip).mock.instances[0]; - - expect(result.success).toBe(true); - expect(mockKonvaStage.toBlob).toHaveBeenCalled(); - expect(blobArg instanceof Blob).toBe(true); - expect(fileNameArg).toBe("panels.zip"); - expect(zipInstance.file).toHaveBeenCalledTimes(1); - expect(zipInstance.file).toHaveBeenCalledWith("test-panel-1.png", expect.anything()); - }); - - it("should add to zip all files", async () => { - const panels: Array = [ - { filename: "test-panel-1", stage: mockKonvaStage }, - { filename: "test-panel-2", stage: mockKonvaStage }, - { filename: "test-panel-3", stage: mockKonvaStage }, - { filename: "test-panel-4", stage: mockKonvaStage }, - { filename: "test-panel-5", stage: mockKonvaStage }, - ]; - const result = await service.downloadAll(panels); - const [blobArg, fileNameArg] = vi.mocked(saveAs).mock.calls[0]; - const zipInstance = vi.mocked(JSZip).mock.instances[0]; - - expect(result.success).toBe(true); - expect(mockKonvaStage.toBlob).toHaveBeenCalledTimes(5); - expect(zipInstance.file).toHaveBeenCalledTimes(5); - expect(zipInstance.file).toHaveBeenCalledWith("test-panel-1.png", expect.anything()); - expect(zipInstance.file).toHaveBeenCalledWith("test-panel-2.png", expect.anything()); - expect(zipInstance.file).toHaveBeenCalledWith("test-panel-3.png", expect.anything()); - expect(zipInstance.file).toHaveBeenCalledWith("test-panel-4.png", expect.anything()); - expect(zipInstance.file).toHaveBeenCalledWith("test-panel-5.png", expect.anything()); - }); - - it("should handle failure", async () => { - const invalidStage = { toBlob: undefined }; - const panels: Array = [{ filename: "test-panel-1", stage: invalidStage as any }]; - const result = await service.downloadAll(panels); - - expect(result.success).toBe(false); - if (!result.success) { - expect(result.error).toContain("Ошибка сохранения архива"); - } - }); - }); -}); +import { DownloadService, type DownloadItem } from "$services/downloadService"; +import { saveAs } from "file-saver"; +import JSZip from "jszip"; +import type { Stage } from "konva/lib/Stage"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("file-saver", () => { + return { + saveAs: vi.fn(), + }; +}); + +vi.mock("jszip", () => { + const mockZipInstance = { + file: vi.fn().mockReturnThis(), + generateAsync: vi.fn().mockResolvedValue(new Blob([])), + }; + + return { + default: vi.fn(function () { + return mockZipInstance; + }), + }; +}); + +describe("DownloadService", () => { + let service: DownloadService; + let mockKonvaStage: Stage; + + beforeEach(() => { + service = new DownloadService(); + vi.clearAllMocks(); + + mockKonvaStage = { + toBlob: vi.fn(async ({ callback }) => { + callback(new Blob(["test image data"], { type: "image/png" })); + }), + } as unknown as Stage; + }); + + describe("downloadPanel", () => { + it("should export panel successfully", async () => { + const result = await service.downloadPanel(mockKonvaStage, "test-panel-1"); + + const [blobArg, fileNameArg] = vi.mocked(saveAs).mock.calls[0]; + + expect(result.success).toBe(true); + expect(mockKonvaStage.toBlob).toHaveBeenCalled(); + expect(blobArg instanceof Blob).toBe(true); + expect(fileNameArg).toBe("test-panel-1.png"); + }); + + it("should handle Konva stage without toBlob method", async () => { + const invalidStage = { toBlob: undefined }; + + const result = await service.downloadPanel( + invalidStage as unknown as Stage, + "test-panel-1.png", + ); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toContain("Konva Stage не найден или не поддерживает toBlob"); + } + }); + + it("should handle blob creation failure", async () => { + mockKonvaStage.toBlob = vi.fn(async ({ callback }) => { + callback(null); + }); + + const result = await service.downloadPanel(mockKonvaStage, "test-panel-1.png"); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toContain("Не удалось создать изображение"); + } + }); + }); + + describe("downloadAllPanels", () => { + it("should handle successful download of multiple panels", async () => { + const panels: Array = [{ filename: "test-panel-1", stage: mockKonvaStage }]; + const result = await service.downloadAll(panels); + const [blobArg, fileNameArg] = vi.mocked(saveAs).mock.calls[0]; + const zipInstance = vi.mocked(JSZip).mock.instances[0]; + + expect(result.success).toBe(true); + expect(mockKonvaStage.toBlob).toHaveBeenCalled(); + expect(blobArg instanceof Blob).toBe(true); + expect(fileNameArg).toBe("panels.zip"); + expect(zipInstance.file).toHaveBeenCalledTimes(1); + expect(zipInstance.file).toHaveBeenCalledWith("test-panel-1.png", expect.anything()); + }); + + it("should add to zip all files", async () => { + const panels: Array = [ + { filename: "test-panel-1", stage: mockKonvaStage }, + { filename: "test-panel-2", stage: mockKonvaStage }, + { filename: "test-panel-3", stage: mockKonvaStage }, + { filename: "test-panel-4", stage: mockKonvaStage }, + { filename: "test-panel-5", stage: mockKonvaStage }, + ]; + const result = await service.downloadAll(panels); + // const [blobArg, fileNameArg] = vi.mocked(saveAs).mock.calls[0]; + const zipInstance = vi.mocked(JSZip).mock.instances[0]; + + expect(result.success).toBe(true); + expect(mockKonvaStage.toBlob).toHaveBeenCalledTimes(5); + expect(zipInstance.file).toHaveBeenCalledTimes(5); + expect(zipInstance.file).toHaveBeenCalledWith("test-panel-1.png", expect.anything()); + expect(zipInstance.file).toHaveBeenCalledWith("test-panel-2.png", expect.anything()); + expect(zipInstance.file).toHaveBeenCalledWith("test-panel-3.png", expect.anything()); + expect(zipInstance.file).toHaveBeenCalledWith("test-panel-4.png", expect.anything()); + expect(zipInstance.file).toHaveBeenCalledWith("test-panel-5.png", expect.anything()); + }); + + it("should handle failure", async () => { + const invalidStage = { toBlob: undefined }; + const panels: Array = [ + { filename: "test-panel-1", stage: invalidStage as unknown as Stage }, + ]; + const result = await service.downloadAll(panels); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toContain("Ошибка сохранения архива"); + } + }); + }); +}); diff --git a/tests/unit/states/imageConfig.test.ts b/tests/unit/states/imageConfig.test.ts index 3fa3e93..4dd0b99 100644 --- a/tests/unit/states/imageConfig.test.ts +++ b/tests/unit/states/imageConfig.test.ts @@ -1,192 +1,216 @@ -import { PANEL_SETTINGS } from "$lib/constants"; -import { imageConfigState, ImageConfigState } from "$states/imageConfig.svelte"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -let lastOnload: (() => void) | null = null; -let lastOnerror: (() => void) | null = null; - -vi.stubGlobal( - "Image", - class { - _onload: (() => void) | null = null; - _onerror: (() => void) | null = null; - _src: string = ""; - crossOrigin: string = ""; - - set src(val: string) { - this._src = val; - if (val.includes("error")) { - setTimeout(() => this._onerror?.(), 1); - } else { - setTimeout(() => this._onload?.(), 1); - } - } - get src() { - return this._src; - } - - set onload(val: any) { - this._onload = val; - if (val) lastOnload = val; - } - get onload() { - return this._onload; - } - - set onerror(val: any) { - this._onerror = val; - if (val) lastOnerror = val; - } - get onerror() { - return this._onerror; - } - }, -); - -describe("ImageConfigState", () => { - beforeEach(() => { - lastOnload = null; - lastOnerror = null; - imageConfigState.reset(); - }); - - it("should create new instance with default values", () => { - const newState = new ImageConfigState(); - expect(newState.image).toBeUndefined(); - expect(newState.imageLink).toBe(""); - expect(newState.imageReady).toBe(false); - expect(newState.cropLeft).toBe(0); - expect(newState.cropTop).toBe(0); - expect(newState.cropRight).toBe(0); - expect(newState.cropBottom).toBe(0); - newState.destroy(); - }); - - it("should initialize with default background image", async () => { - await imageConfigState.uploadImageByLink(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE); - - expect(imageConfigState.imageReady).toBe(true); - expect(imageConfigState.image).toBeDefined(); - expect(imageConfigState.imageLink).toBe(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE); - }); - - it("should handle manual image upload correctly", async () => { - const testLink = "https://example.com/test.png"; - const uploadPromise = imageConfigState.uploadImageByLink(testLink); - - expect(imageConfigState.imageReady).toBe(false); - - await uploadPromise; - expect(imageConfigState.imageReady).toBe(true); - expect(imageConfigState.imageLink).toBe(testLink); - }); - - it("should reset state to defaults", async () => { - await imageConfigState.uploadImageByLink("some-image.png"); - imageConfigState.cropLeft = 100; - - imageConfigState.reset(); - - expect(imageConfigState.imageReady).toBe(false); - expect(imageConfigState.imageLink).toBe(""); - expect(imageConfigState.cropLeft).toBe(0); - expect(imageConfigState.image).toBeUndefined(); - }); - - it("should handle image loading error", async () => { - await expect(imageConfigState.uploadImageByLink("error-link")).rejects.toThrow("Failed to load image"); - - expect(imageConfigState.imageReady).toBe(false); - }); - - it("should abort previous upload when new upload starts", async () => { - const upload1 = imageConfigState.uploadImageByLink("test1.jpg"); - const upload2 = imageConfigState.uploadImageByLink("test2.jpg"); - - await expect(upload1).rejects.toThrow("Aborted"); - await expect(upload2).resolves.toBeUndefined(); - - expect(imageConfigState.imageLink).toBe("test2.jpg"); - }); - - it("should cleanup previous image before loading new one", async () => { - await imageConfigState.uploadImageByLink("test1.jpg"); - const firstImage = imageConfigState.image; - - await imageConfigState.uploadImageByLink("test2.jpg"); - - expect(firstImage?.onload).toBeNull(); - expect(firstImage?.onerror).toBeNull(); - }); - - it("should set crop values", () => { - imageConfigState.cropLeft = 10; - imageConfigState.cropTop = 20; - imageConfigState.cropRight = 30; - imageConfigState.cropBottom = 40; - - expect(imageConfigState.cropLeft).toBe(10); - expect(imageConfigState.cropTop).toBe(20); - expect(imageConfigState.cropRight).toBe(30); - expect(imageConfigState.cropBottom).toBe(40); - }); - - it("should cleanup image event handlers on reset", async () => { - await imageConfigState.uploadImageByLink("test.jpg"); - const img = imageConfigState.image; - - imageConfigState.reset(); - - expect(img?.onload).toBeNull(); - expect(img?.onerror).toBeNull(); - }); - - it("should abort ongoing upload on reset", async () => { - const upload = imageConfigState.uploadImageByLink("test.jpg"); - - imageConfigState.reset(); - - await expect(upload).rejects.toThrow("Aborted"); - }); - - it("should cleanup resources on destroy", async () => { - await imageConfigState.uploadImageByLink("test.jpg"); - const img = imageConfigState.image; - - imageConfigState.destroy(); - - expect(imageConfigState.image).toBeUndefined(); - expect(img?.onload).toBeNull(); - expect(img?.onerror).toBeNull(); - }); - - it("should abort ongoing upload on destroy", async () => { - const upload = imageConfigState.uploadImageByLink("test.jpg"); - - imageConfigState.destroy(); - - await expect(upload).rejects.toThrow("Aborted"); - }); - - it("should cover aborted onload branch", async () => { - const promise = imageConfigState.uploadImageByLink("test.png"); - - imageConfigState.destroy(); - - if (lastOnload) lastOnload(); - - await expect(promise).rejects.toThrow(); - expect(imageConfigState.imageReady).toBe(false); - }); - - it("should cover aborted onerror branch", async () => { - const promise = imageConfigState.uploadImageByLink("test.png"); - - imageConfigState.destroy(); - - if (lastOnerror) lastOnerror(); - - await expect(promise).rejects.toThrow(); - expect(imageConfigState.imageReady).toBe(false); - }); -}); +import { PANEL_SETTINGS } from "$lib/constants"; +import { imageConfigState, ImageConfigState } from "$states/imageConfig.svelte"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +type ImageEventHandler = ((this: HTMLImageElement, ev?: Event) => void) | null; +type ImageErrorEventHandler = + | (( + this: HTMLImageElement, + ev?: string | Event, + source?: string, + lineno?: number, + colno?: number, + error?: Error, + ) => void) + | null; + +let lastOnload: ImageEventHandler = null; +let lastOnerror: ImageErrorEventHandler = null; + +vi.stubGlobal( + "Image", + class { + _onload: ImageEventHandler = null; + _onerror: ImageErrorEventHandler = null; + _src: string = ""; + crossOrigin: string = ""; + + set src(val: string) { + this._src = val; + if (val.includes("error")) { + setTimeout( + () => this._onerror?.call(this as unknown as HTMLImageElement, new Event("error")), + 1, + ); + } else { + setTimeout( + () => this._onload?.call(this as unknown as HTMLImageElement, new Event("load")), + 1, + ); + } + } + get src() { + return this._src; + } + + set onload(val: ImageEventHandler) { + this._onload = val; + if (val) lastOnload = val; + } + get onload() { + return this._onload; + } + + set onerror(val: ImageErrorEventHandler) { + this._onerror = val; + if (val) lastOnerror = val; + } + get onerror() { + return this._onerror; + } + }, +); + +describe("ImageConfigState", () => { + beforeEach(() => { + lastOnload = null; + lastOnerror = null; + imageConfigState.reset(); + }); + + it("should create new instance with default values", () => { + const newState = new ImageConfigState(); + expect(newState.image).toBeUndefined(); + expect(newState.imageLink).toBe(""); + expect(newState.imageReady).toBe(false); + expect(newState.cropLeft).toBe(0); + expect(newState.cropTop).toBe(0); + expect(newState.cropRight).toBe(0); + expect(newState.cropBottom).toBe(0); + newState.destroy(); + }); + + it("should initialize with default background image", async () => { + await imageConfigState.uploadImageByLink(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE); + + expect(imageConfigState.imageReady).toBe(true); + expect(imageConfigState.image).toBeDefined(); + expect(imageConfigState.imageLink).toBe(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE); + }); + + it("should handle manual image upload correctly", async () => { + const testLink = "https://example.com/test.png"; + const uploadPromise = imageConfigState.uploadImageByLink(testLink); + + expect(imageConfigState.imageReady).toBe(false); + + await uploadPromise; + expect(imageConfigState.imageReady).toBe(true); + expect(imageConfigState.imageLink).toBe(testLink); + }); + + it("should reset state to defaults", async () => { + await imageConfigState.uploadImageByLink("some-image.png"); + imageConfigState.cropLeft = 100; + + imageConfigState.reset(); + + expect(imageConfigState.imageReady).toBe(false); + expect(imageConfigState.imageLink).toBe(""); + expect(imageConfigState.cropLeft).toBe(0); + expect(imageConfigState.image).toBeUndefined(); + }); + + it("should handle image loading error", async () => { + await expect(imageConfigState.uploadImageByLink("error-link")).rejects.toThrow( + "Failed to load image", + ); + + expect(imageConfigState.imageReady).toBe(false); + }); + + it("should abort previous upload when new upload starts", async () => { + const upload1 = imageConfigState.uploadImageByLink("test1.jpg"); + const upload2 = imageConfigState.uploadImageByLink("test2.jpg"); + + await expect(upload1).rejects.toThrow("Aborted"); + await expect(upload2).resolves.toBeUndefined(); + + expect(imageConfigState.imageLink).toBe("test2.jpg"); + }); + + it("should cleanup previous image before loading new one", async () => { + await imageConfigState.uploadImageByLink("test1.jpg"); + const firstImage = imageConfigState.image; + + await imageConfigState.uploadImageByLink("test2.jpg"); + + expect(firstImage?.onload).toBeNull(); + expect(firstImage?.onerror).toBeNull(); + }); + + it("should set crop values", () => { + imageConfigState.cropLeft = 10; + imageConfigState.cropTop = 20; + imageConfigState.cropRight = 30; + imageConfigState.cropBottom = 40; + + expect(imageConfigState.cropLeft).toBe(10); + expect(imageConfigState.cropTop).toBe(20); + expect(imageConfigState.cropRight).toBe(30); + expect(imageConfigState.cropBottom).toBe(40); + }); + + it("should cleanup image event handlers on reset", async () => { + await imageConfigState.uploadImageByLink("test.jpg"); + const img = imageConfigState.image; + + imageConfigState.reset(); + + expect(img?.onload).toBeNull(); + expect(img?.onerror).toBeNull(); + }); + + it("should abort ongoing upload on reset", async () => { + const upload = imageConfigState.uploadImageByLink("test.jpg"); + + imageConfigState.reset(); + + await expect(upload).rejects.toThrow("Aborted"); + }); + + it("should cleanup resources on destroy", async () => { + await imageConfigState.uploadImageByLink("test.jpg"); + const img = imageConfigState.image; + + imageConfigState.destroy(); + + expect(imageConfigState.image).toBeUndefined(); + expect(img?.onload).toBeNull(); + expect(img?.onerror).toBeNull(); + }); + + it("should abort ongoing upload on destroy", async () => { + const upload = imageConfigState.uploadImageByLink("test.jpg"); + + imageConfigState.destroy(); + + await expect(upload).rejects.toThrow("Aborted"); + }); + + it("should cover aborted onload branch", async () => { + const promise = imageConfigState.uploadImageByLink("test.png"); + + imageConfigState.destroy(); + + if (lastOnload) { + lastOnload.call(new Image() as HTMLImageElement, new Event("load")); + } + + await expect(promise).rejects.toThrow(); + expect(imageConfigState.imageReady).toBe(false); + }); + + it("should cover aborted onerror branch", async () => { + const promise = imageConfigState.uploadImageByLink("test.png"); + + imageConfigState.destroy(); + + if (lastOnerror) { + lastOnerror.call(new Image() as HTMLImageElement, new Event("load")); + } + + await expect(promise).rejects.toThrow(); + expect(imageConfigState.imageReady).toBe(false); + }); +}); diff --git a/tests/unit/states/konvaAllStages.test.ts b/tests/unit/states/konvaAllStages.test.ts index 06daeb6..7deea96 100644 --- a/tests/unit/states/konvaAllStages.test.ts +++ b/tests/unit/states/konvaAllStages.test.ts @@ -1,8 +1,8 @@ -import { konvaAllStagesState } from "$states/konvaAllStages.svelte"; -import { describe, expect, it } from "vitest"; - -describe("konvaAllStages.svelte", () => { - it("should import successfully", () => { - expect(konvaAllStagesState).toBeDefined(); - }); -}); +import { konvaAllStagesState } from "$states/konvaAllStages.svelte"; +import { describe, expect, it } from "vitest"; + +describe("konvaAllStages.svelte", () => { + it("should import successfully", () => { + expect(konvaAllStagesState).toBeDefined(); + }); +}); diff --git a/tests/unit/states/konvaStage.test.ts b/tests/unit/states/konvaStage.test.ts index e06cb8a..7096c35 100644 --- a/tests/unit/states/konvaStage.test.ts +++ b/tests/unit/states/konvaStage.test.ts @@ -1,54 +1,14 @@ -import { konvaStageState } from "$states/konvaStage.svelte"; -import { describe, expect, it } from "vitest"; - -describe("konvaStage.svelte", () => { - describe("initial state", () => { - it("should have undefined stage initially", () => { - expect(konvaStageState.stage).toBeUndefined(); - }); - - it("should have stage getter", () => { - expect(konvaStageState).toHaveProperty("stage"); - }); - - it("should have stage setter", () => { - expect(() => { - konvaStageState.stage = {} as any; - }).not.toThrow(); - }); - }); - - describe("stage getter", () => { - it("should return current stage", () => { - const mockStage = { id: "test" } as any; - konvaStageState.stage = mockStage; - expect(konvaStageState.stage).toBeDefined(); - expect(konvaStageState.stage).toStrictEqual(mockStage); - }); - - it("should be undefined only at initialization", () => { - expect(konvaStageState.stage).toBeDefined(); - }); - }); - - describe("stage setter", () => { - it("should set stage", () => { - const mockStage = { id: "test" } as any; - konvaStageState.stage = mockStage; - - expect(konvaStageState.stage).toStrictEqual(mockStage); - }); - - it("should allow updating stage", () => { - const firstStage = { id: "first" } as any; - const secondStage = { id: "second" } as any; - - konvaStageState.stage = firstStage; - expect(konvaStageState.stage).toStrictEqual(firstStage); - - konvaStageState.stage = secondStage; - expect(konvaStageState.stage).toStrictEqual(secondStage); - expect(konvaStageState.stage).not.toStrictEqual(firstStage); - }); - }); -}); +import { konvaStageState } from "$states/konvaStage.svelte"; +import type { Stage } from "svelte-konva"; +import { describe, expect, it } from "vitest"; + +describe("konvaStageState integration", () => { + it("should work", () => { + expect(konvaStageState.stage).toBeUndefined(); + + const mock = { name: "stage" } as unknown as Stage; + konvaStageState.stage = mock; + + expect(konvaStageState.stage).toStrictEqual(mock); + }); +}); diff --git a/tests/unit/states/persisted.svelte.test.ts b/tests/unit/states/persisted.svelte.test.ts index 14c50d9..52a0d5f 100644 --- a/tests/unit/states/persisted.svelte.test.ts +++ b/tests/unit/states/persisted.svelte.test.ts @@ -1,158 +1,167 @@ -import { STATE_DATA, withPersistence } from "$states/persisted.svelte"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -const localStorageMock = (() => { - let store: Record = {}; - return { - getItem: vi.fn((key: string) => store[key] || null), - setItem: vi.fn((key: string, value: string) => { - store[key] = value; - }), - removeItem: vi.fn((key: string) => { - delete store[key]; - }), - clear: vi.fn(() => { - store = {}; - }), - }; -})(); - -Object.defineProperty(globalThis, "localStorage", { - value: localStorageMock, -}); - -describe("persisted.svelte", () => { - beforeEach(() => { - localStorageMock.clear(); - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - describe("withPersistence", () => { - it("should return state unchanged when not in browser", () => { - const mockState = { - [STATE_DATA]: { test: "value" }, - }; - const result = withPersistence("test-key", mockState); - expect(result).toBe(mockState); - }); - - it("should restore state from localStorage when valid JSON exists", () => { - const savedData = { value: 42, name: "test" }; - localStorageMock.getItem.mockReturnValue(JSON.stringify(savedData)); - - const state = { - [STATE_DATA]: { value: 0, name: "" }, - }; - - withPersistence("test-key", state); - - expect(state[STATE_DATA]).toEqual(savedData); - expect(localStorageMock.getItem).toHaveBeenCalledWith("test-key"); - }); - - it("should handle corrupted JSON in localStorage gracefully", () => { - localStorageMock.getItem.mockReturnValue("{ invalid json"); - - const state = { - [STATE_DATA]: { value: 0 }, - }; - - expect(() => withPersistence("test-key", state)).not.toThrow(); - expect(state[STATE_DATA]).toEqual({ value: 0 }); - }); - - it("should handle empty localStorage (no saved data)", () => { - localStorageMock.getItem.mockReturnValue(null); - - const state = { - [STATE_DATA]: { value: 0 }, - }; - - withPersistence("test-key", state); - - expect(state[STATE_DATA]).toEqual({ value: 0 }); - }); - - it("should save state to localStorage with debounce", async () => { - vi.useFakeTimers(); - const state = { - [STATE_DATA]: { count: 1 }, - }; - - withPersistence("test-key", state, 500); - - state[STATE_DATA] = { count: 2 }; - await Promise.resolve(); - - vi.advanceTimersByTime(500); - - await Promise.resolve(); - - expect(localStorageMock.setItem).toHaveBeenCalledWith("test-key", JSON.stringify({ count: 2 })); - - vi.useRealTimers(); - }); - - it("should save state immediately when debounce is 0", async () => { - const state = { - [STATE_DATA]: { count: 1 }, - }; - - withPersistence("test-key", state, 0); - - state[STATE_DATA] = { count: 2 }; - - await Promise.resolve(); - - expect(localStorageMock.setItem).toHaveBeenCalledWith("test-key", JSON.stringify({ count: 2 })); - }); - - it("should cleanup timeout on state change during debounce", async () => { - vi.useFakeTimers(); - const data = $state({ count: 1 }); - - const state = { - get [STATE_DATA]() { - return data; - }, - set [STATE_DATA](v) { - data.count = v.count; - }, - }; - - withPersistence("test-key", state, 500); - - state[STATE_DATA] = { count: 2 }; - await Promise.resolve(); - - state[STATE_DATA] = { count: 3 }; - await Promise.resolve(); - - vi.runOnlyPendingTimers(); - await Promise.resolve(); - - expect(localStorageMock.setItem).toHaveBeenCalledTimes(1); - expect(localStorageMock.setItem).toHaveBeenCalledWith("test-key", JSON.stringify({ count: 3 })); - - vi.useRealTimers(); - }); - - it("should use DEBOUNCE_DURATION constant as default", async () => { - const state = { - [STATE_DATA]: { test: true }, - }; - - withPersistence("test-key", state); - - state[STATE_DATA] = { test: false }; - - await Promise.resolve(); - - expect(localStorageMock.setItem).toHaveBeenCalled(); - }); - }); -}); +import { STATE_DATA, withPersistence } from "$states/persisted.svelte"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const localStorageMock = (() => { + let store: Record = {}; + return { + getItem: vi.fn((key: string) => store[key] || null), + setItem: vi.fn((key: string, value: string) => { + store[key] = value; + }), + removeItem: vi.fn((key: string) => { + delete store[key]; + }), + clear: vi.fn(() => { + store = {}; + }), + }; +})(); + +Object.defineProperty(globalThis, "localStorage", { + value: localStorageMock, +}); + +describe("persisted.svelte", () => { + beforeEach(() => { + localStorageMock.clear(); + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("withPersistence", () => { + it("should return state unchanged when not in browser", () => { + const mockState = { + [STATE_DATA]: { test: "value" }, + }; + const result = withPersistence("test-key", mockState); + expect(result).toBe(mockState); + }); + + it("should restore state from localStorage when valid JSON exists", () => { + const savedData = { value: 42, name: "test" }; + localStorageMock.getItem.mockReturnValue(JSON.stringify(savedData)); + + const state = { + [STATE_DATA]: { value: 0, name: "" }, + }; + + withPersistence("test-key", state); + + expect(state[STATE_DATA]).toEqual(savedData); + expect(localStorageMock.getItem).toHaveBeenCalledWith("test-key"); + }); + + it("should handle corrupted JSON in localStorage gracefully", () => { + localStorageMock.getItem.mockReturnValue("{ invalid json"); + + const state = { + [STATE_DATA]: { value: 0 }, + }; + + expect(() => withPersistence("test-key", state)).not.toThrow(); + expect(state[STATE_DATA]).toEqual({ value: 0 }); + }); + + it("should handle empty localStorage (no saved data)", () => { + localStorageMock.getItem.mockReturnValue(null); + + const state = { + [STATE_DATA]: { value: 0 }, + }; + + withPersistence("test-key", state); + + expect(state[STATE_DATA]).toEqual({ value: 0 }); + }); + + it("should save state to localStorage with debounce", async () => { + vi.useFakeTimers(); + const state = { + [STATE_DATA]: { count: 1 }, + }; + + withPersistence("test-key", state, 500); + + state[STATE_DATA] = { count: 2 }; + await Promise.resolve(); + + vi.advanceTimersByTime(500); + + await Promise.resolve(); + + expect(localStorageMock.setItem).toHaveBeenCalledWith( + "test-key", + JSON.stringify({ count: 2 }), + ); + + vi.useRealTimers(); + }); + + it("should save state immediately when debounce is 0", async () => { + const state = { + [STATE_DATA]: { count: 1 }, + }; + + withPersistence("test-key", state, 0); + + state[STATE_DATA] = { count: 2 }; + + await Promise.resolve(); + + expect(localStorageMock.setItem).toHaveBeenCalledWith( + "test-key", + JSON.stringify({ count: 2 }), + ); + }); + + it("should cleanup timeout on state change during debounce", async () => { + vi.useFakeTimers(); + const data = $state({ count: 1 }); + + const state = { + get [STATE_DATA]() { + return data; + }, + set [STATE_DATA](v) { + data.count = v.count; + }, + }; + + withPersistence("test-key", state, 500); + + state[STATE_DATA] = { count: 2 }; + await Promise.resolve(); + + state[STATE_DATA] = { count: 3 }; + await Promise.resolve(); + + vi.runOnlyPendingTimers(); + await Promise.resolve(); + + expect(localStorageMock.setItem).toHaveBeenCalledTimes(1); + expect(localStorageMock.setItem).toHaveBeenCalledWith( + "test-key", + JSON.stringify({ count: 3 }), + ); + + vi.useRealTimers(); + }); + + it("should use DEBOUNCE_DURATION constant as default", async () => { + const state = { + [STATE_DATA]: { test: true }, + }; + + withPersistence("test-key", state); + + state[STATE_DATA] = { test: false }; + + await Promise.resolve(); + + expect(localStorageMock.setItem).toHaveBeenCalled(); + }); + }); +}); diff --git a/tests/unit/states/textConfig.test.ts b/tests/unit/states/textConfig.test.ts index 0f7f3a4..7446272 100644 --- a/tests/unit/states/textConfig.test.ts +++ b/tests/unit/states/textConfig.test.ts @@ -1,79 +1,79 @@ -import { TextAlign } from "$lib/constants"; -import type { HexColor } from "$lib/types"; -import { STATE_DATA } from "$states/persisted.svelte"; -import { textConfigState } from "$states/textConfig.svelte"; -import { describe, expect, it } from "vitest"; - -const TEXT_ALIGN_VALUES = Object.values(TextAlign); - -describe("textConfig.svelte", () => { - describe("initial state", () => { - it("should have valid initial state structure", () => { - expect(typeof textConfigState.fontSize).toBe("number"); - expect(typeof textConfigState.fontFamily).toBe("string"); - expect(typeof textConfigState.color).toBe("string"); - expect(textConfigState.color).toMatch(/^#[0-9a-fA-F]{6}$/); - expect(TEXT_ALIGN_VALUES).toContain(textConfigState.align); - expect(typeof textConfigState.paddingX).toBe("number"); - expect(typeof textConfigState.offsetY).toBe("number"); - }); - }); - - describe("property setters", () => { - it("should update all properties correctly", () => { - textConfigState.fontSize = 32; - textConfigState.fontFamily = "Roboto"; - textConfigState.color = "#ff0000" as HexColor; - textConfigState.align = TextAlign.LEFT; - textConfigState.paddingX = 20; - textConfigState.offsetY = -10; - - expect(textConfigState.fontSize).toBe(32); - expect(textConfigState.fontFamily).toBe("Roboto"); - expect(textConfigState.color).toBe("#ff0000"); - expect(textConfigState.align).toBe(TextAlign.LEFT); - expect(textConfigState.paddingX).toBe(20); - expect(textConfigState.offsetY).toBe(-10); - }); - }); - - describe("STATE_DATA", () => { - it("should serialize and deserialize state", () => { - // Set custom values - textConfigState.fontSize = 18; - textConfigState.fontFamily = "Georgia"; - textConfigState.color = "#00ff00" as HexColor; - textConfigState.align = TextAlign.RIGHT; - textConfigState.paddingX = 5; - textConfigState.offsetY = 15; - - // Get serialized data - const data = textConfigState[STATE_DATA]; - expect(data).toEqual({ - fontSize: 18, - fontFamily: "Georgia", - color: "#00ff00", - align: TextAlign.RIGHT, - paddingX: 5, - offsetY: 15, - }); - - // Restore from serialized data - textConfigState[STATE_DATA] = { - fontSize: 24, - fontFamily: "Arial", - color: "#ffffff" as HexColor, - align: TextAlign.CENTER, - paddingX: 10, - offsetY: 0, - }; - - expect(textConfigState.fontSize).toBe(24); - expect(textConfigState.fontFamily).toBe("Arial"); - expect(textConfigState.color).toBe("#ffffff"); - expect(textConfigState.align).toBe(TextAlign.CENTER); - expect(textConfigState.paddingX).toBe(10); - expect(textConfigState.offsetY).toBe(0); - }); - }); -}); +import { TextAlign } from "$lib/constants"; +import type { HexColor } from "$lib/types"; +import { STATE_DATA } from "$states/persisted.svelte"; +import { textConfigState } from "$states/textConfig.svelte"; +import { describe, expect, it } from "vitest"; + +const TEXT_ALIGN_VALUES = Object.values(TextAlign); + +describe("textConfig.svelte", () => { + describe("initial state", () => { + it("should have valid initial state structure", () => { + expect(typeof textConfigState.fontSize).toBe("number"); + expect(typeof textConfigState.fontFamily).toBe("string"); + expect(typeof textConfigState.color).toBe("string"); + expect(textConfigState.color).toMatch(/^#[0-9a-fA-F]{6}$/); + expect(TEXT_ALIGN_VALUES).toContain(textConfigState.align); + expect(typeof textConfigState.paddingX).toBe("number"); + expect(typeof textConfigState.offsetY).toBe("number"); + }); + }); + + describe("property setters", () => { + it("should update all properties correctly", () => { + textConfigState.fontSize = 32; + textConfigState.fontFamily = "Roboto"; + textConfigState.color = "#ff0000" as HexColor; + textConfigState.align = TextAlign.LEFT; + textConfigState.paddingX = 20; + textConfigState.offsetY = -10; + + expect(textConfigState.fontSize).toBe(32); + expect(textConfigState.fontFamily).toBe("Roboto"); + expect(textConfigState.color).toBe("#ff0000"); + expect(textConfigState.align).toBe(TextAlign.LEFT); + expect(textConfigState.paddingX).toBe(20); + expect(textConfigState.offsetY).toBe(-10); + }); + }); + + describe("STATE_DATA", () => { + it("should serialize and deserialize state", () => { + // Set custom values + textConfigState.fontSize = 18; + textConfigState.fontFamily = "Georgia"; + textConfigState.color = "#00ff00" as HexColor; + textConfigState.align = TextAlign.RIGHT; + textConfigState.paddingX = 5; + textConfigState.offsetY = 15; + + // Get serialized data + const data = textConfigState[STATE_DATA]; + expect(data).toEqual({ + fontSize: 18, + fontFamily: "Georgia", + color: "#00ff00", + align: TextAlign.RIGHT, + paddingX: 5, + offsetY: 15, + }); + + // Restore from serialized data + textConfigState[STATE_DATA] = { + fontSize: 24, + fontFamily: "Arial", + color: "#ffffff" as HexColor, + align: TextAlign.CENTER, + paddingX: 10, + offsetY: 0, + }; + + expect(textConfigState.fontSize).toBe(24); + expect(textConfigState.fontFamily).toBe("Arial"); + expect(textConfigState.color).toBe("#ffffff"); + expect(textConfigState.align).toBe(TextAlign.CENTER); + expect(textConfigState.paddingX).toBe(10); + expect(textConfigState.offsetY).toBe(0); + }); + }); +}); diff --git a/tests/unit/states/texts.test.ts b/tests/unit/states/texts.test.ts index 50f2990..053031c 100644 --- a/tests/unit/states/texts.test.ts +++ b/tests/unit/states/texts.test.ts @@ -1,95 +1,95 @@ -import { STATE_DATA } from "$states/persisted.svelte"; -import { createState } from "$states/texts.svelte"; -import { describe, expect, it } from "vitest"; - -describe("texts.svelte", () => { - describe("addText", () => { - it("should add new text with unique ID", () => { - const state = createState(); - const initialLength = state.texts.length; - - state.addText("Test text"); - - expect(state.texts).toHaveLength(initialLength + 1); - expect(state.texts[initialLength].text).toBe("Test text"); - expect(state.texts[initialLength].id).toBeDefined(); - }); - - it("should not add empty text", () => { - const state = createState(); - const initialLength = state.texts.length; - - state.addText(""); - state.addText(" "); - - expect(state.texts).toHaveLength(initialLength); - }); - }); - - describe("removeText", () => { - it("should remove text by ID", () => { - const state = createState(); - const idToRemove = state.texts[0].id; - const initialLength = state.texts.length; - - state.removeText(idToRemove); - - expect(state.texts).toHaveLength(initialLength - 1); - expect(state.texts.find((item) => item.id === idToRemove)).toBeUndefined(); - }); - - it("should not affect other texts when removing", () => { - const state = createState(); - const remainingTexts = state.texts.filter((item) => item.id !== state.texts[0].id); - const idToRemove = state.texts[0].id; - - state.removeText(idToRemove); - - expect(state.texts).toStrictEqual(remainingTexts); - }); - }); - - describe("clear", () => { - it("should remove all texts", () => { - const state = createState(); - state.addText("Test 1"); - state.addText("Test 2"); - state.addText("Test 3"); - - state.clear(); - - expect(state.texts).toHaveLength(0); - }); - }); - - describe("STATE_DATA", () => { - it("should serialize and deserialize texts", () => { - const state = createState(); - state.clear(); - state.addText("First"); - state.addText("Second"); - state.addText("Third"); - - const data = state[STATE_DATA]; - expect(data).toEqual(["First", "Second", "Third"]); - - // Restore from serialized data - state[STATE_DATA] = ["New 1", "New 2"]; - - expect(state.texts).toHaveLength(2); - expect(state.texts[0].text).toBe("New 1"); - expect(state.texts[1].text).toBe("New 2"); - expect(state.texts[0].id).toBe(0); - expect(state.texts[1].id).toBe(1); - }); - - it("should handle empty array", () => { - const state = createState(); - state.addText("Test"); - - state[STATE_DATA] = []; - - expect(state.texts).toHaveLength(0); - }); - }); -}); +import { STATE_DATA } from "$states/persisted.svelte"; +import { createState } from "$states/texts.svelte"; +import { describe, expect, it } from "vitest"; + +describe("texts.svelte", () => { + describe("addText", () => { + it("should add new text with unique ID", () => { + const state = createState(); + const initialLength = state.texts.length; + + state.addText("Test text"); + + expect(state.texts).toHaveLength(initialLength + 1); + expect(state.texts[initialLength].text).toBe("Test text"); + expect(state.texts[initialLength].id).toBeDefined(); + }); + + it("should not add empty text", () => { + const state = createState(); + const initialLength = state.texts.length; + + state.addText(""); + state.addText(" "); + + expect(state.texts).toHaveLength(initialLength); + }); + }); + + describe("removeText", () => { + it("should remove text by ID", () => { + const state = createState(); + const idToRemove = state.texts[0].id; + const initialLength = state.texts.length; + + state.removeText(idToRemove); + + expect(state.texts).toHaveLength(initialLength - 1); + expect(state.texts.find((item) => item.id === idToRemove)).toBeUndefined(); + }); + + it("should not affect other texts when removing", () => { + const state = createState(); + const remainingTexts = state.texts.filter((item) => item.id !== state.texts[0].id); + const idToRemove = state.texts[0].id; + + state.removeText(idToRemove); + + expect(state.texts).toStrictEqual(remainingTexts); + }); + }); + + describe("clear", () => { + it("should remove all texts", () => { + const state = createState(); + state.addText("Test 1"); + state.addText("Test 2"); + state.addText("Test 3"); + + state.clear(); + + expect(state.texts).toHaveLength(0); + }); + }); + + describe("STATE_DATA", () => { + it("should serialize and deserialize texts", () => { + const state = createState(); + state.clear(); + state.addText("First"); + state.addText("Second"); + state.addText("Third"); + + const data = state[STATE_DATA]; + expect(data).toEqual(["First", "Second", "Third"]); + + // Restore from serialized data + state[STATE_DATA] = ["New 1", "New 2"]; + + expect(state.texts).toHaveLength(2); + expect(state.texts[0].text).toBe("New 1"); + expect(state.texts[1].text).toBe("New 2"); + expect(state.texts[0].id).toBe(0); + expect(state.texts[1].id).toBe(1); + }); + + it("should handle empty array", () => { + const state = createState(); + state.addText("Test"); + + state[STATE_DATA] = []; + + expect(state.texts).toHaveLength(0); + }); + }); +}); diff --git a/tests/unit/states/theme.test.ts b/tests/unit/states/theme.test.ts index 44445c0..21235b5 100644 --- a/tests/unit/states/theme.test.ts +++ b/tests/unit/states/theme.test.ts @@ -1,45 +1,45 @@ -import { Theme } from "$lib/constants"; -import { STATE_DATA } from "$states/persisted.svelte"; -import { themeState } from "$states/theme.svelte"; -import { describe, expect, it } from "vitest"; - -const THEME_VALUES = Object.values(Theme); - -describe("theme.svelte", () => { - describe("initial state", () => { - it("should have valid initial theme value", () => { - expect(THEME_VALUES).toContain(themeState.current); - }); - }); - - describe("toggle", () => { - it("should toggle between themes", () => { - themeState.current = Theme.LIGHT; - themeState.toggle(); - expect(themeState.current).toBe(Theme.DARK); - - themeState.toggle(); - expect(themeState.current).toBe(Theme.LIGHT); - }); - }); - - describe("current setter", () => { - it("should set theme correctly", () => { - themeState.current = Theme.DARK; - expect(themeState.current).toBe(Theme.DARK); - - themeState.current = Theme.LIGHT; - expect(themeState.current).toBe(Theme.LIGHT); - }); - }); - - describe("STATE_DATA", () => { - it("should serialize and deserialize theme", () => { - themeState.current = Theme.DARK; - expect(themeState[STATE_DATA]).toEqual({ current: Theme.DARK }); - - themeState[STATE_DATA] = { current: Theme.LIGHT }; - expect(themeState.current).toBe(Theme.LIGHT); - }); - }); -}); +import { Theme } from "$lib/constants"; +import { STATE_DATA } from "$states/persisted.svelte"; +import { themeState } from "$states/theme.svelte"; +import { describe, expect, it } from "vitest"; + +const THEME_VALUES = Object.values(Theme); + +describe("theme.svelte", () => { + describe("initial state", () => { + it("should have valid initial theme value", () => { + expect(THEME_VALUES).toContain(themeState.current); + }); + }); + + describe("toggle", () => { + it("should toggle between themes", () => { + themeState.current = Theme.LIGHT; + themeState.toggle(); + expect(themeState.current).toBe(Theme.DARK); + + themeState.toggle(); + expect(themeState.current).toBe(Theme.LIGHT); + }); + }); + + describe("current setter", () => { + it("should set theme correctly", () => { + themeState.current = Theme.DARK; + expect(themeState.current).toBe(Theme.DARK); + + themeState.current = Theme.LIGHT; + expect(themeState.current).toBe(Theme.LIGHT); + }); + }); + + describe("STATE_DATA", () => { + it("should serialize and deserialize theme", () => { + themeState.current = Theme.DARK; + expect(themeState[STATE_DATA]).toEqual({ current: Theme.DARK }); + + themeState[STATE_DATA] = { current: Theme.LIGHT }; + expect(themeState.current).toBe(Theme.LIGHT); + }); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index 638d297..25a5539 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,47 +1,47 @@ -import { sveltekit } from "@sveltejs/kit/vite"; -import Icons from "unplugin-icons/vite"; -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - plugins: [ - sveltekit(), - Icons({ - compiler: "svelte", - }), - ], - test: { - environment: "jsdom", - globals: true, - isolate: true, - setupFiles: ["./tests/setup.ts"], - include: ["tests/**/*.{test,spec}.{js,ts}"], - exclude: ["**/node_modules/**", "**/dist/**", "**/.svelte-kit/**", "**/build/**"], - env: { - DEBUG_PRINT_LIMIT: "0", - }, - coverage: { - provider: "istanbul", - enabled: true, - include: ["src/**/*.{js,ts,svelte}"], - exclude: [ - "src/**/*.d.ts", - "src/**/types.ts", - "src/**/index.ts", - "**/*.config.*", - "**/build/**", - "**/node_modules/**", - "**/coverage/**", - ], - }, - server: { - deps: { - inline: [/unplugin-icons/], - }, - }, - }, - resolve: process.env.VITEST - ? { - conditions: ["browser"], - } - : undefined, -}); +import { sveltekit } from "@sveltejs/kit/vite"; +import Icons from "unplugin-icons/vite"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + sveltekit(), + Icons({ + compiler: "svelte", + }), + ], + test: { + environment: "jsdom", + globals: true, + isolate: true, + setupFiles: ["./tests/setup.ts"], + include: ["tests/**/*.{test,spec}.{js,ts}"], + exclude: ["**/node_modules/**", "**/dist/**", "**/.svelte-kit/**", "**/build/**"], + env: { + DEBUG_PRINT_LIMIT: "0", + }, + coverage: { + provider: "istanbul", + enabled: true, + include: ["src/**/*.{js,ts,svelte}"], + exclude: [ + "src/**/*.d.ts", + "src/**/types.ts", + "src/**/index.ts", + "**/*.config.*", + "**/build/**", + "**/node_modules/**", + "**/coverage/**", + ], + }, + server: { + deps: { + inline: [/unplugin-icons/], + }, + }, + }, + resolve: process.env.VITEST + ? { + conditions: ["browser"], + } + : undefined, +});