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/.gitignore b/.gitignore index d9923a7..743ef9c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ node_modules /.svelte-kit /build /coverage +/.VSCodeCounter # OS .DS_Store @@ -22,3 +23,4 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + 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 new file mode 100644 index 0000000..4cdc552 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +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/**/*" + ] +} diff --git a/docs/COMPONENT_ORGANIZATION.md b/docs/COMPONENT_ORGANIZATION.md deleted file mode 100644 index 8d584b6..0000000 --- a/docs/COMPONENT_ORGANIZATION.md +++ /dev/null @@ -1,154 +0,0 @@ -# Component Organization Guide - -This document describes the organized folder structure for the Svelte components in the Twitch Panels Creator project. - -## 📁 Folder Structure - -``` -src/components/ -├── layout/ # Application layout components -├── image/ # Image-related components -├── text/ # Text management components -├── panel/ # Panel display and management -├── feedback/ # User feedback components -└── ui/ # Reusable UI components (from lib) -``` - -## 🏗️ Layout Components - -**Location**: `src/components/layout/` - -Components that define the overall application structure and layout: - -- **AppContainer.svelte** - Main application wrapper and coordinator -- **AppHeader.svelte** - Application header with title and actions -- **AppContent.svelte** - Main content area container -- **MainSection.svelte** - Primary workspace section -- **Sidebar.svelte** - Side panel for previews and controls - -## 🖼️ Image Components - -**Location**: `src/components/image/` - -Components related to image upload, processing, and display: - -- **ImageManager.svelte** - Orchestrates image upload and cropping workflow -- **ImageUpload.svelte** - Handles drag-drop, paste, and URL image upload -- **ImageCropper.svelte** - Provides image cropping interface with cropperjs -- **BackgroundPreview.svelte** - Displays the background image preview - -## 📝 Text Components - -**Location**: `src/components/text/` - -Components for text management and display: - -- **TextManager.svelte** - Main text input and settings management -- **TextSection.svelte** - Text management section wrapper -- **TextPreview.svelte** - Preview of text styling and positioning - -## 🎨 Panel Components - -**Location**: `src/components/panel/` - -Components for panel creation, preview, and management: - -- **PanelPreview.svelte** - Individual panel preview with canvas rendering -- **PanelsList.svelte** - List of all created panels -- **PanelList.svelte** - Alternative panel list implementation - -## 💬 Feedback Components - -**Location**: `src/components/feedback/` - -Components for user feedback and error handling: - -- **ErrorMessage.svelte** - Displays error messages to users - -## 🧩 UI Components - -**Location**: `src/components/ui/` (from lib) - -Reusable UI components: - -- **Button.svelte** - Standard button component -- **IconButton.svelte** - Button with icon support - -## 🔧 Import Conventions - -When importing components, follow these patterns: - -```typescript -// Layout components -import AppContainer from "../components/layout/AppContainer.svelte"; -import AppHeader from "../components/layout/AppHeader.svelte"; - -// Image components -import ImageManager from "../components/image/ImageManager.svelte"; -import ImageUpload from "../components/image/ImageUpload.svelte"; - -// Text components -import TextManager from "../components/text/TextManager.svelte"; -import TextSection from "../components/text/TextSection.svelte"; - -// Panel components -import PanelPreview from "../components/panel/PanelPreview.svelte"; -import PanelsList from "../components/panel/PanelsList.svelte"; - -// Feedback components -import ErrorMessage from "../components/feedback/ErrorMessage.svelte"; - -// UI components -import { Button } from "../lib/components/ui"; -``` - -## 🎯 Benefits of This Organization - -1. **Logical Grouping**: Components are grouped by functionality, making them easy to find -2. **Maintainability**: Related components are co-located, reducing cognitive load -3. **Scalability**: Easy to add new components to appropriate categories -4. **Import Clarity**: Clear import paths that reflect component purpose -5. **Team Collaboration**: Consistent structure that team members can follow - -## 📋 Component Responsibilities - -### Layout Components - -- Handle application-wide structure and navigation -- Manage high-level state and data flow -- Provide containers for functional components - -### Image Components - -- Handle all image-related operations (upload, crop, preview) -- Manage image state and validation -- Provide image processing functionality - -### Text Components - -- Manage text input, editing, and styling -- Handle text validation and state -- Provide text preview functionality - -### Panel Components - -- Handle panel creation and management -- Provide panel preview and export functionality -- Manage panel state and interactions - -### Feedback Components - -- Display user feedback and error messages -- Handle user notifications and alerts -- Provide consistent error handling UI - -## 🔄 Future Considerations - -As the project grows, consider: - -1. **Sub-categorization**: Further divide categories if they become too large -2. **Feature-based organization**: Group by feature rather than type for larger applications -3. **Component documentation**: Add individual component documentation -4. **Storybook integration**: Use Storybook for component development and testing - -This organization provides a solid foundation for the current project while remaining flexible for future expansion. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..9627bf0 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,100 @@ +import { includeIgnoreFile } from "@eslint/compat"; +import js from "@eslint/js"; +import vitest from "@vitest/eslint-plugin"; +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", + "no-unused-vars": "off", + "no-console": ["error", { allow: ["warn", "error"] }], + "@typescript-eslint/no-magic-numbers": [ + "error", + { + enforceConst: true, + ignoreDefaultValues: true, + ignore: [0, 1, 2], + }, + ], + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], + }, + }, + { + files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: [".svelte"], + parser: ts.parser, + svelteConfig, + }, + }, + }, + { + files: ["**/constants.ts", "**/constants/*.ts"], + rules: { + "no-magic-numbers": "off", + "@typescript-eslint/no-magic-numbers": "off", + }, + }, + { + files: ["**/constants.ts", "**/constants/*.ts"], + rules: { + "no-magic-numbers": "off", + "@typescript-eslint/no-magic-numbers": "off", + }, + }, + { + files: ["scripts/**"], + rules: { + "no-magic-numbers": "off", + "@typescript-eslint/no-magic-numbers": "off", + "no-console": "off", + }, + }, + { + files: ["**/*.test.ts", "**/*.spec.ts", "tests/**/*"], + plugins: { vitest }, + rules: { + ...vitest.configs.recommended.rules, + + "vitest/no-focused-tests": "error", + "vitest/no-disabled-tests": "warn", + "vitest/expect-expect": "error", + "vitest/no-conditional-expect": "error", + "vitest/require-hook": "warn", + "vitest/consistent-test-it": ["error", { fn: "it", withinDescribe: "it" }], + "vitest/require-top-level-describe": "error", + "vitest/no-identical-title": "error", + + "no-magic-numbers": "off", + "@typescript-eslint/no-magic-numbers": "off", + }, + }, +); diff --git a/package-lock.json b/package-lock.json index c81246b..0daff94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3791 +1,7217 @@ -{ - "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": { - "@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", - "@types/file-saver": "^2.0.7", - "@types/node": "^25.1.0", - "@vitest/coverage-istanbul": "^4.0.18", - "@vitest/ui": "^4.0.18", - "jsdom": "^28.0.0", - "konva": "^10.2.0", - "svelte": "^5.48.2", - "svelte-check": "^4.3.5", - "typescript": "^5.9.3", - "vite": "^7.3.1", - "vitest": "^4.0.18" - } - }, - "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/@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/@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/@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/@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/@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/@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/@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/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/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/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/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/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/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/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/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-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/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/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/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/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/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-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/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/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/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/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/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, - "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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/eslint-plugin": "^1.6.9", + "@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/eslint-plugin": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.6.9.tgz", + "integrity": "sha512-9WfPx1OwJ19QLCSRLkqVO7//1WcWnK3fE/3fJhKMAmDe8+9G4rB47xCNIIeCq3FdEzkIoLTfDlwDlPBaUTMhow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "^8.55.0", + "@typescript-eslint/utils": "^8.55.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": ">=8.57.0", + "typescript": ">=5.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "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 6e6e401..0903ddd 100644 --- a/package.json +++ b/package.json @@ -1,50 +1,74 @@ -{ - "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", - "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": { - "@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", - "@types/file-saver": "^2.0.7", - "@types/node": "^25.1.0", - "@vitest/coverage-istanbul": "^4.0.18", - "@vitest/ui": "^4.0.18", - "jsdom": "^28.0.0", - "konva": "^10.2.0", - "svelte": "^5.48.2", - "svelte-check": "^4.3.5", - "typescript": "^5.9.3", - "vite": "^7.3.1", - "vitest": "^4.0.18" - }, - "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/eslint-plugin": "^1.6.9", + "@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/CRITICAL_ISSUES_REMEDIATION_PLAN.md b/plans/CRITICAL_ISSUES_REMEDIATION_PLAN.md deleted file mode 100644 index ea790aa..0000000 --- a/plans/CRITICAL_ISSUES_REMEDIATION_PLAN.md +++ /dev/null @@ -1,226 +0,0 @@ -# Critical Issues Remediation Plan - -## Phase 1: Testing Infrastructure (Priority 1) - -**Timeline: Immediate** - -### 1.1 Set Up Testing Framework - -- Install Vitest for unit testing -- Install Playwright for E2E testing -- Install @testing-library/svelte for component testing -- Configure test scripts in package.json - -### 1.2 Write Critical Tests - -- Unit tests for error handling utilities -- Component tests for PanelPreview and core components -- E2E tests for panel creation and export flow -- Storage service tests with mocked localStorage - -### 1.3 Test Coverage Requirements - -- Minimum 80% code coverage -- Critical path coverage: 100% -- Error scenarios: 100% coverage - -## Phase 2: Security Hardening (Priority 1) - -**Timeline: Week 1** - -### 2.1 Input Validation & Sanitization - -```typescript -// Implement in panelStorage.ts -function validatePanelData(data: unknown): Panel { - // Schema validation using Zod or similar - // Sanitize text content to prevent XSS - // Validate image URLs and file types - // Size limits enforcement -} -``` - -### 2.2 Content Security Policy - -- Implement CSP headers in SvelteKit configuration -- Restrict image sources to trusted domains -- Add XSS protection headers - -### 2.3 Secure Storage Implementation - -- Encrypt sensitive data in localStorage -- Implement data integrity checks -- Add versioning for backward compatibility - -## Phase 3: Error Handling & UX (Priority 2) - -**Timeline: Week 2** - -### 3.1 Error Boundary Implementation - -```svelte - - - -{#if error} - error = null} /> -{:else} - -{/if} -``` - -### 3.2 User-Friendly Error Messages - -- Implement consistent error message formatting -- Add error recovery suggestions -- Create error message localization system - -### 3.3 Loading States & Feedback - -- Add skeleton loaders for image loading -- Implement progress indicators for exports -- Add toast notifications for user actions - -## Phase 4: Performance Optimization (Priority 2) - -**Timeline: Week 3** - -### 4.1 Image Optimization - -```typescript -// Implement image optimization service -class ImageOptimizationService { - async optimizeImage(file: File): Promise { - // Compress images to optimal size - // Convert to WebP format when supported - // Implement responsive image sizing - } - - lazyLoadImages(container: HTMLElement) { - // Intersection Observer implementation - } -} -``` - -### 4.2 Memory Management - -- Implement proper cleanup in component lifecycle -- Add image caching with size limits -- Optimize Konva stage rendering - -### 4.3 Bundle Optimization - -- Implement code splitting -- Add tree shaking configuration -- Optimize vendor chunk splitting - -## Phase 5: Accessibility Compliance (Priority 3) - -**Timeline: Week 4** - -### 5.1 WCAG 2.1 Compliance - -- Add ARIA labels to all interactive elements -- Implement keyboard navigation -- Add screen reader announcements -- Ensure color contrast compliance - -### 5.2 Responsive Design - -- Test on multiple device sizes -- Implement touch-friendly interactions -- Add high contrast mode support - -## Phase 6: Code Quality & Documentation (Priority 3) - -**Timeline: Week 5** - -### 6.1 Code Standardization - -- Implement ESLint with Svelte-specific rules -- Add Prettier configuration -- Standardize error message language -- Add comprehensive JSDoc documentation - -### 6.2 Monitoring & Analytics - -- Implement error tracking (Sentry integration) -- Add performance monitoring -- Implement user analytics (privacy-compliant) - -## Implementation Checklist - -### Week 1: Foundation - -- [ ] Set up Vitest testing framework -- [ ] Write core utility tests -- [ ] Implement input validation -- [ ] Add CSP headers - -### Week 2: Reliability - -- [ ] Implement error boundaries -- [ ] Add loading states -- [ ] Write component tests -- [ ] Implement error logging - -### Week 3: Performance - -- [ ] Optimize image handling -- [ ] Implement lazy loading -- [ ] Add memory management -- [ ] Optimize bundle size - -### Week 4: Accessibility - -- [ ] Add ARIA labels -- [ ] Implement keyboard navigation -- [ ] Test screen reader compatibility -- [ ] Add high contrast support - -### Week 5: Polish - -- [ ] Complete test coverage -- [ ] Add monitoring -- [ ] Final documentation -- [ ] Performance audit - -## Success Metrics - -- **Test Coverage**: >80% overall, 100% critical paths -- **Security**: Zero XSS vulnerabilities, CSP compliance -- **Performance**: <2s initial load, <100ms interactions -- **Accessibility**: WCAG 2.1 AA compliance -- **Error Rate**: <0.1% unhandled errors - -## Risk Mitigation - -### Technical Risks - -- **Breaking Changes**: Implement feature flags for gradual rollout -- **Performance Regression**: Establish baseline metrics before changes -- **Browser Compatibility**: Maintain cross-browser testing - -### Timeline Risks - -- **Resource Constraints**: Prioritize Phase 1 and 2 items -- **Dependencies**: Use well-established libraries for security features -- **Testing Complexity**: Start with unit tests, expand to E2E gradually - -## Next Steps - -1. **Immediate**: Set up testing infrastructure -2. **This Week**: Implement security fixes -3. **Next Week**: Address critical error handling -4. **Ongoing**: Monitor and iterate based on user feedback - -This remediation plan prioritizes the most critical issues that could impact production stability and user security. Each phase builds upon the previous one to ensure a robust, maintainable, and user-friendly application. diff --git a/plans/FUNCTIONAL_ROADMAP.md b/plans/FUNCTIONAL_ROADMAP.md new file mode 100644 index 0000000..8b470c2 --- /dev/null +++ b/plans/FUNCTIONAL_ROADMAP.md @@ -0,0 +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. **Сохранить обрезанное изображение**: + - Метод `imageState.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/PROJECT_PLAN.md b/plans/PROJECT_PLAN.md deleted file mode 100644 index ea7a547..0000000 --- a/plans/PROJECT_PLAN.md +++ /dev/null @@ -1,248 +0,0 @@ -# Twitch Panels Creator - Project Plan - -## MVP (Minimum Viable Product) - -### Цель MVP - -Создать инструмент для быстрого создания нескольких Twitch панелей в едином стиле с разными текстами. - -### Функциональность MVP - -#### 1. Загрузка фонового изображения - -- Автоматическая загрузка фонового изображения по умолчанию при старте -- Возможность загрузки собственного фонового изображения -- Обрезка изображения до нужных пропорций - -#### 2. Создание панелей - -- Ввод текстов для создания отдельных панелей -- Каждый текст создает отдельную панель -- Все панели используют одно фоновое изображение -- Настройки текста применяются ко всем панелям - -#### 3. Настройки текста - -- Размер шрифта (10-72px) -- Выбор шрифта из списка (Arial, Verdana, Georgia и др.) -- Цвет текста -- Выравнивание (лево, центр, право) -- Боковые отступы -- Вертикальное смещение от центра - -#### 4. Управление текстами - -- Список всех созданных текстов -- Редактирование текста прямо в списке -- Удаление текста кнопкой "×" -- Автоматическое обновление панелей при изменении текстов -- Автоматическая фильтрация пустых текстов и дубликатов - -#### 5. Просмотр панелей - -- Единое фоновое изображение в боковой панели (один раз вверху) -- Превью всех созданных панелей с текстом на фоне -- Превью только для просмотра -- Скачивание отдельной панели -- Массовое скачивание всех панелей - -#### 6. Экспорт панелей - -- Скачивание панелей в формате PNG -- Размер панелей: 320px ширина, настраиваемая высота (по умолчанию 100px) - -## Workflow работы программы (MVP) - -### Шаг 1: Запуск приложения - -1. Пользователь открывает приложение -2. Автоматически загружается фоновое изображение по умолчанию -3. Пользователь видит экран добавления текстов - -### Шаг 2: Настройка фона (опционально) - -1. Фоновое изображение отображается в боковой панели с кнопкой "Загрузить" -2. Пользователь может загрузить собственное фоновое изображение -3. При необходимости обрезает изображение -4. Фон отображается один раз в боковой панели с правильными размерами (максимальная ширина 320px) -5. Фон применяется ко всем создаваемым панелям -6. Превью панелей показывают финальный результат с текстом на фоне - -### Шаг 3: Создание и редактирование текстов - -1. Пользователь вводит текст для первой панели (например: "links") -2. Нажимает кнопку "Добавить" -3. Текст добавляется в список текстов (пустые тексты и дубликаты автоматически фильтруются) -4. В боковой панели автоматически отображается превью созданной панели -5. Пользователь повторяет процесс для других текстов ("about me", "projects", "memes") -6. Все тексты отображаются в списке ниже поля ввода -7. Каждый текст в списке можно редактировать прямо в поле ввода (дубликаты автоматически фильтруются) -8. Любой текст можно удалить кнопкой "×" -9. При редактировании или удалении текста автоматически обновляется соответствующая панель - -### Шаг 4: Настройка внешнего вида (опционально) - -1. Пользователь настраивает параметры текста: - - Размер шрифта - - Шрифт - - Цвет - - Выравнивание - - Отступы -2. Настройки применяются ко всем создаваемым панелям -3. Превью панелей в боковой панели обновляется в реальном времени - -### Шаг 5: Просмотр панелей - -1. В боковой панели отображается фоновое изображение (один раз вверху) -2. Ниже отображаются превью всех созданных панелей -3. Превью панелей показывают финальный результат с текстом на фоне -4. Превью только для просмотра, редактирование происходит в списке текстов - -### Шаг 6: Экспорт панелей - -1. Пользователь скачивает отдельные панели или все сразу -2. Панели сохраняются в формате PNG -3. Готовые панели загружаются в Twitch - -## Технические требования MVP - -### Frontend - -- ✅ Svelte 5 с runes -- ✅ TypeScript -- ✅ SvelteKit -- ✅ Vite для сборки -- ✅ Адаптер для статического деплоя - -### Основные компоненты - -#### UI компоненты (Реализованы) - -- ✅ AppContainer - главный контейнер приложения -- ✅ AppHeader - заголовок приложения с кнопкой загрузки изображения -- ✅ AppContent - основной контент приложения -- ✅ ImageManager - управление загрузкой и обрезкой изображений -- ✅ TextSection - управление текстами и настройками -- ✅ BackgroundPreview - превью фонового изображения -- ✅ PanelsList - список превью всех созданных панелей -- ✅ Sidebar - боковая панель с превью -- ✅ MainSection - основная секция приложения - -#### Вспомогательные компоненты (Реализованы) - -- ✅ ImageUpload - загрузка изображений (drag-drop, paste, URL) -- ✅ ImageCropper - обрезка изображений с cropperjs -- ✅ TextManager - добавление, редактирование и удаление текстов -- ✅ PanelPreview - превью отдельной панели -- ✅ TextPreview - превью текста -- ✅ ErrorMessage - обработка ошибок - -### Библиотеки и зависимости - -- ✅ cropperjs v2.1.0 - обрезка изображений -- ✅ file-saver v2.0.5 - сохранение файлов -- ✅ jszip v3.10.1 - архивирование (для batch download) -- ✅ uuid - генерация ID -- ✅ @types/\* - TypeScript типы - -### Хранилище данных (Реализовано) - -- ✅ panelStore - хранение состояния панелей -- ✅ uiStore - хранение состояния UI (загрузка, ошибки) -- ✅ Локальное хранилище для сохранения панелей - -### Сервисы (Реализованы) - -- ✅ imageService - загрузка и обработка изображений -- ✅ panelService - управление панелями и текстами -- ✅ exportService - экспорт панелей в PNG -- ✅ Локальные и глобальные сервисы - -### Архитектура - -- ✅ Компонентная архитектура с разделением ответственности -- ✅ Сервисный слой для бизнес-логики -- ✅ Сторы для управления состоянием -- ✅ Типизация TypeScript -- ✅ Обработка ошибок и валидация - -## Будущее развитие (Future Roadmap) - -### Фаза 2: Расширение функциональности - -#### 1. Дополнительные настройки - -- Добавление иконок к панелям -- Настройка тени текста -- Градиентный текст -- Несколько текстовых элементов на одной панели -- Настройка прозрачности фона - -#### 2. Библиотека шаблонов - -- Предустановленные шаблоны панелей -- Возможность сохранения собственных шаблонов -- Категории шаблонов (игры, технологии, искусство и т.д.) - -#### 3. Пакетная обработка - -- Импорт списка текстов из файла -- Автоматическое создание панелей из списка -- Пакетное переименование панелей - -### Фаза 3: Социальные функции - -#### 1. Облачное хранилище - -- Сохранение проектов в облаке -- Синхронизация между устройствами -- История версий - -#### 2. Шаринг - -- Возможность делиться проектами -- Публичная библиотека панелей -- Рейтинг и отзывы - -## Метрики успеха MVP - -- Время создания одной панели: < 30 секунд -- Время создания набора из 5 панелей: < 2 минут -- Успешное скачивание панелей: 100% -- Положительный фидбек от первых пользователей - -## Ограничения MVP - -- Один фоновый рисунок для всех панелей -- Фон отображается один раз в боковой панели (максимальная ширина 320px) -- Превью панелей показывают финальный результат с текстом на фоне -- Один текстовый элемент на панель -- Фиксированная ширина панели (320px) -- Только экспорт в PNG -- Работа в браузере без сохранения на сервере -- Редактирование текстов происходит в списке, превью только для просмотра - -## Следующие шаги - -### Фаза 1: Завершение MVP (Текущая) - -1. ✅ Реализовать базовую структуру приложения -2. ✅ Реализовать загрузку и обрезку изображений -3. ✅ Реализовать создание панелей из текстов -4. ✅ Реализовать настройки текста -5. ✅ Реализовать управление текстами (редактирование, удаление) -6. ✅ Реализовать превью панелей (только для просмотра) -7. ✅ Реализовать экспорт панелей -8. ✅ Разделить редактирование текстов и превью панелей -9. 🔄 Реализовать пакетный экспорт (batch download) с JSZip -10. 🔄 Добавить валидацию и обработку ошибок -11. 🔄 Оптимизировать производительность рендеринга -12. ✅ Тестирование и исправление багов - ВСЕ ТЕСТЫ ПРОЙДЕНЫ (43 теста) -13. ⏳ Деплой на GitHub Pages - -### Фаза 2: Расширенные функции (Будущая) - -1. ⏳ Добавить иконки и продвинутые настройки текста -2. ⏳ Создать библиотеку шаблонов -3. ⏳ Реализовать пакетную обработку -4. ⏳ Добавить социальные функции и облачное хранилище diff --git a/plans/TECHNICAL_DEBT_ANALYSIS.md b/plans/TECHNICAL_DEBT_ANALYSIS.md deleted file mode 100644 index 14dbfff..0000000 --- a/plans/TECHNICAL_DEBT_ANALYSIS.md +++ /dev/null @@ -1,227 +0,0 @@ -# Technical Debt Analysis: Twitch Panels Project - -## Overview - -This document provides a comprehensive analysis of technical debt in the Twitch Panels project, categorizing issues by severity and providing remediation strategies. - -## Technical Debt Categories - -### 🚨 **Critical Debt (Immediate Action Required)** - -#### 1. Testing Infrastructure Debt - -**Current State**: Zero testing infrastructure -**Debt Level**: Critical -**Impact**: High risk of regressions, unreliable deployments -**Files Affected**: Entire codebase -**Remediation Cost**: High (requires comprehensive test suite) - -#### 2. Security Debt - -**Current State**: No input validation, XSS vulnerabilities -**Debt Level**: Critical -**Impact**: Security breaches, data corruption -**Files Affected**: - -- [`src/lib/utils/panelStorage.ts`](src/lib/utils/panelStorage.ts) -- [`src/services/exportService.ts`](src/services/exportService.ts) -- [`src/components/panel/PanelPreview.svelte`](src/components/panel/PanelPreview.svelte) - -#### 3. Error Handling Debt - -**Current State**: Inconsistent error handling, no error boundaries -**Debt Level**: Critical -**Impact**: Poor user experience, application crashes -**Files Affected**: All service files and components - -### ⚠️ **Major Debt (High Priority)** - -#### 4. Performance Debt - -**Current State**: No optimization, memory leaks -**Debt Level**: High -**Impact**: Poor performance, user dissatisfaction -**Specific Issues**: - -- Unoptimized image loading in [`PanelPreview.svelte`](src/components/panel/PanelPreview.svelte:36) -- No lazy loading implementation -- Missing cleanup in effect hooks - -#### 5. Accessibility Debt - -**Current State**: No accessibility features -**Debt Level**: High -**Impact**: Unusable for disabled users, legal compliance issues -**Files Affected**: All UI components - -#### 6. Internationalization Debt - -**Current State**: Mixed Russian/English error messages -**Debt Level**: High -**Impact**: Poor maintainability, inconsistent UX -**Files Affected**: [`src/lib/utils/errorHandler.ts`](src/lib/utils/errorHandler.ts:6-7) - -### 🔧 **Minor Debt (Medium Priority)** - -#### 7. Code Quality Debt - -**Current State**: Inconsistent patterns, missing documentation -**Debt Level**: Medium -**Impact**: Reduced development velocity, onboarding difficulties -**Specific Issues**: - -- Missing JSDoc comments -- Inconsistent file naming -- Mixed coding patterns - -#### 8. Build Configuration Debt - -**Current State**: Basic configuration, missing optimizations -**Debt Level**: Medium -**Impact**: Suboptimal build performance, larger bundle sizes -**Files Affected**: [`vite.config.ts`](vite.config.ts), [`svelte.config.js`](svelte.config.js) - -#### 9. Dependency Management Debt - -**Current State**: Some outdated dependencies -**Debt Level**: Low -**Impact**: Potential security vulnerabilities, missing features -**Files Affected**: [`package.json`](package.json) - -## Debt Quantification - -### Technical Debt Ratio - -``` -Technical Debt Ratio = (Remediation Time / Development Time) × 100 -Estimated Ratio: 45% -``` - -### Debt Distribution - -``` -Critical: 40% (Testing, Security, Error Handling) -Major: 35% (Performance, Accessibility, i18n) -Minor: 25% (Code Quality, Build, Dependencies) -``` - -## Remediation Strategy - -### Phase 1: Critical Debt (Weeks 1-2) - -1. **Testing Infrastructure** - - Set up Vitest + Playwright - - Write core utility tests - - Implement component testing - - Target: 80% coverage - -2. **Security Hardening** - - Implement input validation - - Add CSP headers - - Sanitize user inputs - - Encrypt sensitive data - -3. **Error Handling** - - Add error boundaries - - Implement consistent error messages - - Add user feedback mechanisms - -### Phase 2: Major Debt (Weeks 3-4) - -1. **Performance Optimization** - - Implement image optimization - - Add lazy loading - - Optimize bundle size - - Fix memory leaks - -2. **Accessibility Compliance** - - Add ARIA labels - - Implement keyboard navigation - - Test screen reader compatibility - - Ensure WCAG 2.1 compliance - -3. **Internationalization** - - Standardize error messages - - Implement i18n framework - - Add language switching capability - -### Phase 3: Minor Debt (Week 5) - -1. **Code Quality** - - Add ESLint/Prettier - - Write comprehensive JSDoc - - Standardize code patterns - -2. **Build Optimization** - - Optimize build configuration - - Add bundle analysis - - Implement code splitting - -## Cost-Benefit Analysis - -### Remediation Costs - -- **Testing Infrastructure**: 40 hours -- **Security Hardening**: 24 hours -- **Error Handling**: 16 hours -- **Performance**: 32 hours -- **Accessibility**: 24 hours -- **Code Quality**: 16 hours -- **Total**: 152 hours (≈ 4 weeks) - -### Benefits - -- **Risk Reduction**: 90% reduction in security vulnerabilities -- **Quality Improvement**: 80% reduction in bug reports -- **Performance**: 50% improvement in load times -- **Accessibility**: WCAG 2.1 AA compliance -- **Maintainability**: 60% reduction in onboarding time - -## Risk Assessment - -### High-Risk Areas - -1. **Production Deployment**: Current security vulnerabilities -2. **User Experience**: Accessibility violations -3. **Maintainability**: Zero test coverage - -### Medium-Risk Areas - -1. **Performance**: Unoptimized assets -2. **Scalability**: Memory management issues -3. **Reliability**: Inconsistent error handling - -### Low-Risk Areas - -1. **Build Process**: Basic but functional -2. **Dependencies**: Mostly up-to-date -3. **Code Style**: Inconsistent but readable - -## Recommendations - -### Immediate Actions (This Week) - -1. **Stop Production Deployment**: Security vulnerabilities present -2. **Implement Basic Tests**: Start with critical utilities -3. **Add Input Validation**: Prevent XSS attacks -4. **Set Up Error Monitoring**: Track production errors - -### Short-term Actions (Next 2 Weeks) - -1. **Complete Test Suite**: Achieve 80% coverage -2. **Security Audit**: Comprehensive security review -3. **Performance Audit**: Identify optimization opportunities -4. **Accessibility Audit**: WCAG compliance assessment - -### Long-term Actions (Next Month) - -1. **Code Quality Standards**: Implement linting and formatting -2. **Performance Monitoring**: Continuous performance tracking -3. **Security Monitoring**: Automated vulnerability scanning -4. **Documentation**: Comprehensive API and architecture docs - -## Conclusion - -The project has significant technical debt, particularly in testing, security, and error handling. The estimated 45% technical debt ratio is above the recommended 25% threshold. Immediate action is required on critical debt items before production deployment. - -The remediation effort of approximately 4 weeks will significantly improve code quality, security, and maintainability while reducing long-term maintenance costs. diff --git a/plans/TECHNICAL_ROADMAP.md b/plans/TECHNICAL_ROADMAP.md new file mode 100644 index 0000000..30919a0 --- /dev/null +++ b/plans/TECHNICAL_ROADMAP.md @@ -0,0 +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 imageState { + image = $state(undefined); + imageLink = $state(""); + // ... +} +export const imageState = new imageState(); +``` + +**Проблемы:** + +- `konvaAllStagesState` - глобальный изменяемый массив, нет контроля над операциями +- `imageState` - класс с $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 и imageState +- Настройки яркости/контраста — только UI, нет логики применения +- **Вывод:** Компоненты выглядят готовыми, но функциональность отсутствует + +**Рекомендации (High Priority):** + +- Реализовать imageService.ts и интегрировать в ImageManager +- Добавить логику crop в CropInline (drag, resize, update imageState) +- Применить фильтры яркости/контраста к 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 (глобальные настройки) + imageState (глобальное изображение) + ↓ +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` + `imageState` + +Это **достаточно** для 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 → imageState → 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. Проверить imageState.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/plans/UPDATED_PROJECT_STATUS.md b/plans/UPDATED_PROJECT_STATUS.md deleted file mode 100644 index 925c632..0000000 --- a/plans/UPDATED_PROJECT_STATUS.md +++ /dev/null @@ -1,155 +0,0 @@ -# Статус проекта Twitch Panels - Полный обзор - -## Дата обновления: 04.02.2026 - -## 📋 ОБЩЕЕ СОСТОЯНИЕ ПРИЛОЖЕНИЯ - -Приложение для создания панелей Twitch полностью функционально и готово к использованию. ✅ Все тесты проходят успешно (43 теста). - -## 🏗️ АРХИТЕКТУРА ПРИЛОЖЕНИЯ - -### Основные модули: - -**1. Компоненты пользовательского интерфейса (`src/components/`):** - -- **Layout** - структура приложения (AppContainer, Sidebar, MainSection) -- **Panel** - работа с панелями (PanelPreview, PanelsList) -- **Image** - управление изображениями (ImageUpload, ImageCropper, ImageManager) -- **Text** - работа с текстом (TextManager, TextSection, TextPreview) -- **UI** - базовые элементы интерфейса (Button, IconButton) -- **Feedback** - обработка ошибок (ErrorMessage) - -**2. Сервисы (`src/services/`):** - -- **exportService** - экспорт готовых панелей (использует Konva Stage toBlob) -- **imageService** - загрузка и обработка изображений -- **panelService** - управление панелями и текстом - -**3. Глобальное состояние (`src/stores/`):** - -- **panelStore** - хранение данных о панелях -- **uiStore** - управление UI состоянием (текущий шаг, загрузка, ошибки) - -**4. Типы и утилиты (`src/lib/`):** - -- **types** - TypeScript типы для всего приложения -- **utils** - валидаторы изображений, обработка ошибок, хранение данных - -**5. Маршрутизация (`src/routes/`):** - -- **+page.svelte** - главная страница приложения -- **+layout.ts** - базовая настройка маршрутов - -## ✅ ФУНКЦИОНАЛЬНОСТЬ - -### Основные возможности: - -1. **Создание панелей** - - Добавление текста с настройкой шрифтов, размера, цвета, выравнивания - - Настройка положения текста на панели - - Предпросмотр панелей в реальном времени - -2. **Работа с изображениями** - - Загрузка изображений через файл, URL или буфер обмена - - Обрезка изображений с интерактивным интерфейсом - - Использование изображений в качестве фона панелей - -3. **Экспорт панелей** - - Экспорт в формате PNG с высоким качеством - - Использование встроенного Konva Stage toBlob метода - - Пакетный экспорт нескольких панелей - -### Текущий процесс работы: - -1. Пользователь загружает фоновое изображение или использует стандартное -2. Добавляет текст с нужными настройками -3. Просматривает результат в реальном времени -4. Экспортирует готовые панели - -## 📊 ТЕХНИЧЕСКИЕ ХАРАКТЕРИСТИКИ - -**Технологии:** - -- **Фреймворк:** Svelte 5 с современными runes -- **TypeScript:** Полная типизация всего приложения -- **Canvas:** Konva.js для работы с графикой -- **Обработка изображений:** Cropper.js для обрезки -- **Сборка:** Vite для быстрой разработки - -**Производительность:** - -- ✅ Все TypeScript проверки проходят без ошибок -- ✅ Современная реактивная система Svelte 5 -- ✅ Оптимизированная работа с изображениями -- ✅ Чистая архитектура без дублирования -- ✅ ✅ Полная тестовая покрытие критичных компонентов (43 теста) - -## 🧪 ТЕСТИРОВАНИЕ - -**Реализованные тесты:** - -- ✅ **Обработчик ошибок** - 19 тестов покрывают все сценарии обработки ошибок -- ✅ **Хранилище панелей** - 24 теста покрывают сохранение, загрузку, удаление и валидацию данных -- ✅ **Инфраструктура тестирования** - Vitest, покрытие кода, моки - -**Команды для запуска тестов:** - -- `npm test` - запуск тестов в режиме наблюдения -- `npm run test:run` - одноразовый запуск всех тестов -- `npm run test:coverage` - запуск тестов с отчетом о покрытии - -## 🎯 ДЛЯ ПОЛЬЗОВАТЕЛЯ - -**Что может делать пользователь:** - -- Создавать неограниченное количество панелей -- Настраивать текст (шрифт, размер, цвет, выравнивание) -- Загружать собственные фоновые изображения -- Обрезать изображения до нужного размера -- Предварительно просматривать панели перед экспортом -- Экспортировать панели в высоком качестве для Twitch - -**Процесс использования:** - -1. Открыть приложение -2. Загрузить фоновое изображение (по желанию) -3. Добавить текст с нужными настройками -4. Просмотреть результат -5. Экспортировать панель -6. Использовать экспортированную панель в Twitch - -## 📁 ФАЙЛОВАЯ СТРУКТУРА - -``` -src/ -├── components/ # UI компоненты -│ ├── layout/ # Структура приложения -│ ├── panel/ # Компоненты панелей -│ ├── image/ # Компоненты работы с изображениями -│ ├── text/ # Компоненты работы с текстом -│ ├── ui/ # Базовые UI элементы -│ └── feedback/ # Обработка ошибок -├── services/ # Бизнес-логика -│ ├── exportService.ts # Экспорт (Konva Stage toBlob) -│ ├── imageService.ts # Работа с изображениями -│ └── panelService.ts # Управление панелями -├── stores/ # Глобальное состояние -│ ├── panelStore.ts # Данные о панелях -│ └── uiStore.ts # UI состояние -├── lib/ # Библиотечный код -│ ├── types/ # TypeScript типы -│ └── utils/ # Утилиты и валидаторы -├── routes/ # Страницы приложения -└── app.html # HTML шаблон -``` - -## 🚀 СТАТУС РАЗРАБОТКИ - -**✅ Готово к использованию:** - -- Полностью функциональное приложение -- Все основные фичи реализованы -- Прошло все TypeScript проверки -- Оптимизирована архитектура - -**Текущая версия:** Готова к деплою и использованию пользователями Twitch. diff --git a/plans/core-features-plan.md b/plans/core-features-plan.md deleted file mode 100644 index 3f1ea4d..0000000 --- a/plans/core-features-plan.md +++ /dev/null @@ -1,410 +0,0 @@ -# Core Features Implementation Plan - -## 🎯 **Focus: Core Features First** - -This plan prioritizes the essential features needed for a functional Twitch panel creator, deferring advanced features like batch download for later implementation. - -### **Phase 1: Minimum Viable Product (MVP)** - -#### **1. Image Upload System** ✅ **COMPLETED** - -**Priority: HIGH** - -- **Drag & Drop Zone**: Visual feedback, file validation ✅ -- **Ctrl+V Paste**: Clipboard API integration ✅ -- **URL Input**: External image loading ✅ -- **Image Preview**: Before cropping confirmation ✅ - -**Key Components:** - -```typescript -interface ImageUploadProps { - onImageSelect: (image: string) => void; - onError: (error: string) => void; -} - -// Features: -- File type validation (jpg, png, webp) -- Size limit (10MB max) -- Drag over visual states -- Paste detection -- URL fetch with CORS handling -``` - -#### **2. Image Cropping Interface** ✅ **COMPLETED** - -**Priority: HIGH** - -- **Cropper Integration**: Fixed 320px width constraint ✅ -- **Crop Confirmation**: Accept/Cancel options ✅ -- **Error Handling**: Invalid crop areas ✅ - -**Implementation Details:** - -- Integrated cropperjs v2.1.0 with Web Components -- Used `$toCanvas()` method to get HTMLCanvasElement -- Implemented proper error handling with user-friendly messages - -**Key Components:** - -```typescript -interface ImageCropperProps { - image: string; - onCropComplete: (croppedImage: string) => void; - onCancel: () => void; -} - -// Features: -- Fixed width (320px), variable height -- Aspect ratio locking -- Crop area validation -- Base64 output -- Mobile responsive -``` - -#### **3. Text Management System** ✅ **COMPLETED** - -**Priority: HIGH** - -- **Dynamic Text List**: Add, edit, delete ✅ -- **Text Styling**: Font, size, color, positioning ✅ -- **Real-time Updates**: Live preview sync ✅ -- **Common Settings**: Apply settings to all texts ✅ - -**Key Components:** - -```typescript -interface TextItem { - id: string; - text: string; - fontSize: number; - fontFamily: string; - color: string; - x: number; - y: number; -} - -interface TextManagerProps { - texts: TextItem[]; - onTextChange: (texts: TextItem[]) => void; -} - -// Features: -- Add new text item -- Edit existing text -- Delete text item -- Font selection from available fonts -- Font size adjustment -- Color picker -- Position controls (x, y) -- Text validation (length limits) -``` - -#### **4. Canvas Rendering Engine** ✅ **COMPLETED** - -**Priority: HIGH** - -- **SvelteKonva Integration**: Enhanced implementation ✅ -- **Dynamic Height**: Configurable panel height ✅ -- **Real-time Preview**: Live updates ✅ -- **Layer Management**: Background + text layers ✅ - -**Key Components:** - -```typescript -interface PanelCanvasProps { - backgroundImage: string; - texts: TextItem[]; - width: number; // 320px fixed - height: number; // configurable -} - -// Features: -- Background image layer -- Text layers with proper positioning -- Dynamic height support -- Real-time rendering -- Performance optimization -``` - -#### **5. Basic Panel Management** ✅ **COMPLETED** - -**Priority: MEDIUM** - -- **Panel Storage**: Local storage for current panel ✅ -- **Panel Navigation**: Basic next/prev (single panel for MVP) ✅ -- **Panel Validation**: Basic validation ✅ -- **Panel List**: View and manage saved panels ✅ - -**Key Components:** - -```typescript -interface Panel { - id: string; - backgroundImage: string; - texts: TextItem[]; - height: number; - createdAt: Date; -} - -interface PanelManagerProps { - currentPanel: Panel | undefined; - onPanelUpdate: (panel: Panel) => void; -} - -// Features: -- Save current panel state -- Load panel from storage -- Basic validation -- Single panel focus for MVP -``` - -### **Phase 2: Enhanced Core Features** - -#### **6. User Interface Enhancements** ✅ **COMPLETED** - -**Priority: MEDIUM** - -- ✅ **Responsive Layout**: Mobile-friendly design -- ✅ **Loading States**: Visual feedback через uiStore -- ✅ **Error Messages**: User-friendly error handling -- ✅ **Keyboard Shortcuts**: Ctrl+V для вставки изображений - -#### **7. Error Handling & Validation** ✅ **COMPLETED** - -**Priority: MEDIUM** - -- ✅ **Input Validation**: Валидация текста и изображений -- ✅ **Error Boundaries**: Компонент ErrorMessage для обработки ошибок -- ✅ **User Guidance**: Понятные сообщения об ошибках -- ✅ **Retry Mechanisms**: Возможность повторной загрузки - -#### **8. Batch Download System** 🔄 **IN PROGRESS** - -**Priority: HIGH** - -- 🔄 **JSZip Integration**: Подключена библиотека JSZip -- ⏳ **Batch Rendering**: Параллельная генерация изображений -- ⏳ **Progress Tracking**: Индикатор прогресса экспорта -- ⏳ **ZIP Archive Creation**: Создание архива с панелями - -### **Implementation Order** ✅ **UPDATED STATUS** - -``` -Week 1-2: Foundation ✅ COMPLETED -├── Setup dependencies (cropperjs, file-saver, jszip) ✅ -├── TypeScript types and interfaces ✅ -├── Error handling structure ✅ -├── Basic project structure ✅ -└── SvelteKit configuration ✅ - -Week 2-3: Image System ✅ COMPLETED -├── Image upload component ✅ -├── Image cropping component ✅ -├── Image validation utilities ✅ -├── Image service layer ✅ -└── Default backgrounds loading ✅ - -Week 3-4: Text System ✅ COMPLETED -├── Text management component ✅ -├── Text styling controls ✅ -├── Common text settings ✅ -├── Text validation ✅ -└── Text service layer ✅ - -Week 4-5: Canvas & Integration ✅ COMPLETED -├── Enhanced canvas implementation ✅ -├── Dynamic height support ✅ -├── Real-time preview ✅ -├── Panel storage system ✅ -└── Panel management ✅ - -Week 5-6: UI & Polish ✅ COMPLETED -├── Responsive layout ✅ -├── Loading states ✅ -├── Error handling improvements ✅ -├── Keyboard shortcuts ✅ -└── User experience polish ✅ - -Week 6-7: Batch Download 🔄 IN PROGRESS -├── JSZip integration ✅ -├── Batch rendering engine ⏳ -├── Progress tracking ⏳ -└── ZIP archive creation ⏳ - -Week 7-8: Testing & Deployment ⏳ PLANNED -├── Unit testing ⏳ -├── Integration testing ⏳ -├── Bug fixes ⏳ -└── GitHub Pages deployment ⏳ -``` - -## 🔧 **Technical Specifications** - -### **Dependencies to Install** - -```bash -npm install cropperjs@^2.1.0 file-saver @types/cropperjs@^1.1.5 -``` - -**Note:** Using cropperjs v2.1.0 with Web Components API. The `$toCanvas()` method is used to obtain HTMLCanvasElement from `` component. - -### **TypeScript Interfaces** - -```typescript -// Core types -interface TextItem { - id: string; - text: string; - fontSize: number; - fontFamily: string; - color: string; - x: number; - y: number; -} - -interface Panel { - id: string; - backgroundImage: string; - texts: TextItem[]; - height: number; - createdAt: Date; -} - -interface ImageUploadResult { - success: boolean; - image?: string; - error?: string; -} -``` - -### **State Management** - -```typescript -// Core stores -export const panelStore = writable(undefined); -export const uiStore = writable({ - isLoading: false, - error: string | undefined, - currentStep: "upload" | "crop" | "text" | "preview", -}); -``` - -### **Error Handling Strategy** - -```typescript -// Error types -export class AppError extends Error { - constructor( - message: string, - public code: string, - public recoverable: boolean = true, - ) { - super(message); - } -} - -// Error handling utilities -export const handleImageError = (error: unknown): string => { - if (error instanceof AppError) { - return error.recoverable ? `Ошибка: ${error.message}. Попробуйте снова.` : `Критическая ошибка: ${error.message}`; - } - return "Произошла неизвестная ошибка"; -}; -``` - -## 🚨 **Common Issues & Solutions** - -### **Image Upload Issues** - -1. **CORS Errors** - - Solution: Use proxy for external images or allow CORS in development - - Fallback: Show error message with alternative upload methods - -2. **Large Files** - - Solution: Implement client-side compression - - Limit: 10MB per file with clear user feedback - -3. **Invalid Formats** - - Solution: Validate before upload, show supported formats - - Fallback: Convert to webp if possible - -### **Canvas Rendering Issues** - -1. **Memory Limits** - - Solution: Implement lazy rendering and cleanup - - Monitor: Canvas size and memory usage - -2. **Font Loading** - - Solution: Fallback to web-safe fonts - - Preload: Load fonts during initialization - -3. **Performance** - - Solution: Debounce rapid updates - - Optimize: Use requestAnimationFrame for smooth rendering - -### **User Experience Issues** - -1. **Slow Operations** - - Solution: Loading states and progress indicators - - Optimize: Async operations with proper error handling - -2. **Complex Interface** - - Solution: Step-by-step wizard approach - - Guide: Tooltips and help text - -## 📱 **UI/UX Considerations** - -### **Mobile First Design** - -- Touch-friendly controls -- Responsive layout for all screen sizes -- Swipe gestures for navigation - -### **Accessibility** - -- Screen reader compatibility -- Keyboard navigation -- High contrast mode support - -### **Performance** - -- Lazy loading of components -- Optimized bundle size -- Efficient state management - ---- - -_Created: 2026-02-03_ -_Focus: Core Features Implementation_ -_Estimated Duration: 5 weeks_ - -## 📝 **Implementation Notes** - -### **Image Cropping Implementation (Completed)** - -**Challenge:** cropperjs v2.x uses Web Components API which differs significantly from v1.x - -**Solution:** - -1. Used `getCropperCanvas()` to get `` Web Component -2. Called `$toCanvas()` method to convert to HTMLCanvasElement -3. Applied `toDataURL()` on the resulting canvas to get base64 image - -**Code Example:** - -```typescript -const cropperCanvasElement = cropper.getCropperCanvas(); -const canvas = await cropperCanvasElement.$toCanvas({ - width: 320, - imageSmoothingEnabled: true, - imageSmoothingQuality: "high", -}); -const croppedImage = canvas.toDataURL("image/png"); -``` - -**Key Points:** - -- cropperjs v2.x methods return Web Components, not standard DOM elements -- `$toCanvas()` is an async method that returns HTMLCanvasElement -- Proper error handling is essential for user experience diff --git a/plans/implementation-plan.md b/plans/implementation-plan.md deleted file mode 100644 index 614adea..0000000 --- a/plans/implementation-plan.md +++ /dev/null @@ -1,312 +0,0 @@ -# Twitch Panels Implementation Plan - -## 🎯 **Project Overview** - -A SvelteKit-based Twitch panel creator with the following key features: - -- **320px width** panels with **configurable height** (default 100px) -- **Multiple image upload methods**: URL, drag-and-drop, Ctrl+V paste -- **Image cropping interface** with proper constraints -- **Dynamic text management** (add, edit, delete) -- **Real-time preview** with navigation -- **Batch download** using JSZip - -## 📁 **Project Structure** - -``` -src/ -├── components/ -│ ├── ImageUpload.svelte # Drag/drop, paste, URL input -│ ├── ImageCropper.svelte # Cropperjs integration -│ ├── TextManager.svelte # Text CRUD operations -│ ├── PanelPreview.svelte # Canvas preview -│ ├── PanelList.svelte # Panel navigation -│ └── DownloadModal.svelte # Batch download -├── lib/ -│ ├── utils/ -│ │ ├── imageProcessor.ts # Image handling -│ │ ├── textManager.ts # Text operations -│ │ ├── panelStorage.ts # Local storage -│ │ └── batchProcessor.ts # JSZip integration -│ ├── types/ -│ │ ├── panel.ts # TypeScript interfaces -│ │ └── errors.ts # Error types -│ └── services/ -│ ├── imageService.ts # Image operations -│ ├── textService.ts # Text operations -│ └── exportService.ts # Export functionality -└── stores/ - ├── panelStore.ts # Reactive state - └── uiStore.ts # UI state -``` - -## 📋 **Implementation Tasks** - -### **Setup and Configuration** - -- [x] Install required dependencies (JSZip, cropperjs, file-saver) -- [x] Configure TypeScript types for new features -- [x] Set up proper error handling structure -- [x] Configure SvelteKit with static adapter -- [x] Set up GitHub Pages deployment configuration - -### **Core Image Upload System** - -- [x] Create drag-and-drop zone component -- [x] Implement Ctrl+V paste functionality -- [x] Add URL-based image loading -- [x] Create image validation and error handling -- [x] Implement image preview before cropping -- [x] Add default background images loading - -### **Image Cropping Interface** - -- [x] Integrate cropperjs library -- [x] Create responsive crop interface -- [x] Add crop ratio constraints (320px width) -- [x] Implement crop confirmation and cancellation -- [x] Handle crop errors and edge cases - -### **Text Management System** - -- [x] Create dynamic text list component -- [x] Implement add/edit/delete text functionality -- [x] Add text positioning controls -- [x] Implement text styling options (font, size, color) -- [x] Add text validation and error handling -- [x] Add common text settings for all text items -- [x] Implement text alignment and padding controls - -### **Canvas Rendering Engine** - -- [x] Upgrade canvas implementation with native Canvas API -- [x] Create dynamic height support -- [x] Implement real-time preview updates -- [x] Add layer management system -- [x] Optimize rendering performance -- [x] Add text positioning and styling - -### **Panel Management** - -- [x] Create panel storage system -- [x] Implement panel navigation (previous/next) -- [x] Add panel deletion functionality -- [x] Create panel export queue -- [x] Implement panel validation -- [x] Add panel list component with preview -- [x] Implement panel creation from texts - -### **Batch Download System** - -- [x] Integrate JSZip library -- [ ] Create batch rendering engine -- [ ] Implement parallel image generation -- [ ] Add progress tracking -- [ ] Implement ZIP archive creation -- [ ] Add batch download UI controls -- [x] Handle large batch downloads (basic implementation) -- [ ] Create download error recovery - -### **User Interface** ✅ **COMPLETED** - -- [x] Design responsive layout -- [x] Create modal dialogs for crop/confirm -- [x] Add loading states and spinners -- [x] Implement keyboard shortcuts -- [x] Add tooltips and help text (through error messages) -- [x] Create error message system - -### **Error Handling and Validation** ✅ **COMPLETED** - -- [x] Implement comprehensive error boundaries -- [x] Add input validation for all forms -- [x] Create user-friendly error messages -- [x] Add retry mechanisms for failed operations -- [x] Implement logging for debugging - -### **Performance Optimization** 🔄 **IN PROGRESS** - -- [x] Add image compression for uploads (basic) -- [x] Implement lazy loading for panels -- [x] Optimize canvas rendering -- [ ] Add memory management for large batches -- [x] Create debouncing for rapid inputs - -### **Testing and Quality Assurance** ⏳ **PLANNED** - -- [ ] Create unit tests for core functions -- [ ] Add integration tests for user flows -- [ ] Test with various image formats and sizes -- [ ] Validate error scenarios -- [ ] Test performance with large datasets - -### **Documentation and Deployment** ⏳ **PLANNED** - -- [ ] Create user documentation -- [ ] Add GitHub Pages deployment script -- [ ] Configure proper build optimization -- [ ] Set up CI/CD pipeline -- [ ] Set up CI/CD pipeline -- [ ] Create README with setup instructions - -## 🔧 **Technical Implementation Details** - -### **Core Components Architecture** - -#### **Image Upload System** - -- **Drag & Drop**: HTML5 drag API with visual feedback -- **Paste Handler**: Clipboard API integration -- **URL Input**: Fetch API with CORS handling -- **Validation**: File size limits (max 10MB), format checking - -#### **Image Cropping** - -- **Library**: Cropper.js with Svelte wrapper -- **Constraints**: Fixed 320px width, variable height -- **Output**: Base64 cropped images -- **Error Handling**: Invalid crop areas, format conversion - -#### **Text Management** - -- **CRUD Operations**: Add, edit, delete text elements -- **Styling**: Font selection, size, color, positioning -- **Validation**: Text length limits, character encoding -- **Real-time Updates**: Live preview synchronization - -#### **Canvas Rendering** - -- **SvelteKonva**: Enhanced implementation -- **Dynamic Height**: Flexible panel dimensions -- **Layer Management**: Background + text layers -- **Performance**: Debounced updates, lazy rendering - -#### **Batch Processing** - -- **JSZip**: Parallel image generation -- **Progress Tracking**: Real-time progress updates -- **Memory Management**: Stream processing for large batches -- **Error Recovery**: Failed image retry mechanism - -## 🚨 **Error Handling Strategy** - -### **Common Issues to Address** - -1. **Image Loading Failures** - - CORS errors for external URLs - - Corrupted image files - - Network timeouts - -2. **Canvas Rendering Issues** - - Memory limits for large canvases - - Font loading failures - - Invalid text encoding - -3. **User Input Errors** - - Empty text fields - - Invalid image URLs - - Crop area too small - -4. **Export Problems** - - Large batch timeouts - - Storage quota exceeded - - Browser download restrictions - -### **Error Recovery Patterns** - -- **Retry Mechanisms**: Exponential backoff for failed operations -- **Fallback Options**: Default fonts, error images -- **User Guidance**: Clear error messages with suggested fixes -- **Graceful Degradation**: Basic functionality without advanced features - -## 📱 **User Interface Considerations** - -### **Responsive Design** - -- **Mobile Support**: Touch-friendly controls -- **Desktop Optimization**: Keyboard shortcuts -- **Accessibility**: Screen reader compatibility - -### **User Experience** - -- **Loading States**: Visual feedback during operations -- **Progress Indicators**: For batch processes -- **Undo/Redo**: For text and panel operations -- **Keyboard Shortcuts**: Ctrl+V for paste, arrow keys for navigation - -## 🚀 **Deployment Configuration** - -### **GitHub Pages Setup** - -- **Base Path**: Automatic path handling for subdirectories -- **Build Optimization**: Code splitting, lazy loading -- **Cache Strategy**: Proper cache headers for static assets - -### **Performance Optimizations** - -- **Image Compression**: WebP format conversion -- **Lazy Loading**: On-demand panel rendering -- **Bundle Splitting**: Separate chunks for large libraries -- **Service Worker**: Offline functionality consideration - -## 📊 **Testing Strategy** - -### **Test Coverage** - -- **Unit Tests**: Individual component testing -- **Integration Tests**: User workflow validation -- **Performance Tests**: Large batch processing -- **Error Scenarios**: Edge case handling - -### **Test Data** - -- **Sample Images**: Various formats and sizes -- **Text Content**: Unicode characters, special symbols -- **Network Conditions**: Slow connections, timeouts - -## 🔄 **Data Flow Architecture** - -```mermaid -graph TD - A[Image Upload] --> B[Image Validation] - B --> C[Image Cropping] - C --> D[Text Management] - D --> E[Panel Assembly] - E --> F[Preview] - F --> G[Export/Download] - - H[Local Storage] --> D - I[Error Handling] --> B - I --> C - I --> D - I --> E -``` - -## 🎯 **Priority Implementation Order** - -### **Phase 1: Core Features** - -1. Image Upload System (drag/drop, paste, URL) -2. Image Cropping Interface -3. Text Management System -4. Canvas Rendering Engine -5. Basic Preview - -### **Phase 2: Advanced Features** - -1. Panel Management System -2. Batch Download System -3. User Interface Enhancements -4. Error Handling Improvements - -### **Phase 3: Polish & Deployment** - -1. Performance Optimization -2. Testing & Quality Assurance -3. Documentation & Deployment -4. CI/CD Pipeline - ---- - -_Created: 2026-02-03_ -_Last Updated: 2026-02-03_ diff --git a/reference/index.html b/reference/index.html new file mode 100644 index 0000000..13d1022 --- /dev/null +++ b/reference/index.html @@ -0,0 +1,288 @@ + + + + + + Twitch Panels Creator + + + +
+
+
+

Twitch Panels

+ +
+
+ +
+ +
+
+

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

+ +
+ + +
+ +
+
+ +
+

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

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

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

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

Панели 0

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

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

+
+
+ +
+ + +
+
+
+
+
+
+ + + + diff --git a/reference/script.js b/reference/script.js new file mode 100644 index 0000000..ba342db --- /dev/null +++ b/reference/script.js @@ -0,0 +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); + }); +} diff --git a/reference/styles.css b/reference/styles.css new file mode 100644 index 0000000..415a5b5 --- /dev/null +++ b/reference/styles.css @@ -0,0 +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); +} diff --git a/scripts/css-vars.js b/scripts/css-vars.js new file mode 100644 index 0000000..d512288 --- /dev/null +++ b/scripts/css-vars.js @@ -0,0 +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); +} diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..8a2f662 --- /dev/null +++ b/src/app.css @@ -0,0 +1,78 @@ +: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); + + --border-main: oklch(from var(--brand-main) 0.26 0.04 h); + --border-strong: oklch(from var(--brand-main) 0.4 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/app.html b/src/app.html index f273cc5..258219d 100644 --- a/src/app.html +++ b/src/app.html @@ -1,11 +1,24 @@ - - - - %sveltekit.head% - - -
%sveltekit.body%
- + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ diff --git a/src/components/feedback/ErrorMessage.svelte b/src/components/feedback/ErrorMessage.svelte deleted file mode 100644 index 035ee12..0000000 --- a/src/components/feedback/ErrorMessage.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - -{#if errorMessage} -
- {errorMessage} -
-{/if} - - diff --git a/src/components/image/BackgroundPreview.svelte b/src/components/image/BackgroundPreview.svelte deleted file mode 100644 index e368259..0000000 --- a/src/components/image/BackgroundPreview.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - -{#if backgroundImage} -
-
-

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

- -
- Фон -
-{/if} - - diff --git a/src/components/image/CropInline.svelte b/src/components/image/CropInline.svelte new file mode 100644 index 0000000..60f8929 --- /dev/null +++ b/src/components/image/CropInline.svelte @@ -0,0 +1,149 @@ + + +
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/src/components/image/ImageCropper.svelte b/src/components/image/ImageCropper.svelte deleted file mode 100644 index 35bffe7..0000000 --- a/src/components/image/ImageCropper.svelte +++ /dev/null @@ -1,214 +0,0 @@ - - -
-
- -
- - {#if errorMessage} -
- Ошибка: - {errorMessage} -
- {/if} - -
- - -
- -
-

💡 Изображение будет обрезано до ширины 320px

-
-
- - diff --git a/src/components/image/ImageManager.svelte b/src/components/image/ImageManager.svelte index b6cec04..627c846 100644 --- a/src/components/image/ImageManager.svelte +++ b/src/components/image/ImageManager.svelte @@ -1,20 +1,97 @@ - - -{#if currentStep === "upload"} - -{:else if currentStep === "crop" && uploadedImage} - -{/if} + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + diff --git a/src/components/image/ImageUpload.svelte b/src/components/image/ImageUpload.svelte deleted file mode 100644 index c54a818..0000000 --- a/src/components/image/ImageUpload.svelte +++ /dev/null @@ -1,401 +0,0 @@ - - -
- {#if uploadedImage} -
- Uploaded preview -
- -
-
- {:else} -
{ - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - triggerFileInput(); - } - }} - > -
- - - - - - -

Загрузите изображение

-

Перетащите файл сюда, нажмите для выбора, или вставьте через Ctrl+V

- -
- - - -
- - {#if showUrlInput} -
- - -
- {/if} - -
-

• Поддерживаемые форматы: JPG, PNG, WebP, GIF

-

• Максимальный размер: 10MB

-

• Или вставьте изображение через Ctrl+V

-
-
-
- - { - const file = (e.target as HTMLInputElement).files?.[0]; - if (file) handleFileUpload(file); - }} - /> - {/if} - {#if errorMessage} -
- Ошибка: - {errorMessage} -
- {/if} - - {#if $uiStore.isLoading} -
-
-

Загрузка...

-
- {/if} -
- - diff --git a/src/components/layout/AppContainer.svelte b/src/components/layout/AppContainer.svelte deleted file mode 100644 index c07d231..0000000 --- a/src/components/layout/AppContainer.svelte +++ /dev/null @@ -1,72 +0,0 @@ - - -
- - - - - -
- - diff --git a/src/components/layout/AppContent.svelte b/src/components/layout/AppContent.svelte deleted file mode 100644 index 4243ac4..0000000 --- a/src/components/layout/AppContent.svelte +++ /dev/null @@ -1,64 +0,0 @@ - - -
- - -
- - diff --git a/src/components/layout/AppHeader.svelte b/src/components/layout/AppHeader.svelte index bfc90b0..8586718 100644 --- a/src/components/layout/AppHeader.svelte +++ b/src/components/layout/AppHeader.svelte @@ -1,30 +1,86 @@ - - -
-

Twitch Panels Creator

-
- - + + +
+
+

Twitch Panels

+ +
+
+ + diff --git a/src/components/layout/Card.svelte b/src/components/layout/Card.svelte new file mode 100644 index 0000000..61e3c55 --- /dev/null +++ b/src/components/layout/Card.svelte @@ -0,0 +1,67 @@ + + +{#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 new file mode 100644 index 0000000..a819ae9 --- /dev/null +++ b/src/components/layout/InputGroup.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children()} +
+ + diff --git a/src/components/layout/MainSection.svelte b/src/components/layout/MainSection.svelte deleted file mode 100644 index 75bc5da..0000000 --- a/src/components/layout/MainSection.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - -
- - {#if $uiStore.currentStep === "text"} - - {/if} -
- - diff --git a/src/components/layout/PanelBar.svelte b/src/components/layout/PanelBar.svelte new file mode 100644 index 0000000..d29c1be --- /dev/null +++ b/src/components/layout/PanelBar.svelte @@ -0,0 +1,9 @@ + + +
+ + +
diff --git a/src/components/layout/SettingsGrid.svelte b/src/components/layout/SettingsGrid.svelte new file mode 100644 index 0000000..6a0df13 --- /dev/null +++ b/src/components/layout/SettingsGrid.svelte @@ -0,0 +1,47 @@ + + +
+
+ + diff --git a/src/components/layout/SettingsRow.svelte b/src/components/layout/SettingsRow.svelte new file mode 100644 index 0000000..8f2f99c --- /dev/null +++ b/src/components/layout/SettingsRow.svelte @@ -0,0 +1,41 @@ + + + +
{label}
+
+ {@render children()} +
+
+ + diff --git a/src/components/layout/Sidebar.svelte b/src/components/layout/Sidebar.svelte deleted file mode 100644 index 2d68cc7..0000000 --- a/src/components/layout/Sidebar.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/src/components/layout/TextBar.svelte b/src/components/layout/TextBar.svelte new file mode 100644 index 0000000..6282c95 --- /dev/null +++ b/src/components/layout/TextBar.svelte @@ -0,0 +1,9 @@ + + +
+ + +
diff --git a/src/components/panel/PanelPreview.svelte b/src/components/panel/PanelPreview.svelte deleted file mode 100644 index 7957720..0000000 --- a/src/components/panel/PanelPreview.svelte +++ /dev/null @@ -1,173 +0,0 @@ - - -
-
-
{panel.text?.text || "Без названия"}
- -
-
-
-
- - - {#if backgroundImage} - - {/if} - - - - {#if panel.text} - {@const textPosition = getTextPosition(panel.text)} - - - {/if} - - -
-
-
-
- - diff --git a/src/components/panel/PanelsList.svelte b/src/components/panel/PanelsList.svelte deleted file mode 100644 index 6ea122a..0000000 --- a/src/components/panel/PanelsList.svelte +++ /dev/null @@ -1,66 +0,0 @@ - - -{#if panels.length > 0} -
-
-

Созданные панели ({panels.length})

- -
-
- {#each panels as panel (panel.id)} -
- onDownload(panel, konvaStage)} /> -
- {/each} -
-
-{/if} - - diff --git a/src/components/panel/Preview.svelte b/src/components/panel/Preview.svelte new file mode 100644 index 0000000..32cc402 --- /dev/null +++ b/src/components/panel/Preview.svelte @@ -0,0 +1,38 @@ + + + + + + + + diff --git a/src/components/panel/PreviewAll.svelte b/src/components/panel/PreviewAll.svelte new file mode 100644 index 0000000..2f9da94 --- /dev/null +++ b/src/components/panel/PreviewAll.svelte @@ -0,0 +1,22 @@ + + +
+ {#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 new file mode 100644 index 0000000..e9f8b8d --- /dev/null +++ b/src/components/panel/PreviewControls.svelte @@ -0,0 +1,46 @@ + + + - - {#if errorMessage} -
- {errorMessage} -
- {/if} - - - {#if texts.length > 0} -
-

Созданные тексты ({texts.length})

-
- {#each texts as textItem (textItem.id)} -
- handleUpdateText(textItem.id, e.currentTarget.value)} - class="text-edit-input" - maxlength={TYPOGRAPHY.MAX_TEXT_LENGTH} - /> - -
- {/each} -
-
- {/if} - -
-

Общие настройки текста

-

Настройки применятся ко всем создаваемым панелям

- -
-
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
-
-
- - - + + + + + + - - + + + + + diff --git a/src/components/ui/ColorPicker.svelte b/src/components/ui/ColorPicker.svelte new file mode 100644 index 0000000..eb8b869 --- /dev/null +++ b/src/components/ui/ColorPicker.svelte @@ -0,0 +1,40 @@ + + + +{value} + + diff --git a/src/components/ui/IconButton.svelte b/src/components/ui/IconButton.svelte deleted file mode 100644 index 88dc3e3..0000000 --- a/src/components/ui/IconButton.svelte +++ /dev/null @@ -1,103 +0,0 @@ - - - - - diff --git a/src/components/ui/Outline.svelte b/src/components/ui/Outline.svelte new file mode 100644 index 0000000..148a03b --- /dev/null +++ b/src/components/ui/Outline.svelte @@ -0,0 +1,17 @@ + + + + diff --git a/tests/unit/components/layout/PanelBar.test.ts b/tests/unit/components/layout/PanelBar.test.ts new file mode 100644 index 0000000..a32e907 --- /dev/null +++ b/tests/unit/components/layout/PanelBar.test.ts @@ -0,0 +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(); + }); +}); diff --git a/tests/unit/components/layout/SettingsGrid.test.ts b/tests/unit/components/layout/SettingsGrid.test.ts new file mode 100644 index 0000000..03281ba --- /dev/null +++ b/tests/unit/components/layout/SettingsGrid.test.ts @@ -0,0 +1,48 @@ +import { render, screen } from "@testing-library/svelte"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it } from "vitest"; +import SettingsGridTest from "./SettingsGridTest.svelte"; + +describe("SettingsGrid.svelte", () => { + describe("initial state", () => { + it("should render with collapsed state by default", () => { + render(SettingsGridTest); + expect(screen.queryByText("Item 1")).not.toBeInTheDocument(); + expect(screen.queryByText("Item 2")).not.toBeInTheDocument(); + expect(screen.queryByText("Item 3")).not.toBeInTheDocument(); + }); + + it("should display the label", () => { + render(SettingsGridTest); + expect(screen.getByText("Test Grid")).toBeInTheDocument(); + }); + + it("should render expand button", () => { + render(SettingsGridTest); + const expandButton = screen.getByRole("button", { name: "Expand" }); + expect(expandButton).toBeInTheDocument(); + }); + }); + + describe("expand/collapse behavior", () => { + it("should show children when expand button is clicked", async () => { + const user = userEvent.setup(); + render(SettingsGridTest); + const expandButton = screen.getByRole("button", { name: "Expand" }); + await user.click(expandButton); + expect(screen.getByText("Item 1")).toBeInTheDocument(); + expect(screen.getByText("Item 2")).toBeInTheDocument(); + expect(screen.getByText("Item 3")).toBeInTheDocument(); + }); + + it("should hide children when expand button is clicked twice", async () => { + const user = userEvent.setup(); + render(SettingsGridTest); + const expandButton = screen.getByRole("button", { name: "Expand" }); + await user.click(expandButton); + expect(screen.getByText("Item 1")).toBeInTheDocument(); + await user.click(expandButton); + expect(screen.queryByText("Item 1")).not.toBeInTheDocument(); + }); + }); +}); diff --git a/tests/unit/components/layout/SettingsGridTest.svelte b/tests/unit/components/layout/SettingsGridTest.svelte new file mode 100644 index 0000000..b676a98 --- /dev/null +++ b/tests/unit/components/layout/SettingsGridTest.svelte @@ -0,0 +1,10 @@ + + + +
Item 1
+
Item 2
+
Item 3
+
diff --git a/tests/unit/components/layout/SettingsRow.test.ts b/tests/unit/components/layout/SettingsRow.test.ts new file mode 100644 index 0000000..fe24193 --- /dev/null +++ b/tests/unit/components/layout/SettingsRow.test.ts @@ -0,0 +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(); + }); +}); diff --git a/tests/unit/components/layout/SettingsRowTest.svelte b/tests/unit/components/layout/SettingsRowTest.svelte new file mode 100644 index 0000000..8b71387 --- /dev/null +++ b/tests/unit/components/layout/SettingsRowTest.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/tests/unit/components/layout/TextBar.test.ts b/tests/unit/components/layout/TextBar.test.ts new file mode 100644 index 0000000..9d8557a --- /dev/null +++ b/tests/unit/components/layout/TextBar.test.ts @@ -0,0 +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(); + }); +}); diff --git a/tests/unit/components/panel/Preview.test.ts b/tests/unit/components/panel/Preview.test.ts new file mode 100644 index 0000000..3f8da7b --- /dev/null +++ b/tests/unit/components/panel/Preview.test.ts @@ -0,0 +1,32 @@ +import Preview from "$components/panel/Preview.svelte"; +import { cleanup, render } from "@testing-library/svelte"; +import { afterEach, describe, expect, it } from "vitest"; + +describe("Preview.svelte", () => { + afterEach(() => { + cleanup(); + }); + + 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 new file mode 100644 index 0000000..d6ec499 --- /dev/null +++ b/tests/unit/components/panel/PreviewAll.test.ts @@ -0,0 +1,11 @@ +import PreviewAll from "$components/panel/PreviewAll.svelte"; +import { render } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; + +describe("PreviewAll.svelte", () => { + it("should render without crashing", () => { + const { container } = render(PreviewAll); + + expect(container).toBeTruthy(); + }); +}); diff --git a/tests/unit/components/panel/PreviewControls.test.ts b/tests/unit/components/panel/PreviewControls.test.ts new file mode 100644 index 0000000..ba674aa --- /dev/null +++ b/tests/unit/components/panel/PreviewControls.test.ts @@ -0,0 +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(); + }); +}); diff --git a/tests/unit/components/panel/PreviewControlsTest.svelte b/tests/unit/components/panel/PreviewControlsTest.svelte new file mode 100644 index 0000000..d6de611 --- /dev/null +++ b/tests/unit/components/panel/PreviewControlsTest.svelte @@ -0,0 +1,13 @@ + + + + +
{current}
+
{direction}
diff --git a/tests/unit/components/panel/PreviewManager.test.ts b/tests/unit/components/panel/PreviewManager.test.ts new file mode 100644 index 0000000..1875f6d --- /dev/null +++ b/tests/unit/components/panel/PreviewManager.test.ts @@ -0,0 +1,98 @@ +import PreviewManager from "$components/panel/PreviewManager.svelte"; +import { downloadService } from "$services/downloadService"; +import { konvaStageState } from "$states/konvaStage.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.fromSnapshot([]); + }); + + 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.fromSnapshot(["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.fromSnapshot(["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.fromSnapshot(["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.fromSnapshot(["Temp"]); + render(PreviewManager); + + expect(screen.queryByLabelText(/Empty texts info/i)).not.toBeInTheDocument(); + + textsState.fromSnapshot([]); + + 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.fromSnapshot(["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 })); + + textsState.fromSnapshot(["Only one left"]); + + await waitFor(() => { + 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 new file mode 100644 index 0000000..0506b6a --- /dev/null +++ b/tests/unit/components/text/TextConfig.test.ts @@ -0,0 +1,11 @@ +import TextConfig from "$components/text/TextConfig.svelte"; +import { render } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; + +describe("TextConfig.svelte", () => { + it("should render without crashing", () => { + const { container } = render(TextConfig); + + expect(container).toBeTruthy(); + }); +}); diff --git a/tests/unit/components/text/TextInlineEdit.test.ts b/tests/unit/components/text/TextInlineEdit.test.ts new file mode 100644 index 0000000..b5a3fcc --- /dev/null +++ b/tests/unit/components/text/TextInlineEdit.test.ts @@ -0,0 +1,36 @@ +import TextInlineEdit from "$components/text/TextInlineEdit.svelte"; +import { cleanup, render, screen } from "@testing-library/svelte"; +import { afterEach, describe, expect, it } from "vitest"; + +describe("TextInlineEdit.svelte", () => { + afterEach(() => { + cleanup(); + }); + + 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 new file mode 100644 index 0000000..e65e87d --- /dev/null +++ b/tests/unit/components/text/TextInput.test.ts @@ -0,0 +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); + }); +}); diff --git a/tests/unit/components/text/TextManager.test.ts b/tests/unit/components/text/TextManager.test.ts new file mode 100644 index 0000000..9480d3a --- /dev/null +++ b/tests/unit/components/text/TextManager.test.ts @@ -0,0 +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); + }); +}); diff --git a/tests/unit/components/ui/Alignment.test.ts b/tests/unit/components/ui/Alignment.test.ts new file mode 100644 index 0000000..d6aaab3 --- /dev/null +++ b/tests/unit/components/ui/Alignment.test.ts @@ -0,0 +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 { 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 new file mode 100644 index 0000000..e347d80 --- /dev/null +++ b/tests/unit/components/ui/AlignmentTest.svelte @@ -0,0 +1,11 @@ + + + + +
{align}
diff --git a/tests/unit/components/ui/Badge.test.ts b/tests/unit/components/ui/Badge.test.ts new file mode 100644 index 0000000..b5b6cbd --- /dev/null +++ b/tests/unit/components/ui/Badge.test.ts @@ -0,0 +1,45 @@ +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", () => { + const { container } = render(Badge, { + props: { + text: "", + }, + }); + + expect(container.textContent?.trim()).toBe(""); + }); + + 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 new file mode 100644 index 0000000..d843021 --- /dev/null +++ b/tests/unit/components/ui/Button.test.ts @@ -0,0 +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(); + }); +}); diff --git a/tests/unit/components/ui/ColorPicker.test.ts b/tests/unit/components/ui/ColorPicker.test.ts new file mode 100644 index 0000000..5219943 --- /dev/null +++ b/tests/unit/components/ui/ColorPicker.test.ts @@ -0,0 +1,14 @@ +import ColorPicker from "$components/ui/ColorPicker.svelte"; +import { render, screen } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; + +describe("ColorPicker.svelte", () => { + it("should render without crashing", () => { + render(ColorPicker, { + props: { + value: "#ffffff", + }, + }); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/components/ui/MockIcon.svelte b/tests/unit/components/ui/MockIcon.svelte new file mode 100644 index 0000000..8ad7227 --- /dev/null +++ b/tests/unit/components/ui/MockIcon.svelte @@ -0,0 +1,3 @@ + + + diff --git a/tests/unit/components/ui/RangeSlider.test.ts b/tests/unit/components/ui/RangeSlider.test.ts new file mode 100644 index 0000000..e51a6f5 --- /dev/null +++ b/tests/unit/components/ui/RangeSlider.test.ts @@ -0,0 +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); + }); +}); diff --git a/tests/unit/components/ui/SelectFont.test.ts b/tests/unit/components/ui/SelectFont.test.ts new file mode 100644 index 0000000..a226200 --- /dev/null +++ b/tests/unit/components/ui/SelectFont.test.ts @@ -0,0 +1,14 @@ +import SelectFont from "$components/ui/SelectFont.svelte"; +import { render, screen } from "@testing-library/svelte"; +import { describe, expect, it } from "vitest"; + +describe("SelectFont.svelte", () => { + it("should render without crashing", () => { + render(SelectFont, { + props: { + value: "Arial", + }, + }); + expect(screen.getByRole("combobox")).toBeInTheDocument(); + }); +}); diff --git a/tests/unit/constants.test.ts b/tests/unit/constants.test.ts new file mode 100644 index 0000000..206ea55 --- /dev/null +++ b/tests/unit/constants.test.ts @@ -0,0 +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.OFFSET_Y_MAX).toBeGreaterThan(TYPOGRAPHY.OFFSET_Y_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.IMAGE_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/errorHandler.test.ts b/tests/unit/errorHandler.test.ts deleted file mode 100644 index db450d1..0000000 --- a/tests/unit/errorHandler.test.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { AppError } from "$lib/types/errors"; -import { createError, handleError, isRecoverableError, logError, retryOperation } from "$lib/utils/errorHandler"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -describe("errorHandler", () => { - let consoleErrorSpy: any; - - beforeEach(() => { - consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - }); - - afterEach(() => { - consoleErrorSpy.mockClear(); - vi.restoreAllMocks(); - }); - - describe("handleError", () => { - it("should handle AppError with recoverable flag", () => { - const error = new AppError("Test error", "TEST_ERROR", true); - const result = handleError(error); - - expect(result).toBe("Ошибка: Test error. Попробуйте снова."); - }); - - it("should handle AppError with non-recoverable flag", () => { - const error = new AppError("Critical error", "CRITICAL_ERROR", false); - const result = handleError(error); - - expect(result).toBe("Критическая ошибка: Critical error"); - }); - - it("should handle standard Error objects", () => { - const error = new Error("Standard error message"); - const result = handleError(error, "Custom prefix"); - - expect(result).toBe("Custom prefix: Standard error message"); - }); - - it("should handle string errors", () => { - const result = handleError("String error", "Custom prefix"); - - expect(result).toBe("Custom prefix: String error"); - }); - - it("should handle unknown error types", () => { - const result = handleError({ some: "object" }, "Custom prefix"); - - expect(result).toBe("Custom prefix: Произошла неизвестная ошибка"); - }); - - it("should use default message when none provided", () => { - const result = handleError({ some: "object" }); - - expect(result).toBe("Произошла ошибка: Произошла неизвестная ошибка"); - }); - }); - - describe("isRecoverableError", () => { - it("should return true for recoverable AppError", () => { - const error = new AppError("Test error", "TEST_ERROR", true); - const result = isRecoverableError(error); - - expect(result).toBe(true); - }); - - it("should return false for non-recoverable AppError", () => { - const error = new AppError("Test error", "TEST_ERROR", false); - const result = isRecoverableError(error); - - expect(result).toBe(false); - }); - - it("should return false for non-AppError objects", () => { - const error = new Error("Standard error"); - const result = isRecoverableError(error); - - expect(result).toBe(false); - }); - }); - - describe("createError", () => { - it("should create AppError with default recoverable flag", () => { - const error = createError("Test message", "TEST_CODE"); - - expect(error).toBeInstanceOf(AppError); - expect(error.message).toBe("Test message"); - expect(error.code).toBe("TEST_CODE"); - expect(error.recoverable).toBe(true); - }); - - it("should create AppError with custom recoverable flag", () => { - const error = createError("Test message", "TEST_CODE", false); - - expect(error).toBeInstanceOf(AppError); - expect(error.recoverable).toBe(false); - }); - - it("should create AppError with details", () => { - const details = { some: "additional info" }; - const error = createError("Test message", "TEST_CODE", true, details); - - expect(error.details).toBe(details); - }); - }); - - describe("logError", () => { - it("should log error with context", () => { - const error = new Error("Test error"); - const context = "Test context"; - - logError(error, context); - - expect(consoleErrorSpy).toHaveBeenCalledWith("Error occurred:", { - error, - context, - timestamp: expect.any(String), - stack: error.stack, - }); - }); - - it("should log error without context", () => { - const error = new Error("Test error"); - - logError(error); - - expect(consoleErrorSpy).toHaveBeenCalledWith("Error occurred:", { - error, - context: undefined, - timestamp: expect.any(String), - stack: error.stack, - }); - }); - - it("should handle non-Error objects", () => { - const error = "String error"; - - logError(error, "Test context"); - - expect(consoleErrorSpy).toHaveBeenCalledWith("Error occurred:", { - error, - context: "Test context", - timestamp: expect.any(String), - stack: undefined, - }); - }); - }); - - describe("retryOperation", () => { - it("should succeed on first attempt", async () => { - const operation = vi.fn().mockResolvedValue("success"); - - const result = await retryOperation(operation, 3); - - expect(result).toBe("success"); - expect(operation).toHaveBeenCalledTimes(1); - }); - - it("should retry on failure and succeed", async () => { - const operation = vi.fn().mockRejectedValueOnce(new Error("First failure")).mockResolvedValueOnce("success"); - - const result = await retryOperation(operation, 3, 10); - - expect(result).toBe("success"); - expect(operation).toHaveBeenCalledTimes(2); - }); - - it("should throw after max retries", async () => { - const operation = vi.fn().mockRejectedValue(new Error("Persistent failure")); - - await expect(retryOperation(operation, 2, 10)).rejects.toThrow("Persistent failure"); - expect(operation).toHaveBeenCalledTimes(2); // Initial + 1 retry - }); - - it("should wait between retries", async () => { - const operation = vi.fn().mockRejectedValueOnce(new Error("First failure")).mockResolvedValueOnce("success"); - - const startTime = Date.now(); - await retryOperation(operation, 2, 50); - const endTime = Date.now(); - - expect(endTime - startTime).toBeGreaterThanOrEqual(50); - }); - }); -}); diff --git a/tests/unit/errorTypes.test.ts b/tests/unit/errorTypes.test.ts new file mode 100644 index 0000000..35b1ca3 --- /dev/null +++ b/tests/unit/errorTypes.test.ts @@ -0,0 +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 }); + }); + }); +}); diff --git a/tests/unit/errorUtils.test.ts b/tests/unit/errorUtils.test.ts new file mode 100644 index 0000000..7dc2b58 --- /dev/null +++ b/tests/unit/errorUtils.test.ts @@ -0,0 +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(); + }); + }); +}); diff --git a/tests/unit/panelStorage.test.ts b/tests/unit/panelStorage.test.ts deleted file mode 100644 index 8866e16..0000000 --- a/tests/unit/panelStorage.test.ts +++ /dev/null @@ -1,342 +0,0 @@ -import type { Panel } from "$lib/types/panel"; -import { PanelStorage } from "$lib/utils/panelStorage"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -// Mock the error handler to avoid console noise during tests -vi.mock("$lib/utils/errorHandler", () => ({ - logError: vi.fn(), - handleError: vi.fn((error: any, message: string) => `${message}: ${error}`), -})); - -describe("PanelStorage", () => { - let storage: PanelStorage; - let mockPanel: Panel; - let mockLocalStorage: any; - - beforeEach(() => { - storage = new PanelStorage(); - mockPanel = { - id: "test-panel-1", - backgroundImage: "/backgrounds/b1.jpg", - text: { - id: "test-text-1", - text: "Test Panel", - fontSize: 18, - fontFamily: "Arial", - color: "#ffffff", - textAlign: "center", - paddingX: 10, - verticalOffset: 0, - }, - height: 100, - createdAt: new Date("2024-01-01"), - updatedAt: new Date("2024-01-01"), - }; - - // Create a properly typed mock for localStorage - mockLocalStorage = { - getItem: vi.fn(), - setItem: vi.fn(), - removeItem: vi.fn(), - clear: vi.fn(), - length: 0, - key: vi.fn(), - }; - - // Replace window.localStorage with our mock - Object.defineProperty(window, "localStorage", { - value: mockLocalStorage, - writable: true, - configurable: true, - }); - - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe("savePanel", () => { - it("should save a new panel successfully", () => { - const result = storage.savePanel(mockPanel); - - expect(result.success).toBe(true); - expect(mockLocalStorage.setItem).toHaveBeenCalledWith("twitch-panels", expect.stringContaining("test-panel-1")); - }); - - it("should update existing panel", () => { - // Save initial panel - storage.savePanel(mockPanel); - - // Update panel - const updatedPanel = { ...mockPanel, height: 150 }; - const result = storage.savePanel(updatedPanel); - - expect(result.success).toBe(true); - - // Get the last call to localStorage.setItem - const calls = mockLocalStorage.setItem.mock.calls; - const savedData = JSON.parse(calls[calls.length - 1][1]); - expect(savedData[0].height).toBe(150); - }); - - it("should limit panels to MAX_PANELS count", () => { - // Create and save 51 panels (exceeding MAX_PANELS = 50) - const panels = Array.from({ length: 51 }, (_, i) => ({ - ...mockPanel, - id: `panel-${i}`, - })); - - // Mock localStorage to return the saved data for getAllPanels - let savedData: any[] = []; - mockLocalStorage.setItem.mockImplementation((key: string, value: string) => { - if (key === "twitch-panels") { - savedData = JSON.parse(value); - } - }); - - mockLocalStorage.getItem.mockImplementation((key: string) => { - if (key === "twitch-panels") { - return JSON.stringify(savedData); - } - return null; - }); - - panels.forEach((panel) => storage.savePanel(panel)); - - // Check the final state by calling getAllPanels - const finalPanels = storage.getAllPanels(); - expect(finalPanels.length).toBe(50); - expect(finalPanels[0].id).toBe("panel-50"); // Most recent panel - }); - - it("should handle localStorage errors gracefully", () => { - // Mock localStorage to throw an error - mockLocalStorage.setItem.mockImplementation(() => { - throw new Error("Storage full"); - }); - - const result = storage.savePanel(mockPanel); - - expect(result.success).toBe(false); - expect(result.error).toContain("Ошибка сохранения панели"); - }); - }); - - describe("getAllPanels", () => { - it("should return empty array when no panels exist", () => { - mockLocalStorage.getItem.mockReturnValue(null); - const panels = storage.getAllPanels(); - - expect(panels).toEqual([]); - }); - - it("should return saved panels", () => { - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel])); - - const panels = storage.getAllPanels(); - - expect(panels).toHaveLength(1); - expect(panels[0]).toEqual(mockPanel); - }); - - it("should filter out invalid panels", () => { - const validPanel = mockPanel; - const invalidPanel = { - id: "invalid-1", - backgroundImage: "/backgrounds/b2.jpg", - // Missing required text property - height: 100, - createdAt: new Date(), - updatedAt: new Date(), - }; - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([validPanel, invalidPanel])); - - const panels = storage.getAllPanels(); - - expect(panels).toHaveLength(1); - expect(panels[0]).toEqual(validPanel); - }); - - it("should handle corrupted localStorage data", () => { - mockLocalStorage.getItem.mockReturnValue("invalid json"); - - const panels = storage.getAllPanels(); - - expect(panels).toEqual([]); - }); - }); - - describe("getPanelById", () => { - it("should return panel by id", () => { - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel])); - - const panel = storage.getPanelById("test-panel-1"); - - expect(panel).toEqual(mockPanel); - }); - - it("should return undefined for non-existent panel", () => { - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel])); - - const panel = storage.getPanelById("non-existent"); - - expect(panel).toBeUndefined(); - }); - - it("should handle errors gracefully", () => { - // Mock localStorage to throw an error - mockLocalStorage.getItem.mockImplementation(() => { - throw new Error("Storage error"); - }); - - const panel = storage.getPanelById("test-panel-1"); - - expect(panel).toBeUndefined(); - }); - }); - - describe("deletePanel", () => { - it("should delete panel by id", () => { - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel])); - - const result = storage.deletePanel("test-panel-1"); - - expect(result.success).toBe(true); - expect(mockLocalStorage.setItem).toHaveBeenCalledWith("twitch-panels", "[]"); - }); - - it("should succeed when panel does not exist", () => { - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel])); - - const result = storage.deletePanel("non-existent"); - - expect(result.success).toBe(true); - // Should still save the unchanged array - expect(mockLocalStorage.setItem).toHaveBeenCalled(); - }); - - it("should handle localStorage errors", () => { - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel])); - - // Mock localStorage to throw an error - mockLocalStorage.setItem.mockImplementation(() => { - throw new Error("Storage error"); - }); - - const result = storage.deletePanel("test-panel-1"); - - expect(result.success).toBe(false); - expect(result.error).toContain("Ошибка удаления панели"); - }); - }); - - describe("clearAll", () => { - it("should clear all panels", () => { - const result = storage.clearAll(); - - expect(result.success).toBe(true); - expect(mockLocalStorage.removeItem).toHaveBeenCalledWith("twitch-panels"); - }); - - it("should handle localStorage errors", () => { - // Mock localStorage to throw an error - mockLocalStorage.removeItem.mockImplementation(() => { - throw new Error("Storage error"); - }); - - const result = storage.clearAll(); - - expect(result.success).toBe(false); - expect(result.error).toContain("Ошибка очистки хранилища"); - }); - }); - - describe("getPanelCount", () => { - it("should return correct panel count", () => { - expect(storage.getPanelCount()).toBe(0); - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel])); - expect(storage.getPanelCount()).toBe(1); - - const secondPanel = { ...mockPanel, id: "test-panel-2" }; - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel, secondPanel])); - expect(storage.getPanelCount()).toBe(2); - }); - }); - - describe("hasSpaceForNewPanel", () => { - it("should return true when under limit", () => { - expect(storage.hasSpaceForNewPanel()).toBe(true); - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel])); - expect(storage.hasSpaceForNewPanel()).toBe(true); - }); - - it("should return false when at limit", () => { - // Create and save 50 panels (MAX_PANELS limit) - const panels = Array.from({ length: 50 }, (_, i) => ({ - ...mockPanel, - id: `panel-${i}`, - })); - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify(panels)); - - expect(storage.hasSpaceForNewPanel()).toBe(false); - }); - }); - - describe("panel validation", () => { - it("should validate correct panel structure", () => { - const validPanel = mockPanel; - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([validPanel])); - const panels = storage.getAllPanels(); - - expect(panels).toHaveLength(1); - expect(panels[0]).toEqual(validPanel); - }); - - it("should reject panel with invalid height", () => { - const invalidPanel = { - ...mockPanel, - height: -100, // Negative height - }; - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([invalidPanel])); - const panels = storage.getAllPanels(); - - expect(panels).toHaveLength(0); - }); - - it("should reject panel with height exceeding maximum", () => { - const invalidPanel = { - ...mockPanel, - height: 2000, // Exceeds PANEL_HEIGHT_MAX (1000) - }; - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([invalidPanel])); - const panels = storage.getAllPanels(); - - expect(panels).toHaveLength(0); - }); - - it("should reject panel with missing required properties", () => { - const invalidPanel = { - id: "invalid-panel", - // Missing backgroundImage - text: mockPanel.text, - height: 100, - createdAt: new Date(), - updatedAt: new Date(), - } as any; - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify([invalidPanel])); - const panels = storage.getAllPanels(); - - expect(panels).toHaveLength(0); - }); - }); -}); diff --git a/tests/unit/routes/LayoutTest.svelte b/tests/unit/routes/LayoutTest.svelte new file mode 100644 index 0000000..7d8a4d3 --- /dev/null +++ b/tests/unit/routes/LayoutTest.svelte @@ -0,0 +1,7 @@ + + + + Hello + diff --git a/tests/unit/routes/PageTest.svelte b/tests/unit/routes/PageTest.svelte new file mode 100644 index 0000000..9eb282f --- /dev/null +++ b/tests/unit/routes/PageTest.svelte @@ -0,0 +1,5 @@ + + + diff --git a/tests/unit/routes/layout.settings.test.ts b/tests/unit/routes/layout.settings.test.ts new file mode 100644 index 0000000..86c0f11 --- /dev/null +++ b/tests/unit/routes/layout.settings.test.ts @@ -0,0 +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); + }); +}); diff --git a/tests/unit/routes/layout.test.ts b/tests/unit/routes/layout.test.ts new file mode 100644 index 0000000..772f9e4 --- /dev/null +++ b/tests/unit/routes/layout.test.ts @@ -0,0 +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(); + }); +}); diff --git a/tests/unit/routes/page.test.ts b/tests/unit/routes/page.test.ts new file mode 100644 index 0000000..97802a0 --- /dev/null +++ b/tests/unit/routes/page.test.ts @@ -0,0 +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(); + }); +}); diff --git a/tests/unit/services/downloadService.test.ts b/tests/unit/services/downloadService.test.ts new file mode 100644 index 0000000..a3e6c85 --- /dev/null +++ b/tests/unit/services/downloadService.test.ts @@ -0,0 +1,127 @@ +import { DownloadService, type DownloadItem } from "$services/downloadService"; +import { saveAs } from "file-saver"; +import JSZip from "jszip"; +import type { Stage } from "konva/lib/Stage"; +import { assert, 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", + ); + + assert(result.success === false); + + 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"); + + assert(result.success === false); + + 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 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); + + assert(result.success === false); + + expect(result.error).toContain("Ошибка сохранения архива"); + }); + }); +}); diff --git a/tests/unit/services/exportService.test.ts b/tests/unit/services/exportService.test.ts deleted file mode 100644 index 0159ceb..0000000 --- a/tests/unit/services/exportService.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import type { Panel } from "$lib/types/panel"; -import { ExportService } from "$services/exportService"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -// Mock file-saver -vi.mock("file-saver", () => ({ - default: { - saveAs: vi.fn(), - }, -})); - -describe("ExportService", () => { - let service: ExportService; - let mockPanel: Panel; - let mockKonvaStage: any; - - beforeEach(() => { - service = new ExportService(); - - mockPanel = { - id: "test-panel-1", - backgroundImage: "/backgrounds/b1.jpg", - text: { - id: "test-text-1", - text: "Test Panel", - fontSize: 18, - fontFamily: "Arial", - color: "#ffffff", - textAlign: "center", - paddingX: 10, - verticalOffset: 0, - }, - height: 100, - createdAt: new Date("2024-01-01"), - updatedAt: new Date("2024-01-01"), - }; - - mockKonvaStage = { - toBlob: vi.fn(({ callback }) => { - callback(new Blob(["test image data"], { type: "image/png" })); - }), - }; - }); - - describe("exportPanel", () => { - it("should export panel successfully with valid Konva stage", async () => { - const result = await service.exportPanel(mockPanel, mockKonvaStage); - - expect(result.success).toBe(true); - expect(result.error).toBeUndefined(); - expect(mockKonvaStage.toBlob).toHaveBeenCalled(); - }); - - it("should handle missing Konva stage gracefully", async () => { - const result = await service.exportPanel(mockPanel, null as any); - - expect(result.success).toBe(false); - expect(result.error).toContain("Konva Stage не передан для экспорта"); - }); - - it("should handle Konva stage without toBlob method", async () => { - const invalidStage = { toBlob: undefined }; - - const result = await service.exportPanel(mockPanel, invalidStage as any); - - expect(result.success).toBe(false); - expect(result.error).toContain("Konva Stage не найден или не поддерживает toBlob"); - }); - - it("should handle blob creation failure", async () => { - mockKonvaStage.toBlob = vi.fn(({ callback }) => { - callback(null); // Simulate blob creation failure - }); - - const result = await service.exportPanel(mockPanel, mockKonvaStage); - - expect(result.success).toBe(false); - expect(result.error).toContain("Не удалось создать изображение"); - }); - - it("should use custom filename when provided", async () => { - const customFilename = "custom-panel-name.png"; - - const result = await service.exportPanel(mockPanel, mockKonvaStage, customFilename); - - expect(result.success).toBe(true); - // The filename is passed to file-saver, but we mocked it - }); - - it("should use default filename when custom filename not provided", async () => { - const result = await service.exportPanel(mockPanel, mockKonvaStage); - - expect(result.success).toBe(true); - // Default filename should be "twitch-panel-test-panel-1.png" - }); - }); - - describe("handleDownload", () => { - it("should handle successful download", async () => { - // Mock successful export - mockKonvaStage.toBlob = vi.fn(({ callback }) => { - callback(new Blob(["test image data"], { type: "image/png" })); - }); - - await expect(service.handleDownload(mockPanel, mockKonvaStage)).resolves.not.toThrow(); - }); - - // it("should throw error when export fails", async () => { - // // Mock failed export - // mockKonvaStage.toBlob = vi.fn(({ callback }) => { - // callback(null); - // }); - - // await expect(service.handleDownload(mockPanel, mockKonvaStage)).rejects.toThrow("Ошибка экспорта панели"); - // }); - }); - - describe("exportPanels", () => { - it("should return not implemented error", async () => { - const panels = [mockPanel, { ...mockPanel, id: "test-panel-2" }]; - - const result = await service.exportPanels(panels); - - expect(result.success).toBe(false); - expect(result.error).toBe("Пакетный экспорт еще не реализован"); - }); - }); -}); diff --git a/tests/unit/states/imageConfig.test.ts b/tests/unit/states/imageConfig.test.ts new file mode 100644 index 0000000..17996d3 --- /dev/null +++ b/tests/unit/states/imageConfig.test.ts @@ -0,0 +1,14 @@ +import { imageState } from "$states/image.svelte"; +import { afterEach, describe, expect, it } from "vitest"; + +describe("imageState", () => { + afterEach(() => { + imageState.reset(); + }); + + it("should create new instance with default values", () => { + expect(imageState.fullImage).toBe(""); + expect(imageState.croppedImage).toBeUndefined(); + expect(imageState.cropRect).toStrictEqual({ left: 0, top: 0, right: 0, bottom: 0 }); + }); +}); diff --git a/tests/unit/states/konvaAllStages.test.ts b/tests/unit/states/konvaAllStages.test.ts new file mode 100644 index 0000000..7deea96 --- /dev/null +++ b/tests/unit/states/konvaAllStages.test.ts @@ -0,0 +1,8 @@ +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 new file mode 100644 index 0000000..7096c35 --- /dev/null +++ b/tests/unit/states/konvaStage.test.ts @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..55bbf41 --- /dev/null +++ b/tests/unit/states/persisted.svelte.test.ts @@ -0,0 +1,215 @@ +import { withPersistence } from "$states/persisted.svelte"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +function createMock() { + 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 = {}; + }), + }; +} + +describe("persisted.svelte", () => { + let localStorageMock: ReturnType; + + beforeAll(() => { + localStorageMock = createMock(); + vi.stubGlobal("localStorage", localStorageMock); + }); + + afterAll(() => { + vi.unstubAllGlobals(); + }); + + beforeEach(() => { + localStorageMock.clear(); + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("withPersistence", () => { + it("should return state unchanged when not in browser", () => { + const data = $state({ test: "value" }); + const mockState = { + toSnapshot: () => data, + fromSnapshot: () => {}, + }; + const result = withPersistence("test-key", mockState); + expect(result).toBe(mockState); + }); + + it("should restore state from localStorage when valid JSON exists", () => { + interface State { + value: number; + name: string; + } + const savedData: State = { value: 42, name: "test" }; + + localStorageMock.getItem.mockReturnValue(JSON.stringify(savedData)); + + const data: State = $state({ value: 0, name: "" }); + const state = { + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.value !== undefined) data.value = newData.value; + if (newData.name !== undefined) data.name = newData.name; + }, + }; + + withPersistence("test-key", state); + + expect(state.toSnapshot()).toEqual(savedData); + expect(localStorageMock.getItem).toHaveBeenCalledWith("test-key"); + }); + + it("should handle corrupted JSON in localStorage gracefully", () => { + localStorageMock.getItem.mockReturnValue("{ invalid json"); + + const data = $state({ value: 0 }); + const state = { + toSnapshot: () => data, + fromSnapshot: () => {}, + }; + + expect(() => withPersistence("test-key", state)).not.toThrow(); + expect(state.toSnapshot()).toEqual({ value: 0 }); + }); + + it("should handle empty localStorage (no saved data)", () => { + localStorageMock.getItem.mockReturnValue(null); + + const data = $state({ value: 0 }); + const state = { + toSnapshot: () => data, + fromSnapshot: () => {}, + }; + + withPersistence("test-key", state); + + expect(state.toSnapshot()).toEqual({ value: 0 }); + }); + + it("should save state to localStorage with debounce", async () => { + vi.useFakeTimers(); + interface State { + count: number; + } + const data: State = $state({ count: 1 }); + + const state = { + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.count !== undefined) data.count = newData.count; + }, + }; + + withPersistence("test-key", state, 500); + + state.fromSnapshot({ 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 () => { + interface State { + count: number; + } + const data: State = $state({ count: 1 }); + + const state = { + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.count !== undefined) data.count = newData.count; + }, + }; + + withPersistence("test-key", state, 0); + + state.fromSnapshot({ 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(); + interface State { + count: number; + } + const data: State = $state({ count: 1 }); + + const state = { + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.count !== undefined) data.count = newData.count; + }, + }; + + withPersistence("test-key", state, 500); + + state.fromSnapshot({ count: 2 }); + await Promise.resolve(); + + state.fromSnapshot({ 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 () => { + interface State { + test: boolean; + } + const data: State = $state({ test: true }); + + const state = { + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.test !== undefined) data.test = newData.test; + }, + }; + + withPersistence("test-key", state); + + state.fromSnapshot({ 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 new file mode 100644 index 0000000..bb52b59 --- /dev/null +++ b/tests/unit/states/textConfig.test.ts @@ -0,0 +1,76 @@ +import { TextAlign } from "$lib/constants"; +import type { HexColor } from "$lib/types"; +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("persistence", () => { + it("should serialize and deserialize state", () => { + textConfigState.fontSize = 18; + textConfigState.fontFamily = "Georgia"; + textConfigState.color = "#00ff00" as HexColor; + textConfigState.align = TextAlign.RIGHT; + textConfigState.paddingX = 5; + textConfigState.offsetY = 15; + + const data = textConfigState.toSnapshot(); + expect(data).toEqual({ + fontSize: 18, + fontFamily: "Georgia", + color: "#00ff00", + align: TextAlign.RIGHT, + paddingX: 5, + offsetY: 15, + outlined: false, + }); + + textConfigState.fromSnapshot({ + 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 new file mode 100644 index 0000000..6285e56 --- /dev/null +++ b/tests/unit/states/texts.test.ts @@ -0,0 +1,93 @@ +import { TextsState } 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 = new TextsState(); + 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 = new TextsState(); + const initialLength = state.texts.length; + + state.addText(""); + state.addText(" "); + + expect(state.texts).toHaveLength(initialLength); + }); + }); + + describe("removeText", () => { + it("should remove text by ID", () => { + const state = new TextsState(); + 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 = new TextsState(); + 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 = new TextsState(); + state.addText("Test 1"); + state.addText("Test 2"); + state.addText("Test 3"); + + state.clear(); + + expect(state.texts).toHaveLength(0); + }); + }); + + describe("persistence", () => { + it("should serialize and deserialize texts", () => { + const state = new TextsState(); + state.clear(); + state.addText("First"); + state.addText("Second"); + state.addText("Third"); + + const data = state.toSnapshot(); + expect(data).toEqual(["First", "Second", "Third"]); + + state.fromSnapshot(["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 = new TextsState(); + state.addText("Test"); + + state.fromSnapshot([]); + + expect(state.texts).toHaveLength(0); + }); + }); +}); diff --git a/tests/unit/states/theme.test.ts b/tests/unit/states/theme.test.ts new file mode 100644 index 0000000..87fd7e4 --- /dev/null +++ b/tests/unit/states/theme.test.ts @@ -0,0 +1,44 @@ +import { Theme } from "$lib/constants"; +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("persistence", () => { + it("should serialize and deserialize theme", () => { + themeState.current = Theme.DARK; + expect(themeState.toSnapshot()).toEqual({ current: Theme.DARK }); + + themeState.fromSnapshot({ current: Theme.LIGHT }); + expect(themeState.current).toBe(Theme.LIGHT); + }); + }); +}); diff --git a/tests/unit/stores/panelStore.test.ts b/tests/unit/stores/panelStore.test.ts deleted file mode 100644 index bde9ed3..0000000 --- a/tests/unit/stores/panelStore.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import type { Panel } from "$lib/types/panel"; -import { - createEmptyPanel, - panelStore, - textSettingsStore, - updateAllTextSettings, - updatePanel, -} from "$stores/panelStore"; -import { get } from "svelte/store"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -// Mock uuid -vi.mock("uuid", () => ({ - v4: vi.fn(() => "test-uuid-12345"), -})); - -describe("panelStore", () => { - beforeEach(() => { - // Reset stores to initial state - panelStore.set(undefined); - textSettingsStore.set({ - fontSize: 18, - fontFamily: "Arial", - color: "#ffffff", - textAlign: "left", - paddingX: 10, - verticalOffset: 0, - }); - }); - - describe("panelStore", () => { - it("should initialize with undefined value", () => { - const store = get(panelStore); - expect(store).toBeUndefined(); - }); - - it("should update panel value", () => { - const mockPanel: Panel = { - id: "test-panel-1", - backgroundImage: "/backgrounds/b1.jpg", - text: { - id: "test-text-1", - text: "Test Panel", - fontSize: 18, - fontFamily: "Arial", - color: "#ffffff", - textAlign: "center", - paddingX: 10, - verticalOffset: 0, - }, - height: 100, - createdAt: new Date("2024-01-01"), - updatedAt: new Date("2024-01-01"), - }; - - panelStore.set(mockPanel); - - const store = get(panelStore); - expect(store).toEqual(mockPanel); - }); - }); - - describe("textSettingsStore", () => { - it("should initialize with default text settings", () => { - const settings = get(textSettingsStore); - expect(settings).toEqual({ - fontSize: 18, - fontFamily: "Arial", - color: "#ffffff", - textAlign: "left", - paddingX: 10, - verticalOffset: 0, - }); - }); - - it("should update text settings", () => { - const newSettings = { - fontSize: 24, - fontFamily: "Helvetica", - color: "#000000", - }; - - updateAllTextSettings(newSettings); - - const settings = get(textSettingsStore); - expect(settings.fontSize).toBe(24); - expect(settings.fontFamily).toBe("Helvetica"); - expect(settings.color).toBe("#000000"); - // Other properties should remain unchanged - expect(settings.textAlign).toBe("left"); - expect(settings.paddingX).toBe(10); - expect(settings.verticalOffset).toBe(0); - }); - }); - - describe("createEmptyPanel", () => { - // it("should create panel with default settings", () => { - // const panel = createEmptyPanel(); - - // expect(panel.id).toBe("test-uuid-12345"); - // expect(panel.backgroundImage).toBe(""); - // expect(panel.height).toBe(320); // PANEL_SETTINGS.PANEL_HEIGHT_DEFAULT - // expect(panel.text.text).toBe(""); - // expect(panel.text.fontSize).toBe(18); - // expect(panel.text.fontFamily).toBe("Arial"); - // expect(panel.text.color).toBe("#ffffff"); - // expect(panel.text.textAlign).toBe("center"); - // expect(panel.text.paddingX).toBe(10); - // expect(panel.text.verticalOffset).toBe(0); - // expect(panel.createdAt).toBeInstanceOf(Date); - // expect(panel.updatedAt).toBeInstanceOf(Date); - // }); - - it("should create panel with custom height", () => { - const customHeight = 500; - const panel = createEmptyPanel(customHeight); - - expect(panel.height).toBe(customHeight); - }); - }); - - describe("updatePanel", () => { - it("should update panel properties and timestamp", () => { - const originalPanel: Panel = { - id: "test-panel-1", - backgroundImage: "/backgrounds/b1.jpg", - text: { - id: "test-text-1", - text: "Original Text", - fontSize: 18, - fontFamily: "Arial", - color: "#ffffff", - textAlign: "center", - paddingX: 10, - verticalOffset: 0, - }, - height: 100, - createdAt: new Date("2024-01-01"), - updatedAt: new Date("2024-01-01"), - }; - - const updates = { - backgroundImage: "/backgrounds/b2.jpg", - height: 150, - text: { - ...originalPanel.text, - text: "Updated Text", - fontSize: 24, - }, - }; - - const updatedPanel = updatePanel(originalPanel, updates); - - expect(updatedPanel.backgroundImage).toBe("/backgrounds/b2.jpg"); - expect(updatedPanel.height).toBe(150); - expect(updatedPanel.text.text).toBe("Updated Text"); - expect(updatedPanel.text.fontSize).toBe(24); - // Unchanged properties should remain the same - expect(updatedPanel.id).toBe("test-panel-1"); - expect(updatedPanel.text.fontFamily).toBe("Arial"); - expect(updatedPanel.text.color).toBe("#ffffff"); - // updatedAt should be changed to current time - expect(updatedPanel.updatedAt.getTime()).toBeGreaterThan(originalPanel.updatedAt.getTime()); - // createdAt should remain unchanged - expect(updatedPanel.createdAt).toEqual(originalPanel.createdAt); - }); - - it("should handle empty updates", () => { - const originalPanel: Panel = { - id: "test-panel-1", - backgroundImage: "/backgrounds/b1.jpg", - text: { - id: "test-text-1", - text: "Original Text", - fontSize: 18, - fontFamily: "Arial", - color: "#ffffff", - textAlign: "center", - paddingX: 10, - verticalOffset: 0, - }, - height: 100, - createdAt: new Date("2024-01-01"), - updatedAt: new Date("2024-01-01"), - }; - - const updatedPanel = updatePanel(originalPanel, {}); - - // Should only update the updatedAt timestamp - expect(updatedPanel).toEqual({ - ...originalPanel, - updatedAt: expect.any(Date), - }); - expect(updatedPanel.updatedAt.getTime()).toBeGreaterThan(originalPanel.updatedAt.getTime()); - }); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 8cb2497..47644fc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,8 @@ "strict": true, "strictNullChecks": true, "exactOptionalPropertyTypes": true, - "moduleResolution": "bundler" + "moduleResolution": "bundler", + "allowArbitraryExtensions": true, + "types": ["unplugin-icons/types/svelte"] } } diff --git a/vite.config.ts b/vite.config.ts index b9a6604..8ed9200 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,9 +1,18 @@ import { sveltekit } from "@sveltejs/kit/vite"; +import Icons from "unplugin-icons/vite"; import { defineConfig } from "vite"; export default defineConfig({ + esbuild: { + pure: ["console.log"], + }, build: { assetsInlineLimit: Infinity, }, - plugins: [sveltekit()], + plugins: [ + sveltekit(), + Icons({ + compiler: "svelte", + }), + ], }); diff --git a/vitest.config.ts b/vitest.config.ts index 16dee32..6cd0a2a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,30 +1,47 @@ -import { sveltekit } from "@sveltejs/kit/vite"; -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - plugins: [sveltekit()], - test: { - environment: "jsdom", - setupFiles: ["./tests/setup.ts"], - include: ["tests/**/*.{test,spec}.{js,ts}"], - 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/**", - ], - }, - }, - 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: "2000", + }, + 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, +});