diff --git a/.github/workflows/reverse-survivors-ci.yml b/.github/workflows/reverse-survivors-ci.yml new file mode 100644 index 00000000..72bafef5 --- /dev/null +++ b/.github/workflows/reverse-survivors-ci.yml @@ -0,0 +1,35 @@ +name: reverse-survivors CI + +# Relocated here from reverse_survivors/.github/workflows/ci.yml when the game +# moved into this monorepo — GitHub only reads workflows from the repository +# root, so the nested copy would have been inert. + +on: + push: + branches: [main] + paths: + - "reverse_survivors/**" + - ".github/workflows/reverse-survivors-ci.yml" + pull_request: + branches: [main] + paths: + - "reverse_survivors/**" + - ".github/workflows/reverse-survivors-ci.yml" + +defaults: + run: + working-directory: reverse_survivors + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: reverse_survivors/package-lock.json + - run: npm ci + - run: npm run check + - run: npm run build diff --git a/CLAUDE.md b/CLAUDE.md index e2c84831..8912757e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,6 +33,7 @@ Archived / unmaintained projects live in the sibling repository | `python_pkg/` | Python packages — each maintained subpackage lives here | | `linux_configuration/` | Arch Linux setup, i3 config, system maintenance scripts | | `phone_focus_mode/` | GPS-based Android focus enforcer (Bash, ADB, Magisk) | +| `reverse_survivors/` | Reverse Survivors — React/Vite game, you play the horde (TypeScript, 100 % coverage gate) | | `meta/` | Repo-wide tooling: `pyproject.toml`, `requirements.txt`, `run.sh`, `lint_python.sh`, `.fvmrc` | | `scripts/` | Workspace-level helper scripts and pre-commit hooks (moved to `meta/scripts/`) | | `docs/` | Reference docs; `docs/superpowers/` holds AI workflow artifacts | diff --git a/README.md b/README.md index cd55035a..240d7bf1 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Utility scripts for development workflows — build file validation, secret dete | `python_pkg/` | Python packages (each maintained subpackage lives here) | | `linux_configuration/` | Arch Linux setup, i3 config, system maintenance scripts | | `phone_focus_mode/` | GPS-based Android focus enforcer | +| `reverse_survivors/` | Reverse Survivors — React/Vite game where you play the horde against an AI survivor (TypeScript, 100 % coverage gate) | | `scripts/` | Workspace-level helper scripts and pre-commit hooks | | `docs/` | Reference docs and historical reports | | `third_party/` | Vendored upstream skills/agents | diff --git a/docs/superpowers/contracts/reverse-survivors-2026-08-03.json b/docs/superpowers/contracts/reverse-survivors-2026-08-03.json new file mode 100644 index 00000000..8ba5c1bd --- /dev/null +++ b/docs/superpowers/contracts/reverse-survivors-2026-08-03.json @@ -0,0 +1,20 @@ +{ + "title": "Migrate Reverse Survivors into the testsAndMisc monorepo", + "objective": "Relocate the standalone React 19 + Vite 8 + TypeScript 6 game from ~/Downloads/reverse/reverse-survivors to reverse_survivors/ inside this monorepo, preserving its toolchain and quality gates verbatim. Success means the project builds, lints, and passes its 100 % coverage gate from the new path; a root-level path-scoped GitHub Actions workflow runs those same gates on every touching push or PR; and the directory is listed in both index tables. The migration is complete only when npm run check passes in place — a moved project that cannot build is the failure mode this contract exists to prevent.", + "acceptance_criteria": [ + "reverse_survivors/ contains all 33 project files; git status --porcelain -uall reports 33 and nothing was swallowed by the root .gitignore", + "The project's nested .github/ is deleted and its workflow lives at .github/workflows/reverse-survivors-ci.yml with paths: filters and defaults.run.working-directory both naming reverse_survivors", + "npm ci, npm run check (typecheck + eslint --max-warnings 0 + vitest at 100 % thresholds), and npm run build all pass from reverse_survivors/", + "npm run dev serves the app and Vite transforms /src/main.tsx from the new path", + "README.md and CLAUDE.md each gain a reverse_survivors/ Repository Layout row", + "The commit stages explicit paths only, leaving the concurrent uncommitted billsplit migration and the other modified files in the working tree untouched" + ], + "out_of_scope": [ + "Converting the project from npm to pnpm, or regenerating package-lock.json", + "Changing the project's source code, pinned dependency versions, ESLint config, or coverage thresholds", + "Adding a per-project run.sh — meta/run.sh is a polling-script profiler, not a project dispatcher, and Node 24 is already present locally", + "Committing, fixing, or otherwise touching the in-progress billsplit migration sitting untracked in this working tree", + "Backfilling the missing billsplit/ and bucket_catch/ rows in the index tables" + ], + "verifier": "cd reverse_survivors && npm ci && npm run check && npm run build (the project's own gate, mirrored by .github/workflows/reverse-survivors-ci.yml), plus pre-commit run --files and pre-commit run --hook-stage pre-push --files over the staged paths" +} diff --git a/docs/superpowers/evidence/reverse-survivors-2026-08-03.json b/docs/superpowers/evidence/reverse-survivors-2026-08-03.json new file mode 100644 index 00000000..e15de7be --- /dev/null +++ b/docs/superpowers/evidence/reverse-survivors-2026-08-03.json @@ -0,0 +1,56 @@ +{ + "intent": "Bring the standalone Reverse Survivors game (React 19 + Vite 8 + TypeScript 6) into this monorepo at reverse_survivors/, so it is version-controlled, indexed in README.md/CLAUDE.md, and built by a path-scoped GitHub Actions workflow instead of sitting untracked in ~/Downloads.", + "scope": [ + "reverse_survivors/ — 33 files copied from ~/Downloads/reverse/reverse-survivors", + ".github/workflows/reverse-survivors-ci.yml — relocated from the project's nested .github/workflows/ci.yml", + "README.md and CLAUDE.md — one Repository Layout row each", + "Non-goal: no change to the project's source, pinned dependency versions, lint config, or coverage thresholds", + "Non-goal: no conversion from npm to pnpm; the project ships package-lock.json and its CI uses npm ci", + "Non-goal: the concurrent uncommitted billsplit migration in this working tree is left untouched" + ], + "changes": [ + "Copied the project with cp -a to reverse_survivors/ (snake_case, matching kcd2_dice_solver / bucket_catch / phone_focus_mode), then deleted its nested .github/ directory — GitHub only reads workflows from the repository root, so the nested copy would have been inert. Same treatment billsplit received.", + "Added .github/workflows/reverse-survivors-ci.yml modeled on billsplit-ci.yml: push/pull_request on main filtered to reverse_survivors/** and the workflow file itself, defaults.run.working-directory: reverse_survivors, then npm ci / npm run check / npm run build on Node 22.", + "Set cache-dependency-path: reverse_survivors/package-lock.json on actions/setup-node, because defaults.run.working-directory applies only to run: steps and not to actions, so the npm cache would otherwise look for a lockfile at the repository root and fail.", + "Kept the project's own .gitignore (node_modules, dist, coverage) so it stays runnable standalone, matching kcd2_dice_solver. The root .prettierignore already covers **/package-lock.json, **/dist/ and **/coverage/, so no change was needed there.", + "Added a reverse_survivors/ row to the Repository Layout table in both README.md and CLAUDE.md." + ], + "verification": [ + { + "command": "cd reverse_survivors && npm ci", + "result": "pass", + "evidence": "added 277 packages, and audited 278 packages in 21s; found 0 vulnerabilities. One npm warn EBADENGINE for jsdom@30.0.1 (wants node ^22.22.2 || ^24.15.0, local node is v24.12.0) — a warning only; the CI job pins node-version: 22, which satisfies the ^22.22.2 range." + }, + { + "command": "cd reverse_survivors && npm run check", + "result": "pass", + "evidence": "tsc --noEmit clean; eslint --max-warnings 0 clean; Test Files 8 passed (8), Tests 95 passed (95) in 15.19s. Coverage summary: Statements 100% (410/410), Branches 100% (117/117), Functions 100% (75/75), Lines 100% (396/396) — the vitest thresholds are hard-set to 100 and the run fails below it." + }, + { + "command": "cd reverse_survivors && npm run build", + "result": "pass", + "evidence": "vite v8.2.0 built in 381ms; dist/index.html 0.42 kB, dist/assets/index-FNJxch8t.css 3.42 kB, dist/assets/index-hDCecHon.js 203.53 kB (gzip 64.55 kB)." + }, + { + "command": "cd reverse_survivors && npm run dev (then curl http://localhost:5173/)", + "result": "pass", + "evidence": "VITE v8.2.0 ready in 560 ms on http://localhost:5173/. curl of / returned the app shell with Reverse Survivors — you are the horde and + + diff --git a/reverse_survivors/package-lock.json b/reverse_survivors/package-lock.json new file mode 100644 index 00000000..27ef5df2 --- /dev/null +++ b/reverse_survivors/package-lock.json @@ -0,0 +1,4452 @@ +{ + "name": "reverse-survivors", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "reverse-survivors", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@eslint-react/eslint-plugin": "5.18.1", + "@eslint/js": "10.0.1", + "@testing-library/jest-dom": "7.0.0", + "@testing-library/react": "16.3.2", + "@testing-library/user-event": "14.6.1", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.4", + "@vitejs/plugin-react": "6.0.5", + "@vitest/coverage-v8": "4.1.10", + "@vitest/eslint-plugin": "1.6.24", + "eslint": "10.8.0", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-react-refresh": "0.5.3", + "globals": "17.8.0", + "jsdom": "30.0.1", + "typescript": "6.0.3", + "typescript-eslint": "8.65.0", + "vite": "8.2.0", + "vitest": "4.1.10" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.5.tgz", + "integrity": "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.2.1", + "@csstools/css-color-parser": "^4.1.9", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@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.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "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/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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "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" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "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/@csstools/css-color-parser": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "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/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "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/@emnapi/core": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "2.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "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/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-react/ast": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/@eslint-react/ast/-/ast-5.18.1.tgz", + "integrity": "sha512-6NTpdv+Z6eHT2lc8DkfJZUG6tUvZQkRBlvBpYuoeen+HIvzz59aOt1YEL4dd6FQlXD4DPbCBGXCjcV879hT8Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/typescript-estree": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0", + "string-ts": "^2.3.1" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/@eslint-react/core": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/@eslint-react/core/-/core-5.18.1.tgz", + "integrity": "sha512-n2avrghmy22iMFJWGXKacyMMvtaFrPqq1Yydd7L1DKmUe/hubjD4Uc1ZKV/F2xRh8dQ0Gp2b5TVv7H/Nq5DxUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.18.1", + "@eslint-react/eslint": "5.18.1", + "@eslint-react/shared": "5.18.1", + "@eslint-react/var": "5.18.1", + "@typescript-eslint/scope-manager": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/@eslint-react/eslint": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/@eslint-react/eslint/-/eslint-5.18.1.tgz", + "integrity": "sha512-T7AOWRwc1+gBEQUFoGSsBVHmWpsanGWVU52NTmWxij9ZGIlqbG+JC6vpCBa6IzGPKbulCu4B/np2JIydZQM1pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.65.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/@eslint-react/eslint-plugin": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/@eslint-react/eslint-plugin/-/eslint-plugin-5.18.1.tgz", + "integrity": "sha512-BlgYu//MKKnLHhQhh6E6ChSbEYj/a/cuRnyc+akOUtn8qCEqf2Le+/Rcxjfso3ujDjrq5yZt8E28aNOubtE6WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/shared": "5.18.1", + "eslint-plugin-react-dom": "5.18.1", + "eslint-plugin-react-jsx": "5.18.1", + "eslint-plugin-react-naming-convention": "5.18.1", + "eslint-plugin-react-rsc": "5.18.1", + "eslint-plugin-react-web-api": "5.18.1", + "eslint-plugin-react-x": "5.18.1" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/@eslint-react/jsx": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/@eslint-react/jsx/-/jsx-5.18.1.tgz", + "integrity": "sha512-sT0JQ0yw/prFwtEu0cplzS0fZBpNHL1s7awHrD5xKABpqsVS2wofB52p2TSX2nmuNj011hSUW+xlu58GXYZ/fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.18.1", + "@eslint-react/eslint": "5.18.1", + "@eslint-react/shared": "5.18.1", + "@eslint-react/var": "5.18.1", + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/@eslint-react/shared": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/@eslint-react/shared/-/shared-5.18.1.tgz", + "integrity": "sha512-sLtaIw5PGVriltlBfDD/wUvF4+K3EDGuFhoUF2HKSIdhf28glyrcf6s2d0HnjFXUjXKcD325MNWL7Cectvs2GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/eslint": "5.18.1", + "@typescript-eslint/utils": "^8.65.0", + "ts-pattern": "^5.9.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/@eslint-react/var": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/@eslint-react/var/-/var-5.18.1.tgz", + "integrity": "sha512-w1OP5/EJ4ocGWSw+nSpZnfehlnAvfVxOrlgx7k+xTHACyoFQZZXrehOYC7M/0ArQA68sOXo0C1ymEfTJE30StQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.18.1", + "@eslint-react/eslint": "5.18.1", + "@typescript-eslint/scope-manager": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "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/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "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.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "2.0.0-alpha.3", + "@emnapi/runtime": "2.0.0-alpha.3", + "@napi-rs/wasm-runtime": "^1.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "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/@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", + "peer": true, + "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/jest-dom": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.0.tgz", + "integrity": "sha512-HKAH9C6mBo5yBG6yRO5i43L2iisencAo5z+o5P/saHUoY+miC5ivXRxHBJcFyB5ypPNxHJdK3BoF/3O4DIptMg==", + "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": ">=22", + "npm": ">=6", + "yarn": ">=1" + }, + "peerDependencies": { + "@testing-library/dom": ">=10 <11" + } + }, + "node_modules/@testing-library/jest-dom/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/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "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/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.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", + "peer": true + }, + "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/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/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.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.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.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.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.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.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.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/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/eslint-plugin": { + "version": "1.6.24", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.6.24.tgz", + "integrity": "sha512-U5isuChJ58H1M6oRGaIotYbH2gM1rhzKUjtiarqpLNG/3+fzXIXs5RyTLrYQJmQPnXbvIMPFvI9z8ufpFZc8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "^8.58.0", + "@typescript-eslint/utils": "^8.58.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "*", + "eslint": ">=8.57.0", + "typescript": ">=5.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "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 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-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", + "peer": true, + "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", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/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/ast-v8-to-istanbul": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.10.tgz", + "integrity": "sha512-35JEvJ5/KKlbCHjMCsONI2w6HE88STjVdHk+C7d8LtcFxUjZR1KeLP9izofn2qs0KUxX5r4z73bwH/rd+JHacw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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/birecord": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/birecord/-/birecord-0.1.2.tgz", + "integrity": "sha512-5PAPTTmMpMEb+GuMb5DebfBkipRGyIW9+gtwEBSoDA9xkhHILm04+hZQ702pMksu3d8YAuGkmgTzQWcKqTPScA==", + "dev": true, + "license": "(MIT OR Apache-2.0)" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "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.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "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/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.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/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "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/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "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/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/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/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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", + "peer": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.399", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.399.tgz", + "integrity": "sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "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": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.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", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-dom": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-dom/-/eslint-plugin-react-dom-5.18.1.tgz", + "integrity": "sha512-0PakQ8iPZ2yq4zi99T28l+NlefxrE/92CpIQ1SbbFT2fqNIjRz1L+ewWIzAsMdyUUjQzcCj8domEJ36y20K8Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.18.1", + "@eslint-react/eslint": "5.18.1", + "@eslint-react/jsx": "5.18.1", + "@eslint-react/shared": "5.18.1", + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0", + "compare-versions": "^6.1.1" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-jsx": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-jsx/-/eslint-plugin-react-jsx-5.18.1.tgz", + "integrity": "sha512-bT8/pcYwp1Anyjnvjqz1jRJBGdo4ydXlR6hciyQUiHzOxAWpIOP61ggAVgbA9JU3qOr4/hldMpGZHdJP5pqfgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.18.1", + "@eslint-react/core": "5.18.1", + "@eslint-react/eslint": "5.18.1", + "@eslint-react/jsx": "5.18.1", + "@eslint-react/shared": "5.18.1", + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/eslint-plugin-react-naming-convention": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-naming-convention/-/eslint-plugin-react-naming-convention-5.18.1.tgz", + "integrity": "sha512-fN6U+Q5QOZ7ZJlTn7XsKB0NvRx0qDp+7tV2Zht3nhtKQ2tl4cxFz8QV78l++FqMtBtCNfxTiInuo5u5Xt8Ymjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.18.1", + "@eslint-react/core": "5.18.1", + "@eslint-react/eslint": "5.18.1", + "@eslint-react/shared": "5.18.1", + "@eslint-react/var": "5.18.1", + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", + "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-plugin-react-rsc": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-rsc/-/eslint-plugin-react-rsc-5.18.1.tgz", + "integrity": "sha512-s792p16e8YNhGA3vgIseDxdrwTuNb+gHn0Gau5PsTZ42d8Zuvi3ZJWLfkAZfvMfblaDev6tSAv2fZzWB0brEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.18.1", + "@eslint-react/core": "5.18.1", + "@eslint-react/eslint": "5.18.1", + "@eslint-react/shared": "5.18.1", + "@eslint-react/var": "5.18.1", + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/eslint-plugin-react-web-api": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-web-api/-/eslint-plugin-react-web-api-5.18.1.tgz", + "integrity": "sha512-Az8kJEM2Bk8PhJR3UBLWBVZkir/2l+qX545PTi8Kx3JdDlK6IlU1vz69/B5tJ/YXo6cJKP3bKTqaAzcT5ikl8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.18.1", + "@eslint-react/core": "5.18.1", + "@eslint-react/eslint": "5.18.1", + "@eslint-react/shared": "5.18.1", + "@eslint-react/var": "5.18.1", + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0", + "birecord": "^0.1.2", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/eslint-plugin-react-x": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-x/-/eslint-plugin-react-x-5.18.1.tgz", + "integrity": "sha512-i0a17vUMoMhiHjcXz4IYfO42Br6xQkbu+PqVftUHM49i1bbYD+22GFMgNmKkLCMNKXZSQhocKvIKUwhMIbFTQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.18.1", + "@eslint-react/core": "5.18.1", + "@eslint-react/eslint": "5.18.1", + "@eslint-react/jsx": "5.18.1", + "@eslint-react/shared": "5.18.1", + "@eslint-react/var": "5.18.1", + "@typescript-eslint/scope-manager": "^8.65.0", + "@typescript-eslint/type-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/typescript-estree": "^8.65.0", + "@typescript-eslint/utils": "^8.65.0", + "compare-versions": "^6.1.1", + "string-ts": "^2.3.1", + "ts-api-utils": "^2.5.0", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "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/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.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-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/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/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/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "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/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/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/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-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/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-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": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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/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", + "peer": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "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.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "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/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "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.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "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/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.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/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.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "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.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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", + "peer": true, + "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/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": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "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", + "peer": true + }, + "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/rolldown": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.142.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.1", + "@rolldown/binding-darwin-arm64": "1.2.1", + "@rolldown/binding-darwin-x64": "1.2.1", + "@rolldown/binding-freebsd-x64": "1.2.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", + "@rolldown/binding-linux-arm64-gnu": "1.2.1", + "@rolldown/binding-linux-arm64-musl": "1.2.1", + "@rolldown/binding-linux-ppc64-gnu": "1.2.1", + "@rolldown/binding-linux-s390x-gnu": "1.2.1", + "@rolldown/binding-linux-x64-gnu": "1.2.1", + "@rolldown/binding-linux-x64-musl": "1.2.1", + "@rolldown/binding-openharmony-arm64": "1.2.1", + "@rolldown/binding-wasm32-wasi": "1.2.1", + "@rolldown/binding-win32-arm64-msvc": "1.2.1", + "@rolldown/binding-win32-x64-msvc": "1.2.1" + } + }, + "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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/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": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-ts": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/string-ts/-/string-ts-2.3.1.tgz", + "integrity": "sha512-xSJq+BS52SaFFAVxuStmx6n5aYZU571uYUnUrPXkPFCfdHyZMMlbP2v2Wx5sNBnAVzq/2+0+mcBLBa3Xa5ubYw==", + "dev": true, + "license": "MIT" + }, + "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/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.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "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.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-pattern": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.9.0.tgz", + "integrity": "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", + "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "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/vite": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.23", + "rolldown": "~1.2.0", + "tinyglobby": "^0.2.17" + }, + "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", + "@vitejs/devtools": "^0.4.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.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.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "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/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "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": "17.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, + "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/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/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/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/reverse_survivors/package.json b/reverse_survivors/package.json new file mode 100644 index 00000000..3db2b362 --- /dev/null +++ b/reverse_survivors/package.json @@ -0,0 +1,41 @@ +{ + "name": "reverse-survivors", + "version": "0.1.0", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview", + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0", + "test": "vitest run", + "coverage": "vitest run --coverage", + "check": "npm run typecheck && npm run lint && npm run coverage" + }, + "dependencies": { + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@eslint-react/eslint-plugin": "5.18.1", + "@eslint/js": "10.0.1", + "@testing-library/jest-dom": "7.0.0", + "@testing-library/react": "16.3.2", + "@testing-library/user-event": "14.6.1", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.4", + "@vitejs/plugin-react": "6.0.5", + "@vitest/coverage-v8": "4.1.10", + "@vitest/eslint-plugin": "1.6.24", + "eslint": "10.8.0", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-react-refresh": "0.5.3", + "globals": "17.8.0", + "jsdom": "30.0.1", + "typescript": "6.0.3", + "typescript-eslint": "8.65.0", + "vite": "8.2.0", + "vitest": "4.1.10" + }, + "private": true, + "type": "module" +} diff --git a/reverse_survivors/src/App.test.tsx b/reverse_survivors/src/App.test.tsx new file mode 100644 index 00000000..460da746 --- /dev/null +++ b/reverse_survivors/src/App.test.tsx @@ -0,0 +1,70 @@ +import { act, fireEvent, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { App } from './App' +import { makeEnemy } from './core/sim' +import type { RafHarness } from './test/harness' +import { installRaf, makeCtxStub } from './test/harness' + +afterEach(() => { + vi.unstubAllGlobals() + vi.restoreAllMocks() +}) + +const pump = (raf: RafHarness, ts: number): void => { + act(() => { + raf.pump(ts) + }) +} + +describe('App', () => { + it('runs the loop and accrues souls even without a canvas context', () => { + const raf = installRaf() + render() + expect(screen.getByText('30 / 400')).toBeInTheDocument() + pump(raf, 0) + for (let i = 1; i <= 20; i += 1) { + pump(raf, i * 1000) + } + expect(screen.getByText('42 / 400')).toBeInTheDocument() + expect(screen.getByText(/0:02 \/ 5:00/)).toBeInTheDocument() + }) + + it('spends souls on a spawn and draws the horde', () => { + const raf = installRaf() + const stub = makeCtxStub() + vi.spyOn(HTMLCanvasElement.prototype, 'getContext') + .mockImplementation(() => stub as unknown as CanvasRenderingContext2D) + render() + fireEvent.click(screen.getByRole('button', { name: /rusher/i })) + pump(raf, 0) + expect(screen.getByText('20 / 400')).toBeInTheDocument() + const arcsWithHorde = stub.arc.mock.calls.length + expect(arcsWithHorde).toBeGreaterThan(4) + }) + + it('shows the director victory overlay and restarts fresh', () => { + const raf = installRaf() + render( + { + state.survivor.hp = 5 + makeEnemy(state, 'rusher', { x: state.survivor.pos.x, y: state.survivor.pos.y }) + }} + />, + ) + pump(raf, 0) + expect(screen.getByText('The intruder falls')).toBeInTheDocument() + fireEvent.click(screen.getByRole('button', { name: /open the gates/i })) + expect(screen.queryByText('The intruder falls')).not.toBeInTheDocument() + expect(screen.getByText('30 / 400')).toBeInTheDocument() + }) + + it('shows the dawn overlay when the survivor outlasts the horde', () => { + const raf = installRaf() + render( { state.duration = 0.05 }} />) + pump(raf, 0) + pump(raf, 100) + expect(screen.getByText('Dawn breaks')).toBeInTheDocument() + }) +}) diff --git a/reverse_survivors/src/App.tsx b/reverse_survivors/src/App.tsx new file mode 100644 index 00000000..235cef7e --- /dev/null +++ b/reverse_survivors/src/App.tsx @@ -0,0 +1,72 @@ +import type { ReactElement } from 'react' +import { useRef, useState } from 'react' +import { fmtTime } from './ui/format' +import { createInitialState, step } from './core/sim' +import type { HudSnapshot } from './core/snapshot' +import { snapshotOf } from './core/snapshot' +import type { DirectorAction, GameState } from './core/types' +import type { Ctx2D } from './render/draw' +import { draw } from './render/draw' +import { CanvasStage } from './ui/CanvasStage' +import { Hud } from './ui/Hud' +import { SpawnBar } from './ui/SpawnBar' +import { useGameLoop } from './ui/useGameLoop' + +interface Props { + seed?: number + /** Test seam: mutate the freshly created state before the loop starts. */ + boot?: (state: GameState) => void +} + +const createGame = (seed: number, boot: Props['boot']): GameState => { + const state = createInitialState(seed) + if (boot !== undefined) { + boot(state) + } + return state +} + +export const App = ({ seed = 20260802, boot }: Props): ReactElement => { + const gameRef = useRef(createGame(seed, boot)) + const [snap, setSnap] = useState(() => snapshotOf(createGame(seed, boot))) + const ctxRef = useRef(null) + const queueRef = useRef([]) + + useGameLoop((dt) => { + const state = gameRef.current + step(state, queueRef.current, dt) + queueRef.current = [] + const ctx = ctxRef.current + if (ctx !== null) { + draw(ctx, state) + } + setSnap(snapshotOf(state)) + }) + + const handleAction = (action: DirectorAction): void => { + queueRef.current.push(action) + } + const handleRestart = (): void => { + gameRef.current = createGame(Date.now() >>> 0, undefined) + queueRef.current = [] + setSnap(snapshotOf(gameRef.current)) + } + const handleCtx = (ctx: Ctx2D | null): void => { + ctxRef.current = ctx + } + + return ( +
+
+

Reverse Survivors

+

You are the horde. The hero is the problem.

+

{fmtTime(snap.t)} / {fmtTime(snap.duration)}

+
+
+ + +
+ +
+ ) +} diff --git a/reverse_survivors/src/core/director.test.ts b/reverse_survivors/src/core/director.test.ts new file mode 100644 index 00000000..7e859d35 --- /dev/null +++ b/reverse_survivors/src/core/director.test.ts @@ -0,0 +1,171 @@ +import { describe, expect, it } from 'vitest' +import { applyAction, edgeSpawnPos, regenRate, tickDirector, waveCost } from './director' +import { createInitialState } from './sim' +import { snapshotOf } from './snapshot' +import type { GameState } from './types' +import { ARENA, BOSS_COOLDOWN, ENERGY_CAP } from './types' + +const fresh = (seed = 1): GameState => createInitialState(seed) + +describe('economy', () => { + it('regen ramps every 30 seconds', () => { + expect(regenRate(0)).toBe(6) + expect(regenRate(29.9)).toBe(6) + expect(regenRate(30)).toBe(8) + expect(regenRate(90)).toBe(12) + }) + + it('wave cost escalates', () => { + expect(waveCost(0)).toBe(60) + expect(waveCost(3)).toBe(135) + }) + + it('energy accrues and caps', () => { + const s = fresh() + s.director.energy = ENERGY_CAP - 0.1 + tickDirector(s, 1) + expect(s.director.energy).toBe(ENERGY_CAP) + }) + + it('boss cooldowns tick down and floor at zero', () => { + const s = fresh() + s.director.bossCooldowns.colossus = 0.5 + tickDirector(s, 1) + expect(s.director.bossCooldowns.colossus).toBe(0) + }) +}) + +describe('edge spawns', () => { + it('always lands on an arena edge, and all four edges occur', () => { + const s = fresh(7) + const edges = new Set() + for (let i = 0; i < 40; i += 1) { + const p = edgeSpawnPos(s) + const onEdge = + p.y === 0 ? 'top' + : p.y === ARENA.h ? 'bottom' + : p.x === 0 ? 'left' + : p.x === ARENA.w ? 'right' + : 'off' + edges.add(onEdge) + } + expect(edges.has('off')).toBe(false) + expect(edges).toEqual(new Set(['top', 'bottom', 'left', 'right'])) + }) +}) + +describe('spawning units', () => { + it('deducts energy and adds the enemy', () => { + const s = fresh() + expect(applyAction(s, { type: 'spawn', kind: 'rusher' })).toBe(true) + expect(s.director.energy).toBe(20) + expect(s.enemies).toHaveLength(1) + }) + + it('rejects what the director cannot afford', () => { + const s = fresh() + expect(applyAction(s, { type: 'spawn', kind: 'tank' })).toBe(false) + expect(s.enemies).toHaveLength(0) + expect(s.director.energy).toBe(30) + }) +}) + +describe('waves', () => { + it('first wave rings six rushers around the survivor', () => { + const s = fresh() + s.director.energy = 100 + expect(applyAction(s, { type: 'wave' })).toBe(true) + expect(s.enemies).toHaveLength(6) + expect(s.enemies.every((e) => e.kind === 'rusher')).toBe(true) + expect(s.director.waveIndex).toBe(1) + }) + + it('later waves grow and mix in stalkers', () => { + const s = fresh() + s.director.energy = 400 + applyAction(s, { type: 'wave' }) + applyAction(s, { type: 'wave' }) + expect(s.enemies).toHaveLength(6 + 9) + expect(s.enemies.filter((e) => e.kind === 'stalker')).toHaveLength(1) + }) + + it('rejects an unaffordable wave', () => { + const s = fresh() + expect(applyAction(s, { type: 'wave' })).toBe(false) + expect(s.enemies).toHaveLength(0) + }) +}) + +describe('bosses', () => { + it('locked before the unlock time', () => { + const s = fresh() + s.director.energy = 400 + expect(applyAction(s, { type: 'boss', kind: 'colossus' })).toBe(false) + }) + + it('rejected while cooling down', () => { + const s = fresh() + s.t = 60 + s.director.energy = 400 + expect(applyAction(s, { type: 'boss', kind: 'colossus' })).toBe(true) + s.director.energy = 400 + expect(applyAction(s, { type: 'boss', kind: 'colossus' })).toBe(false) + }) + + it('rejected when unaffordable', () => { + const s = fresh() + s.t = 60 + s.director.energy = 10 + expect(applyAction(s, { type: 'boss', kind: 'colossus' })).toBe(false) + }) + + it('summoning pays, spawns, and starts the cooldown', () => { + const s = fresh() + s.t = 120 + s.director.energy = 400 + expect(applyAction(s, { type: 'boss', kind: 'hivemind' })).toBe(true) + expect(s.director.energy).toBe(400 - 220) + expect(s.enemies[0]?.kind).toBe('hivemind') + expect(s.director.bossCooldowns.hivemind).toBe(BOSS_COOLDOWN.hivemind) + }) +}) + +describe('snapshot', () => { + it('derives affordability both ways', () => { + const s = fresh() + const snap = snapshotOf(s) + expect(snap.units.find((u) => u.kind === 'rusher')?.affordable).toBe(true) + expect(snap.units.find((u) => u.kind === 'tank')?.affordable).toBe(false) + expect(snap.waveAffordable).toBe(false) + s.director.energy = 300 + expect(snapshotOf(s).waveAffordable).toBe(true) + }) + + it('reports boss phases: locked, cooling, ready', () => { + const s = fresh() + expect(snapshotOf(s).bosses[0]?.phase).toBe('locked') + s.t = 61 + s.director.bossCooldowns.colossus = 5 + expect(snapshotOf(s).bosses[0]?.phase).toBe('cooling') + s.director.bossCooldowns.colossus = 0 + expect(snapshotOf(s).bosses[0]?.phase).toBe('ready') + expect(snapshotOf(s).bosses[0]?.unlockIn).toBe(0) + }) + + it('keeps only the last three upgrades, numbered', () => { + const s = fresh() + s.upgrades = ['damage', 'speed', 'regen', 'multishot'] + const snap = snapshotOf(s) + expect(snap.upgrades).toEqual([ + { n: 2, id: 'speed' }, + { n: 3, id: 'regen' }, + { n: 4, id: 'multishot' }, + ]) + }) + + it('floors the reported survivor hp at zero', () => { + const s = fresh() + s.survivor.hp = -12 + expect(snapshotOf(s).survivorHp).toBe(0) + }) +}) diff --git a/reverse_survivors/src/core/director.ts b/reverse_survivors/src/core/director.ts new file mode 100644 index 00000000..72828a3e --- /dev/null +++ b/reverse_survivors/src/core/director.ts @@ -0,0 +1,96 @@ +import { makeEnemy } from './sim' +import { nextFloat, pick } from './rng' +import type { BossKind, DirectorAction, GameState, UnitKind } from './types' +import { + ARENA, BOSS_COOLDOWN, BOSS_ORDER, BOSS_UNLOCK_AT, ENERGY_CAP, ENEMY_SPECS, +} from './types' +import type { Vec } from './vec' +import { clamp } from './vec' + +/** Energy per second: ramps every 30 seconds so pressure always grows. */ +export const regenRate = (t: number): number => 6 + 2 * Math.floor(t / 30) + +export const waveCost = (waveIndex: number): number => 60 + 25 * waveIndex + +export const tickDirector = (state: GameState, dt: number): void => { + const d = state.director + d.energy = Math.min(ENERGY_CAP, d.energy + regenRate(state.t) * dt) + for (const boss of BOSS_ORDER) { + d.bossCooldowns[boss] = Math.max(0, d.bossCooldowns[boss] - dt) + } +} + +type EdgeFn = (roll: number) => Vec +const EDGES: readonly [EdgeFn, ...EdgeFn[]] = [ + (roll): Vec => ({ x: roll * ARENA.w, y: 0 }), + (roll): Vec => ({ x: roll * ARENA.w, y: ARENA.h }), + (roll): Vec => ({ x: 0, y: roll * ARENA.h }), + (roll): Vec => ({ x: ARENA.w, y: roll * ARENA.h }), +] + +export const edgeSpawnPos = (state: GameState): Vec => { + const edge = pick(state.rng, EDGES) + return edge(nextFloat(state.rng)) +} + +const spawnUnit = (state: GameState, kind: UnitKind): boolean => { + const spec = ENEMY_SPECS[kind] + if (state.director.energy < spec.cost) { + return false + } + state.director.energy -= spec.cost + makeEnemy(state, kind, edgeSpawnPos(state)) + return true +} + +const callWave = (state: GameState): boolean => { + const cost = waveCost(state.director.waveIndex) + if (state.director.energy < cost) { + return false + } + state.director.energy -= cost + const w = state.director.waveIndex + const rushers = 6 + 2 * w + const stalkers = w + const total = rushers + stalkers + const sv = state.survivor.pos + for (let i = 0; i < total; i += 1) { + const angle = (Math.PI * 2 * i) / total + nextFloat(state.rng) * 0.3 + const kind = i < rushers ? 'rusher' : 'stalker' + makeEnemy(state, kind, { + x: clamp(sv.x + Math.cos(angle) * 280, 10, ARENA.w - 10), + y: clamp(sv.y + Math.sin(angle) * 280, 10, ARENA.h - 10), + }) + } + state.director.waveIndex += 1 + return true +} + +const summonBoss = (state: GameState, kind: BossKind): boolean => { + if (state.t < BOSS_UNLOCK_AT[kind]) { + return false + } + if (state.director.bossCooldowns[kind] > 0) { + return false + } + const spec = ENEMY_SPECS[kind] + if (state.director.energy < spec.cost) { + return false + } + state.director.energy -= spec.cost + makeEnemy(state, kind, edgeSpawnPos(state)) + state.director.bossCooldowns[kind] = BOSS_COOLDOWN[kind] + return true +} + +/** Applies a director order. Returns false when rejected (cost, lock, or cooldown). */ +export const applyAction = (state: GameState, action: DirectorAction): boolean => { + switch (action.type) { + case 'spawn': + return spawnUnit(state, action.kind) + case 'wave': + return callWave(state) + case 'boss': + return summonBoss(state, action.kind) + } +} diff --git a/reverse_survivors/src/core/rng.ts b/reverse_survivors/src/core/rng.ts new file mode 100644 index 00000000..1c582101 --- /dev/null +++ b/reverse_survivors/src/core/rng.ts @@ -0,0 +1,33 @@ +/** Mutable state for a mulberry32 generator. */ +export interface Rng { + s: number +} + +export const createRng = (seed: number): Rng => ({ s: seed >>> 0 }) + +/** Deterministic float in [0, 1). Advances the generator. */ +export const nextFloat = (rng: Rng): number => { + rng.s = (rng.s + 0x6d2b79f5) >>> 0 + let t = rng.s + t = Math.imul(t ^ (t >>> 15), t | 1) + t ^= t + Math.imul(t ^ (t >>> 7), t | 61) + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 +} + +/** Deterministic integer in [0, maxExclusive). */ +export const nextInt = (rng: Rng, maxExclusive: number): number => + Math.floor(nextFloat(rng) * maxExclusive) + +/** Deterministic pick from a non-empty tuple. */ +export const pick = (rng: Rng, items: readonly [T, ...T[]]): T => { + const idx = nextInt(rng, items.length) + let out: T = items[0] + let i = 0 + for (const item of items) { + if (i === idx) { + out = item + } + i += 1 + } + return out +} diff --git a/reverse_survivors/src/core/sim.test.ts b/reverse_survivors/src/core/sim.test.ts new file mode 100644 index 00000000..627ea67e --- /dev/null +++ b/reverse_survivors/src/core/sim.test.ts @@ -0,0 +1,370 @@ +import { describe, expect, it } from 'vitest' +import { + applyUpgrade, createInitialState, DANGER_RADIUS, grantXp, makeEnemy, step, xpForLevel, +} from './sim' +import type { GameState, Projectile } from './types' +import { ARENA, GAME_DURATION } from './types' + +const CENTER = { x: ARENA.w / 2, y: ARENA.h / 2 } + +const fresh = (seed = 1, duration?: number): GameState => + duration === undefined ? createInitialState(seed) : createInitialState(seed, duration) + +const shot = (state: GameState, x: number, y: number, damage: number): Projectile => { + const p: Projectile = { + id: state.nextId, + from: 'survivor', + pos: { x, y }, + vel: { x: 0, y: 0 }, + radius: 4, + damage, + ttl: 1, + } + state.nextId += 1 + state.projectiles.push(p) + return p +} + +describe('createInitialState', () => { + it('uses the default duration when omitted', () => { + expect(fresh().duration).toBe(GAME_DURATION) + }) + + it('accepts a duration override', () => { + expect(fresh(1, 5).duration).toBe(5) + }) + + it('starts the survivor at the arena center', () => { + expect(fresh().survivor.pos).toEqual(CENTER) + }) +}) + +describe('xp and upgrades', () => { + it('thresholds grow linearly', () => { + expect(xpForLevel(1)).toBe(20) + expect(xpForLevel(3)).toBe(50) + }) + + it('grantXp below threshold only accumulates', () => { + const s = fresh() + grantXp(s, 10) + expect(s.survivor.level).toBe(1) + expect(s.survivor.xp).toBe(10) + expect(s.upgrades).toHaveLength(0) + }) + + it('grantXp can jump multiple levels and logs one upgrade per level', () => { + const s = fresh() + grantXp(s, 60) + expect(s.survivor.level).toBe(3) + expect(s.survivor.xp).toBe(5) + expect(s.upgrades).toHaveLength(2) + }) + + it('each upgrade mutates the survivor as designed', () => { + const s = fresh().survivor + applyUpgrade(s, 'damage') + expect(s.damage).toBe(11) + applyUpgrade(s, 'speed') + expect(s.speed).toBe(164) + applyUpgrade(s, 'vitality') + expect(s.maxHp).toBe(120) + expect(s.hp).toBe(120) + applyUpgrade(s, 'regen') + expect(s.regen).toBeCloseTo(0.6) + applyUpgrade(s, 'multishot') + expect(s.multishot).toBe(2) + applyUpgrade(s, 'fireRate') + expect(s.fireCooldown).toBeCloseTo(0.55 * 0.88) + }) + + it('fire rate floors at 0.15', () => { + const s = fresh().survivor + for (let i = 0; i < 30; i += 1) { + applyUpgrade(s, 'fireRate') + } + expect(s.fireCooldown).toBe(0.15) + }) +}) + +describe('survivor movement', () => { + it('idles when centered with no threats', () => { + const s = fresh() + step(s, [], 0.1) + expect(s.survivor.pos).toEqual(CENTER) + }) + + it('seeks the center when displaced', () => { + const s = fresh() + s.survivor.pos = { x: 100, y: CENTER.y } + step(s, [], 0.1) + expect(s.survivor.pos.x).toBeGreaterThan(100) + }) + + it('flees from a close enemy', () => { + const s = fresh() + makeEnemy(s, 'rusher', { x: CENTER.x + 20, y: CENTER.y }) + step(s, [], 0.05) + expect(s.survivor.pos.x).toBeLessThan(CENTER.x) + }) + + it('ignores enemies beyond the danger radius when moving', () => { + const s = fresh() + makeEnemy(s, 'rusher', { x: CENTER.x + DANGER_RADIUS + 60, y: CENTER.y }) + step(s, [], 0.01) + expect(s.survivor.pos.x).toBe(CENTER.x) + }) + + it('survives an enemy exactly on top of it without NaN positions', () => { + const s = fresh() + makeEnemy(s, 'rusher', { x: CENTER.x, y: CENTER.y }) + step(s, [], 0.01) + expect(Number.isFinite(s.survivor.pos.x)).toBe(true) + expect(Number.isFinite(s.survivor.pos.y)).toBe(true) + }) +}) + +describe('survivor firing', () => { + it('does not fire with no enemies', () => { + const s = fresh() + step(s, [], 0.05) + expect(s.projectiles).toHaveLength(0) + }) + + it('does not fire at enemies beyond range', () => { + const s = fresh() + makeEnemy(s, 'tank', { x: CENTER.x + 400, y: CENTER.y }) + step(s, [], 0.01) + expect(s.projectiles).toHaveLength(0) + }) + + it('fires at an enemy in range and honors the cooldown', () => { + const s = fresh() + makeEnemy(s, 'tank', { x: CENTER.x + 200, y: CENTER.y }) + step(s, [], 0.01) + expect(s.projectiles).toHaveLength(1) + step(s, [], 0.01) + expect(s.projectiles).toHaveLength(1) + }) + + it('aims at the nearest of several enemies', () => { + const s = fresh() + makeEnemy(s, 'tank', { x: CENTER.x - 280, y: CENTER.y }) + const near = makeEnemy(s, 'tank', { x: CENTER.x + 200, y: CENTER.y }) + step(s, [], 0.001) + const p = s.projectiles[0] + expect(p?.vel.x).toBeGreaterThan(0) + expect(near.hp).toBe(120) + }) + + it('multishot fires a spread', () => { + const s = fresh() + s.survivor.multishot = 3 + makeEnemy(s, 'tank', { x: CENTER.x + 200, y: CENTER.y }) + step(s, [], 0.001) + expect(s.projectiles).toHaveLength(3) + }) +}) + +describe('enemy behaviors', () => { + it('chasers close the distance', () => { + const s = fresh() + const e = makeEnemy(s, 'rusher', { x: CENTER.x + 300, y: CENTER.y }) + step(s, [], 0.1) + expect(e.pos.x).toBeLessThan(CENTER.x + 300) + }) + + it('melee contact damages the survivor once per cooldown window', () => { + const s = fresh() + makeEnemy(s, 'rusher', { x: CENTER.x, y: CENTER.y }) + step(s, [], 0.01) + expect(s.survivor.hp).toBe(100 - 6) + step(s, [], 0.01) + expect(s.survivor.hp).toBe(100 - 6) + }) + + it('tank hits harder', () => { + const s = fresh() + makeEnemy(s, 'tank', { x: CENTER.x + 5, y: CENTER.y }) + step(s, [], 0.01) + expect(s.survivor.hp).toBe(100 - 14) + }) + + it('stalker approaches when too far', () => { + const s = fresh() + const e = makeEnemy(s, 'stalker', { x: CENTER.x + 300, y: CENTER.y }) + step(s, [], 0.05) + expect(e.pos.x).toBeLessThan(CENTER.x + 300) + }) + + it('stalker retreats when too close', () => { + const s = fresh() + const e = makeEnemy(s, 'stalker', { x: CENTER.x + 100, y: CENTER.y }) + step(s, [], 0.05) + expect(e.pos.x).toBeGreaterThan(CENTER.x + 100) + }) + + it('stalker holds position inside its comfort band', () => { + const s = fresh() + const e = makeEnemy(s, 'stalker', { x: CENTER.x + 160, y: CENTER.y }) + const before = e.pos.x + step(s, [], 0.05) + expect(e.pos.x).toBe(before) + }) + + it('stalker fires when in range and ready, then cools down', () => { + const s = fresh() + makeEnemy(s, 'stalker', { x: CENTER.x + 220, y: CENTER.y }) + step(s, [], 0.01) + expect(s.projectiles.filter((p) => p.from === 'enemy')).toHaveLength(1) + step(s, [], 0.01) + expect(s.projectiles.filter((p) => p.from === 'enemy')).toHaveLength(1) + }) + + it('stalker does not fire from beyond its range', () => { + const s = fresh() + makeEnemy(s, 'stalker', { x: CENTER.x + 400, y: CENTER.y }) + step(s, [], 0.01) + expect(s.projectiles.filter((p) => p.from === 'enemy')).toHaveLength(0) + }) + + it('hivemind emits a rusher on its spawn cadence', () => { + const s = fresh() + const hive = makeEnemy(s, 'hivemind', { x: 900, y: 560 }) + step(s, [], 2.5) + expect(s.enemies.filter((e) => e.kind === 'rusher')).toHaveLength(1) + expect(hive.spawnTimer).toBe(2.5) + step(s, [], 0.1) + expect(s.enemies.filter((e) => e.kind === 'rusher')).toHaveLength(1) + }) +}) + +describe('bomber', () => { + it('detonates on contact: damages the survivor, dies, grants nothing', () => { + const s = fresh() + makeEnemy(s, 'bomber', { x: CENTER.x, y: CENTER.y }) + step(s, [], 0.01) + expect(s.survivor.hp).toBe(100 - 22) + expect(s.enemies).toHaveLength(0) + expect(s.survivor.kills).toBe(0) + expect(s.survivor.xp).toBe(0) + }) + + it('sniped from afar: grants xp, blast misses the survivor', () => { + const s = fresh() + makeEnemy(s, 'bomber', { x: CENTER.x + 300, y: CENTER.y }) + shot(s, CENTER.x + 300, CENTER.y, 99) + step(s, [], 0.001) + expect(s.survivor.kills).toBe(1) + expect(s.survivor.xp).toBe(10) + expect(s.survivor.hp).toBe(100) + expect(s.enemies).toHaveLength(0) + }) + + it('shot inside blast range: grants xp and still burns the survivor', () => { + const s = fresh() + makeEnemy(s, 'bomber', { x: CENTER.x + 30, y: CENTER.y }) + shot(s, CENTER.x + 30, CENTER.y, 99) + step(s, [], 0.001) + expect(s.survivor.kills).toBe(1) + expect(s.survivor.hp).toBeCloseTo(100 - 22, 5) + }) +}) + +describe('projectiles', () => { + it('expire by ttl', () => { + const s = fresh() + shot(s, 50, 50, 1) + step(s, [], 2) + expect(s.projectiles).toHaveLength(0) + }) + + it('a miss keeps flying', () => { + const s = fresh() + shot(s, 50, 50, 1) + step(s, [], 0.01) + expect(s.projectiles).toHaveLength(1) + }) + + it('damages without killing when the target outlasts the hit', () => { + const s = fresh() + const tank = makeEnemy(s, 'tank', { x: CENTER.x + 400, y: CENTER.y }) + shot(s, CENTER.x + 400, CENTER.y, 8) + step(s, [], 0.001) + expect(tank.hp).toBe(112) + expect(s.survivor.kills).toBe(0) + expect(s.enemies).toHaveLength(1) + }) + + it('a second projectile in the same tick passes through an already-dead target', () => { + const s = fresh() + makeEnemy(s, 'rusher', { x: CENTER.x + 400, y: CENTER.y }) + shot(s, CENTER.x + 400, CENTER.y, 20) + shot(s, CENTER.x + 400, CENTER.y, 20) + step(s, [], 0.001) + expect(s.survivor.kills).toBe(1) + expect(s.projectiles).toHaveLength(1) + }) + + it('enemy fire hurts the survivor', () => { + const s = fresh() + s.projectiles.push({ + id: 500, from: 'enemy', pos: { x: CENTER.x, y: CENTER.y }, + vel: { x: 0, y: 0 }, radius: 5, damage: 7, ttl: 1, + }) + step(s, [], 0.001) + expect(s.survivor.hp).toBe(93) + expect(s.projectiles).toHaveLength(0) + }) + + it('enemy fire that misses keeps flying', () => { + const s = fresh() + s.projectiles.push({ + id: 501, from: 'enemy', pos: { x: 30, y: 30 }, + vel: { x: 0, y: 0 }, radius: 5, damage: 7, ttl: 1, + }) + step(s, [], 0.001) + expect(s.survivor.hp).toBe(100) + expect(s.projectiles).toHaveLength(1) + }) +}) + +describe('game end', () => { + it('director wins when the survivor drops to zero', () => { + const s = fresh() + s.survivor.hp = 5 + makeEnemy(s, 'rusher', { x: CENTER.x, y: CENTER.y }) + step(s, [], 0.01) + expect(s.status).toBe('directorWon') + expect(s.outcomeTime).toBeCloseTo(0.01) + }) + + it('survivor wins when the clock runs out', () => { + const s = fresh(1, 0.05) + step(s, [], 0.1) + expect(s.status).toBe('survivorWon') + }) + + it('a kill on the final tick still counts as a director win', () => { + const s = fresh(1, 0.05) + s.survivor.hp = 5 + makeEnemy(s, 'rusher', { x: CENTER.x, y: CENTER.y }) + step(s, [], 0.1) + expect(s.status).toBe('directorWon') + }) + + it('a finished game is inert', () => { + const s = fresh(1, 0.05) + step(s, [], 0.1) + const t = s.t + step(s, [], 0.1) + expect(s.t).toBe(t) + }) + + it('director actions flow through step', () => { + const s = fresh() + step(s, [{ type: 'spawn', kind: 'rusher' }], 0.001) + expect(s.enemies).toHaveLength(1) + expect(s.director.energy).toBeLessThan(30) + }) +}) diff --git a/reverse_survivors/src/core/sim.ts b/reverse_survivors/src/core/sim.ts new file mode 100644 index 00000000..8fb715cf --- /dev/null +++ b/reverse_survivors/src/core/sim.ts @@ -0,0 +1,296 @@ +import { applyAction, tickDirector } from './director' +import { createRng, pick } from './rng' +import type { + DirectorAction, Enemy, EnemyKind, GameState, Survivor, UpgradeId, +} from './types' +import { + ARENA, CONTACT_COOLDOWN, ENERGY_START, ENEMY_SPECS, GAME_DURATION, UPGRADE_POOL, +} from './types' +import type { Vec } from './vec' +import { clamp, dist, toward } from './vec' + +export const SURVIVOR_RADIUS = 12 +export const DANGER_RADIUS = 140 +export const FIRE_RANGE = 260 +export const PROJ_SPEED = 420 +const PROJ_TTL = 1.5 +const ENEMY_PROJ_TTL = 2 +const CENTER: Vec = { x: ARENA.w / 2, y: ARENA.h / 2 } + +export const xpForLevel = (level: number): number => 20 + 15 * (level - 1) + +const createSurvivor = (): Survivor => ({ + pos: { x: CENTER.x, y: CENTER.y }, + hp: 100, + maxHp: 100, + speed: 150, + regen: 0, + damage: 8, + multishot: 1, + fireCooldown: 0.55, + fireTimer: 0, + level: 1, + xp: 0, + xpNext: xpForLevel(1), + kills: 0, +}) + +export const createInitialState = (seed: number, duration = GAME_DURATION): GameState => ({ + status: 'running', + t: 0, + duration, + rng: createRng(seed), + survivor: createSurvivor(), + enemies: [], + projectiles: [], + director: { + energy: ENERGY_START, + waveIndex: 0, + bossCooldowns: { colossus: 0, hivemind: 0 }, + }, + upgrades: [], + nextId: 1, + outcomeTime: 0, +}) + +export const makeEnemy = (state: GameState, kind: EnemyKind, pos: Vec): Enemy => { + const id = state.nextId + state.nextId += 1 + const enemy: Enemy = { + id, + kind, + pos: { x: pos.x, y: pos.y }, + hp: ENEMY_SPECS[kind].hp, + hitTimer: 0, + fireTimer: 0, + spawnTimer: ENEMY_SPECS[kind].spawnEvery, + } + state.enemies.push(enemy) + return enemy +} + +const APPLY_UPGRADE: Record void> = { + damage: (sv) => { sv.damage += 3 }, + fireRate: (sv) => { sv.fireCooldown = Math.max(0.15, sv.fireCooldown * 0.88) }, + speed: (sv) => { sv.speed += 14 }, + vitality: (sv) => { sv.maxHp += 20; sv.hp += 20 }, + regen: (sv) => { sv.regen += 0.6 }, + multishot: (sv) => { sv.multishot += 1 }, +} + +export const applyUpgrade = (sv: Survivor, id: UpgradeId): void => { + APPLY_UPGRADE[id](sv) +} + +export const grantXp = (state: GameState, amount: number): void => { + const sv = state.survivor + sv.xp += amount + while (sv.xp >= sv.xpNext) { + sv.xp -= sv.xpNext + sv.level += 1 + sv.xpNext = xpForLevel(sv.level) + const upgrade = pick(state.rng, UPGRADE_POOL) + applyUpgrade(sv, upgrade) + state.upgrades.push(upgrade) + } +} + +const nearestEnemy = (state: GameState): Enemy | null => { + let best: Enemy | null = null + let bestD = Infinity + for (const e of state.enemies) { + const d = dist(e.pos, state.survivor.pos) + if (d < bestD) { + bestD = d + best = e + } + } + return best +} + +const survivorStep = (state: GameState, dt: number): void => { + const sv = state.survivor + let fleeX = 0 + let fleeY = 0 + for (const e of state.enemies) { + const d = dist(e.pos, sv.pos) + if (d < DANGER_RADIUS && d > 0) { + const weight = (DANGER_RADIUS - d) / DANGER_RADIUS + fleeX += ((sv.pos.x - e.pos.x) / d) * weight + fleeY += ((sv.pos.y - e.pos.y) / d) * weight + } + } + const fleeMag = Math.hypot(fleeX, fleeY) + let vel: Vec = { x: 0, y: 0 } + if (fleeMag > 0) { + vel = { x: (fleeX / fleeMag) * sv.speed, y: (fleeY / fleeMag) * sv.speed } + } else if (dist(sv.pos, CENTER) > 4) { + vel = toward(sv.pos, CENTER, sv.speed * 0.4) + } + sv.pos.x = clamp(sv.pos.x + vel.x * dt, SURVIVOR_RADIUS, ARENA.w - SURVIVOR_RADIUS) + sv.pos.y = clamp(sv.pos.y + vel.y * dt, SURVIVOR_RADIUS, ARENA.h - SURVIVOR_RADIUS) + + sv.hp = Math.min(sv.maxHp, sv.hp + sv.regen * dt) + + sv.fireTimer = Math.max(0, sv.fireTimer - dt) + const target = nearestEnemy(state) + if (target !== null && sv.fireTimer === 0 && dist(target.pos, sv.pos) <= FIRE_RANGE) { + const baseAngle = Math.atan2(target.pos.y - sv.pos.y, target.pos.x - sv.pos.x) + for (let i = 0; i < sv.multishot; i += 1) { + const angle = baseAngle + (i - (sv.multishot - 1) / 2) * 0.12 + const id = state.nextId + state.nextId += 1 + state.projectiles.push({ + id, + from: 'survivor', + pos: { x: sv.pos.x, y: sv.pos.y }, + vel: { x: Math.cos(angle) * PROJ_SPEED, y: Math.sin(angle) * PROJ_SPEED }, + radius: 4, + damage: sv.damage, + ttl: PROJ_TTL, + }) + } + sv.fireTimer = sv.fireCooldown + } +} + +const detonate = (state: GameState, enemy: Enemy): void => { + const spec = ENEMY_SPECS[enemy.kind] + enemy.hp = 0 + if (dist(enemy.pos, state.survivor.pos) <= spec.blastRadius) { + state.survivor.hp -= spec.blastDamage + } +} + +const killByShot = (state: GameState, enemy: Enemy): void => { + const spec = ENEMY_SPECS[enemy.kind] + state.survivor.kills += 1 + grantXp(state, spec.xp) + if (spec.detonates) { + detonate(state, enemy) + } +} + +const MOVE_FNS: Record<'chase' | 'skirmish', (e: Enemy, sv: Survivor, d: number) => Vec> = { + chase: (e, sv) => toward(e.pos, sv.pos, ENEMY_SPECS[e.kind].speed), + skirmish: (e, sv, d) => { + const spec = ENEMY_SPECS[e.kind] + if (d > spec.keepDistance) { + return toward(e.pos, sv.pos, spec.speed) + } + if (d < spec.keepDistance * 0.65) { + return toward(sv.pos, e.pos, spec.speed) + } + return { x: 0, y: 0 } + }, +} + +const enemiesStep = (state: GameState, dt: number): void => { + const sv = state.survivor + const spawned: { kind: EnemyKind; pos: Vec }[] = [] + for (const e of state.enemies) { + const spec = ENEMY_SPECS[e.kind] + e.hitTimer = Math.max(0, e.hitTimer - dt) + const d = dist(e.pos, sv.pos) + const vel = MOVE_FNS[spec.move](e, sv, d) + e.pos.x = clamp(e.pos.x + vel.x * dt, spec.radius, ARENA.w - spec.radius) + e.pos.y = clamp(e.pos.y + vel.y * dt, spec.radius, ARENA.h - spec.radius) + + if (dist(e.pos, sv.pos) <= spec.radius + SURVIVOR_RADIUS) { + if (spec.detonates) { + detonate(state, e) + } else if (e.hitTimer === 0) { + sv.hp -= spec.contactDamage + e.hitTimer = CONTACT_COOLDOWN + } + } + + if (spec.projDamage > 0) { + e.fireTimer = Math.max(0, e.fireTimer - dt) + if (e.fireTimer === 0 && d <= spec.fireRange) { + const id = state.nextId + state.nextId += 1 + state.projectiles.push({ + id, + from: 'enemy', + pos: { x: e.pos.x, y: e.pos.y }, + vel: toward(e.pos, sv.pos, spec.projSpeed), + radius: 5, + damage: spec.projDamage, + ttl: ENEMY_PROJ_TTL, + }) + e.fireTimer = spec.fireCooldown + } + } + + if (spec.spawnEvery > 0) { + e.spawnTimer = Math.max(0, e.spawnTimer - dt) + if (e.spawnTimer === 0) { + spawned.push({ kind: 'rusher', pos: { x: e.pos.x, y: e.pos.y } }) + e.spawnTimer = spec.spawnEvery + } + } + } + for (const s of spawned) { + makeEnemy(state, s.kind, s.pos) + } +} + +const findHit = (enemies: Enemy[], pos: Vec, radius: number): Enemy | null => { + for (const e of enemies) { + if (e.hp > 0 && dist(e.pos, pos) <= ENEMY_SPECS[e.kind].radius + radius) { + return e + } + } + return null +} + +const projectilesStep = (state: GameState, dt: number): void => { + const sv = state.survivor + const surviving: typeof state.projectiles = [] + for (const p of state.projectiles) { + p.pos.x += p.vel.x * dt + p.pos.y += p.vel.y * dt + p.ttl -= dt + if (p.ttl <= 0) { + continue + } + if (p.from === 'survivor') { + const hit = findHit(state.enemies, p.pos, p.radius) + if (hit !== null) { + hit.hp -= p.damage + if (hit.hp <= 0) { + killByShot(state, hit) + } + continue + } + } else if (dist(p.pos, sv.pos) <= p.radius + SURVIVOR_RADIUS) { + sv.hp -= p.damage + continue + } + surviving.push(p) + } + state.projectiles = surviving +} + +export const step = (state: GameState, actions: readonly DirectorAction[], dt: number): void => { + if (state.status !== 'running') { + return + } + state.t += dt + tickDirector(state, dt) + for (const action of actions) { + applyAction(state, action) + } + survivorStep(state, dt) + enemiesStep(state, dt) + projectilesStep(state, dt) + state.enemies = state.enemies.filter((e) => e.hp > 0) + if (state.survivor.hp <= 0) { + state.status = 'directorWon' + state.outcomeTime = state.t + } else if (state.t >= state.duration) { + state.status = 'survivorWon' + state.outcomeTime = state.t + } +} diff --git a/reverse_survivors/src/core/snapshot.ts b/reverse_survivors/src/core/snapshot.ts new file mode 100644 index 00000000..66c71e11 --- /dev/null +++ b/reverse_survivors/src/core/snapshot.ts @@ -0,0 +1,90 @@ +import { waveCost } from './director' +import type { BossKind, GameState, Status, UnitKind, UpgradeId } from './types' +import { + BOSS_ORDER, BOSS_UNLOCK_AT, ENERGY_CAP, ENEMY_SPECS, UNIT_ORDER, +} from './types' + +export type BossPhase = 'locked' | 'cooling' | 'ready' + +export interface UnitButton { + kind: UnitKind + name: string + cost: number + affordable: boolean +} + +export interface BossButton { + kind: BossKind + name: string + cost: number + phase: BossPhase + unlockIn: number + cooldown: number + affordable: boolean +} + +export interface UpgradeEntry { + n: number + id: UpgradeId +} + +export interface HudSnapshot { + status: Status + t: number + duration: number + energy: number + energyCap: number + waveIndex: number + waveCost: number + waveAffordable: boolean + survivorHp: number + survivorMaxHp: number + survivorLevel: number + kills: number + upgrades: UpgradeEntry[] + units: UnitButton[] + bosses: BossButton[] + outcomeTime: number +} + +const bossPhase = (state: GameState, kind: BossKind): BossPhase => { + if (state.t < BOSS_UNLOCK_AT[kind]) { + return 'locked' + } + if (state.director.bossCooldowns[kind] > 0) { + return 'cooling' + } + return 'ready' +} + +export const snapshotOf = (state: GameState): HudSnapshot => ({ + status: state.status, + t: state.t, + duration: state.duration, + energy: state.director.energy, + energyCap: ENERGY_CAP, + waveIndex: state.director.waveIndex, + waveCost: waveCost(state.director.waveIndex), + waveAffordable: state.director.energy >= waveCost(state.director.waveIndex), + survivorHp: Math.max(0, state.survivor.hp), + survivorMaxHp: state.survivor.maxHp, + survivorLevel: state.survivor.level, + kills: state.survivor.kills, + upgrades: state.upgrades.map((id, i) => ({ n: i + 1, id })).slice(-3), + units: UNIT_ORDER.map((kind) => ({ + kind, + name: ENEMY_SPECS[kind].name, + cost: ENEMY_SPECS[kind].cost, + affordable: state.director.energy >= ENEMY_SPECS[kind].cost, + })), + bosses: BOSS_ORDER.map((kind) => ({ + kind, + name: ENEMY_SPECS[kind].name, + cost: ENEMY_SPECS[kind].cost, + phase: bossPhase(state, kind), + unlockIn: Math.max(0, Math.ceil(BOSS_UNLOCK_AT[kind] - state.t)), + cooldown: Math.ceil(state.director.bossCooldowns[kind]), + affordable: state.director.energy >= ENEMY_SPECS[kind].cost, + })), + outcomeTime: state.outcomeTime, +}) diff --git a/reverse_survivors/src/core/types.ts b/reverse_survivors/src/core/types.ts new file mode 100644 index 00000000..f458a73d --- /dev/null +++ b/reverse_survivors/src/core/types.ts @@ -0,0 +1,150 @@ +import type { Rng } from './rng' +import type { Vec } from './vec' + +export const ARENA = { w: 960, h: 600 } as const +export const GAME_DURATION = 300 +export const CONTACT_COOLDOWN = 0.5 +export const ENERGY_CAP = 400 +export const ENERGY_START = 30 + +export type UnitKind = 'rusher' | 'stalker' | 'tank' | 'bomber' +export type BossKind = 'colossus' | 'hivemind' +export type EnemyKind = UnitKind | BossKind +export type MoveMode = 'chase' | 'skirmish' + +export interface EnemySpec { + readonly name: string + readonly cost: number + readonly hp: number + readonly speed: number + readonly radius: number + readonly contactDamage: number + readonly xp: number + readonly move: MoveMode + /** 0 disables ranged fire. */ + readonly projDamage: number + readonly projSpeed: number + readonly fireRange: number + readonly fireCooldown: number + readonly keepDistance: number + /** True: dies on contact and blasts. */ + readonly detonates: boolean + readonly blastRadius: number + readonly blastDamage: number + /** 0 disables minion spawning. */ + readonly spawnEvery: number +} + +const spec = (s: EnemySpec): EnemySpec => s + +export const ENEMY_SPECS: Record = { + rusher: spec({ + name: 'Rusher', cost: 10, hp: 14, speed: 170, radius: 9, + contactDamage: 6, xp: 4, move: 'chase', + projDamage: 0, projSpeed: 0, fireRange: 0, fireCooldown: 0, keepDistance: 0, + detonates: false, blastRadius: 0, blastDamage: 0, spawnEvery: 0, + }), + stalker: spec({ + name: 'Stalker', cost: 24, hp: 20, speed: 110, radius: 10, + contactDamage: 4, xp: 8, move: 'skirmish', + projDamage: 7, projSpeed: 260, fireRange: 250, fireCooldown: 1.6, keepDistance: 200, + detonates: false, blastRadius: 0, blastDamage: 0, spawnEvery: 0, + }), + tank: spec({ + name: 'Tank', cost: 45, hp: 120, speed: 55, radius: 18, + contactDamage: 14, xp: 20, move: 'chase', + projDamage: 0, projSpeed: 0, fireRange: 0, fireCooldown: 0, keepDistance: 0, + detonates: false, blastRadius: 0, blastDamage: 0, spawnEvery: 0, + }), + bomber: spec({ + name: 'Bomber', cost: 30, hp: 16, speed: 140, radius: 10, + contactDamage: 0, xp: 10, move: 'chase', + projDamage: 0, projSpeed: 0, fireRange: 0, fireCooldown: 0, keepDistance: 0, + detonates: true, blastRadius: 70, blastDamage: 22, spawnEvery: 0, + }), + colossus: spec({ + name: 'Colossus', cost: 150, hp: 700, speed: 40, radius: 34, + contactDamage: 30, xp: 80, move: 'chase', + projDamage: 0, projSpeed: 0, fireRange: 0, fireCooldown: 0, keepDistance: 0, + detonates: false, blastRadius: 0, blastDamage: 0, spawnEvery: 0, + }), + hivemind: spec({ + name: 'Hivemind', cost: 220, hp: 420, speed: 60, radius: 28, + contactDamage: 10, xp: 100, move: 'chase', + projDamage: 0, projSpeed: 0, fireRange: 0, fireCooldown: 0, keepDistance: 0, + detonates: false, blastRadius: 0, blastDamage: 0, spawnEvery: 2.5, + }), +} as const + +export const UNIT_ORDER: readonly [UnitKind, ...UnitKind[]] = ['rusher', 'stalker', 'tank', 'bomber'] +export const BOSS_ORDER: readonly [BossKind, ...BossKind[]] = ['colossus', 'hivemind'] +export const BOSS_UNLOCK_AT: Record = { colossus: 60, hivemind: 120 } +export const BOSS_COOLDOWN: Record = { colossus: 25, hivemind: 30 } + +export type UpgradeId = 'damage' | 'fireRate' | 'speed' | 'vitality' | 'regen' | 'multishot' +export const UPGRADE_POOL: readonly [UpgradeId, ...UpgradeId[]] = [ + 'damage', 'fireRate', 'speed', 'vitality', 'regen', 'multishot', +] + +export interface Survivor { + pos: Vec + hp: number + maxHp: number + speed: number + regen: number + damage: number + multishot: number + fireCooldown: number + fireTimer: number + level: number + xp: number + xpNext: number + kills: number +} + +export interface Enemy { + id: number + kind: EnemyKind + pos: Vec + hp: number + hitTimer: number + fireTimer: number + spawnTimer: number +} + +export interface Projectile { + id: number + from: 'survivor' | 'enemy' + pos: Vec + vel: Vec + radius: number + damage: number + ttl: number +} + +export interface DirectorState { + energy: number + waveIndex: number + bossCooldowns: Record +} + +export type DirectorAction = + | { type: 'spawn'; kind: UnitKind } + | { type: 'wave' } + | { type: 'boss'; kind: BossKind } + +export type Status = 'running' | 'directorWon' | 'survivorWon' + +export interface GameState { + status: Status + t: number + duration: number + rng: Rng + survivor: Survivor + enemies: Enemy[] + projectiles: Projectile[] + director: DirectorState + upgrades: UpgradeId[] + nextId: number + outcomeTime: number +} diff --git a/reverse_survivors/src/core/vec-rng.test.ts b/reverse_survivors/src/core/vec-rng.test.ts new file mode 100644 index 00000000..bc5ad6ba --- /dev/null +++ b/reverse_survivors/src/core/vec-rng.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from 'vitest' +import { createRng, nextFloat, nextInt, pick } from './rng' +import { clamp, dist, toward } from './vec' + +describe('vec', () => { + it('measures distance', () => { + expect(dist({ x: 0, y: 0 }, { x: 3, y: 4 })).toBe(5) + }) + + it('builds a velocity toward a target with the requested speed', () => { + const v = toward({ x: 0, y: 0 }, { x: 10, y: 0 }, 50) + expect(v).toEqual({ x: 50, y: 0 }) + }) + + it('returns zero velocity for coincident points', () => { + expect(toward({ x: 5, y: 5 }, { x: 5, y: 5 }, 99)).toEqual({ x: 0, y: 0 }) + }) + + it('clamps below, inside, and above the range', () => { + expect(clamp(-1, 0, 10)).toBe(0) + expect(clamp(4, 0, 10)).toBe(4) + expect(clamp(11, 0, 10)).toBe(10) + }) +}) + +describe('rng', () => { + it('is deterministic for a given seed', () => { + const a = createRng(123) + const b = createRng(123) + expect([nextFloat(a), nextFloat(a), nextFloat(a)]) + .toEqual([nextFloat(b), nextFloat(b), nextFloat(b)]) + }) + + it('produces different sequences for different seeds', () => { + expect(nextFloat(createRng(1))).not.toBe(nextFloat(createRng(2))) + }) + + it('stays within [0, 1)', () => { + const rng = createRng(77) + for (let i = 0; i < 200; i += 1) { + const v = nextFloat(rng) + expect(v).toBeGreaterThanOrEqual(0) + expect(v).toBeLessThan(1) + } + }) + + it('wraps negative seeds into unsigned space', () => { + expect(createRng(-1).s).toBe(0xffffffff) + }) + + it('nextInt stays within [0, max)', () => { + const rng = createRng(9) + for (let i = 0; i < 100; i += 1) { + const v = nextInt(rng, 4) + expect(v).toBeGreaterThanOrEqual(0) + expect(v).toBeLessThan(4) + } + }) + + it('pick eventually returns every element of a tuple', () => { + const rng = createRng(5) + const seen = new Set() + for (let i = 0; i < 64; i += 1) { + seen.add(pick(rng, ['a', 'b', 'c'] as const)) + } + expect(seen).toEqual(new Set(['a', 'b', 'c'])) + }) + + it('pick is deterministic', () => { + expect(pick(createRng(42), [10, 20, 30] as const)) + .toBe(pick(createRng(42), [10, 20, 30] as const)) + }) +}) diff --git a/reverse_survivors/src/core/vec.ts b/reverse_survivors/src/core/vec.ts new file mode 100644 index 00000000..a56c87cf --- /dev/null +++ b/reverse_survivors/src/core/vec.ts @@ -0,0 +1,18 @@ +export interface Vec { + x: number + y: number +} + +export const dist = (a: Vec, b: Vec): number => Math.hypot(a.x - b.x, a.y - b.y) + +/** Velocity vector of magnitude `speed` pointing from `from` to `to`. Zero when coincident. */ +export const toward = (from: Vec, to: Vec, speed: number): Vec => { + const d = dist(from, to) + if (d === 0) { + return { x: 0, y: 0 } + } + return { x: ((to.x - from.x) / d) * speed, y: ((to.y - from.y) / d) * speed } +} + +export const clamp = (v: number, min: number, max: number): number => + Math.min(Math.max(v, min), max) diff --git a/reverse_survivors/src/main.test.tsx b/reverse_survivors/src/main.test.tsx new file mode 100644 index 00000000..332ffa1e --- /dev/null +++ b/reverse_survivors/src/main.test.tsx @@ -0,0 +1,27 @@ +import { act } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +beforeEach(() => { + vi.resetModules() + document.body.innerHTML = '' + vi.stubGlobal('requestAnimationFrame', (): number => 0) + vi.stubGlobal('cancelAnimationFrame', (): void => undefined) +}) + +afterEach(() => { + vi.unstubAllGlobals() +}) + +describe('main', () => { + it('refuses to boot without a #root element', async () => { + await expect(import('./main')).rejects.toThrow('missing #root') + }) + + it('mounts the app into #root', async () => { + document.body.innerHTML = '
' + await act(async () => { + await import('./main') + }) + expect(document.querySelector('h1')?.textContent).toBe('Reverse Survivors') + }) +}) diff --git a/reverse_survivors/src/main.tsx b/reverse_survivors/src/main.tsx new file mode 100644 index 00000000..95439e01 --- /dev/null +++ b/reverse_survivors/src/main.tsx @@ -0,0 +1,14 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { App } from './App' +import './styles.css' + +const rootEl = document.getElementById('root') +if (rootEl === null) { + throw new Error('missing #root element') +} +createRoot(rootEl).render( + + + , +) diff --git a/reverse_survivors/src/render/draw.test.ts b/reverse_survivors/src/render/draw.test.ts new file mode 100644 index 00000000..13721345 --- /dev/null +++ b/reverse_survivors/src/render/draw.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest' +import { createInitialState, makeEnemy } from '../core/sim' +import type { GameState } from '../core/types' +import { makeCtxStub } from '../test/harness' +import { draw } from './draw' + +const fresh = (): GameState => createInitialState(1) + +// Empty state baseline: ritual circle + survivor halo + body + hp ring. +const BASE = { beginPath: 4, arc: 4, fill: 2, stroke: 2, fillRect: 1 } + +describe('draw', () => { + it('paints the empty arena baseline', () => { + const ctx = makeCtxStub() + draw(ctx, fresh()) + expect(ctx.fillRect).toHaveBeenCalledTimes(BASE.fillRect) + expect(ctx.beginPath).toHaveBeenCalledTimes(BASE.beginPath) + expect(ctx.arc).toHaveBeenCalledTimes(BASE.arc) + expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill) + expect(ctx.stroke).toHaveBeenCalledTimes(BASE.stroke) + }) + + it('paints projectiles from both sides', () => { + const ctx = makeCtxStub() + const s = fresh() + s.projectiles.push( + { id: 1, from: 'survivor', pos: { x: 10, y: 10 }, vel: { x: 0, y: 0 }, radius: 4, damage: 1, ttl: 1 }, + { id: 2, from: 'enemy', pos: { x: 20, y: 20 }, vel: { x: 0, y: 0 }, radius: 5, damage: 1, ttl: 1 }, + ) + draw(ctx, s) + expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill + 2) + expect(ctx.arc).toHaveBeenCalledTimes(BASE.arc + 2) + }) + + it('a fresh grunt gets a body but no ring and no hp arc', () => { + const ctx = makeCtxStub() + const s = fresh() + makeEnemy(s, 'rusher', { x: 100, y: 100 }) + draw(ctx, s) + expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill + 1) + expect(ctx.stroke).toHaveBeenCalledTimes(BASE.stroke) + }) + + it('a damaged enemy shows an hp arc', () => { + const ctx = makeCtxStub() + const s = fresh() + const tank = makeEnemy(s, 'tank', { x: 100, y: 100 }) + tank.hp = 60 + draw(ctx, s) + expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill + 1) + expect(ctx.stroke).toHaveBeenCalledTimes(BASE.stroke + 1) + }) + + it('bosses wear a blood ring', () => { + const ctx = makeCtxStub() + const s = fresh() + makeEnemy(s, 'colossus', { x: 100, y: 100 }) + draw(ctx, s) + expect(ctx.fill).toHaveBeenCalledTimes(BASE.fill + 1) + expect(ctx.stroke).toHaveBeenCalledTimes(BASE.stroke + 1) + expect(ctx.arc).toHaveBeenCalledTimes(BASE.arc + 2) + }) +}) diff --git a/reverse_survivors/src/render/draw.ts b/reverse_survivors/src/render/draw.ts new file mode 100644 index 00000000..d26ed009 --- /dev/null +++ b/reverse_survivors/src/render/draw.ts @@ -0,0 +1,107 @@ +import { FIRE_RANGE, SURVIVOR_RADIUS } from '../core/sim' +import type { EnemyKind, GameState } from '../core/types' +import { ARENA, ENEMY_SPECS } from '../core/types' + +/** Structural subset of CanvasRenderingContext2D — stubbable in tests. */ +export interface Ctx2D { + fillStyle: string | CanvasGradient | CanvasPattern + strokeStyle: string | CanvasGradient | CanvasPattern + lineWidth: number + globalAlpha: number + fillRect: (x: number, y: number, w: number, h: number) => void + beginPath: () => void + arc: (x: number, y: number, r: number, a0: number, a1: number) => void + fill: () => void + stroke: () => void +} + +const COLORS = { + void: '#140b1c', + ring: '#2b1a3a', + bone: '#e8dcc8', + holy: '#7ce0c3', + holyDim: '#2e5c50', + blood: '#c94f7c', + arcane: '#b06ce8', +} as const + +const ENEMY_COLORS: Record = { + rusher: '#c94f7c', + stalker: '#b06ce8', + tank: '#8a4b3c', + bomber: '#e0a13d', + colossus: '#7d2c50', + hivemind: '#5a3d8a', +} + +const IS_BOSS: Record = { + rusher: false, + stalker: false, + tank: false, + bomber: false, + colossus: true, + hivemind: true, +} + +const circle = (ctx: Ctx2D, x: number, y: number, r: number): void => { + ctx.beginPath() + ctx.arc(x, y, r, 0, Math.PI * 2) +} + +export const draw = (ctx: Ctx2D, state: GameState): void => { + ctx.globalAlpha = 1 + ctx.fillStyle = COLORS.void + ctx.fillRect(0, 0, ARENA.w, ARENA.h) + + // Ritual circle: the survivor's consecrated ground. + ctx.globalAlpha = 0.5 + ctx.strokeStyle = COLORS.ring + ctx.lineWidth = 2 + circle(ctx, ARENA.w / 2, ARENA.h / 2, 150) + ctx.stroke() + ctx.globalAlpha = 1 + + for (const p of state.projectiles) { + ctx.fillStyle = p.from === 'survivor' ? COLORS.holy : COLORS.blood + circle(ctx, p.pos.x, p.pos.y, p.radius) + ctx.fill() + } + + for (const e of state.enemies) { + const spec = ENEMY_SPECS[e.kind] + ctx.fillStyle = ENEMY_COLORS[e.kind] + circle(ctx, e.pos.x, e.pos.y, spec.radius) + ctx.fill() + if (IS_BOSS[e.kind]) { + ctx.strokeStyle = COLORS.blood + ctx.lineWidth = 3 + circle(ctx, e.pos.x, e.pos.y, spec.radius + 6) + ctx.stroke() + } + if (e.hp < spec.hp) { + ctx.strokeStyle = COLORS.bone + ctx.lineWidth = 2 + ctx.beginPath() + ctx.arc(e.pos.x, e.pos.y, spec.radius + 3, -Math.PI / 2, -Math.PI / 2 + (e.hp / spec.hp) * Math.PI * 2) + ctx.stroke() + } + } + + const sv = state.survivor + ctx.globalAlpha = 0.12 + ctx.fillStyle = COLORS.holy + circle(ctx, sv.pos.x, sv.pos.y, FIRE_RANGE) + ctx.fill() + ctx.globalAlpha = 1 + ctx.fillStyle = COLORS.holy + circle(ctx, sv.pos.x, sv.pos.y, SURVIVOR_RADIUS) + ctx.fill() + ctx.strokeStyle = COLORS.holyDim + ctx.lineWidth = 3 + ctx.beginPath() + ctx.arc( + sv.pos.x, sv.pos.y, SURVIVOR_RADIUS + 5, + -Math.PI / 2, -Math.PI / 2 + (Math.max(0, sv.hp) / sv.maxHp) * Math.PI * 2, + ) + ctx.stroke() +} diff --git a/reverse_survivors/src/styles.css b/reverse_survivors/src/styles.css new file mode 100644 index 00000000..6550a997 --- /dev/null +++ b/reverse_survivors/src/styles.css @@ -0,0 +1,222 @@ +:root { + --void: #140b1c; + --panel: #1e1229; + --panel-edge: #34204a; + --bone: #e8dcc8; + --bone-dim: #9c8f7a; + --blood: #c94f7c; + --arcane: #b06ce8; + --holy: #7ce0c3; + --font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif; + --font-data: ui-monospace, "Cascadia Mono", "JetBrains Mono", Menlo, monospace; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + background: + radial-gradient(1200px 500px at 50% -10%, #241335 0%, var(--void) 60%); + color: var(--bone); + font-family: var(--font-data); + min-height: 100vh; +} + +.frame { + max-width: 1280px; + margin: 0 auto; + padding: 16px 20px 28px; + display: flex; + flex-direction: column; + gap: 14px; +} + +.masthead { + display: flex; + align-items: baseline; + gap: 16px; + border-bottom: 1px solid var(--panel-edge); + padding-bottom: 10px; +} + +.masthead h1 { + font-family: var(--font-display); + font-size: 1.7rem; + letter-spacing: 0.06em; + margin: 0; + color: var(--bone); +} + +.tagline { + margin: 0; + color: var(--bone-dim); + font-size: 0.8rem; + flex: 1; +} + +.clock { + margin: 0; + font-size: 1.1rem; + color: var(--arcane); +} + +.board { + display: flex; + gap: 14px; + align-items: flex-start; +} + +.stage { + border: 1px solid var(--panel-edge); + border-radius: 6px; + background: var(--void); + width: 100%; + max-width: 960px; + height: auto; + display: block; +} + +.hud { + width: 270px; + min-width: 220px; + display: flex; + flex-direction: column; + gap: 14px; + position: relative; +} + +.hud-block { + background: var(--panel); + border: 1px solid var(--panel-edge); + border-radius: 6px; + padding: 12px 14px; +} + +.hud-label { + font-family: var(--font-display); + font-size: 0.95rem; + letter-spacing: 0.12em; + text-transform: uppercase; + margin: 0 0 8px; + color: var(--bone-dim); +} + +.hud-num { margin: 6px 0 0; font-size: 0.85rem; } + +.phials { display: flex; gap: 5px; } + +.phial { + width: 18px; + height: 26px; + border: 1px solid var(--panel-edge); + border-radius: 3px 3px 8px 8px; + background: transparent; +} + +.phial-full { + background: linear-gradient(180deg, var(--arcane), #6d3aa8); + border-color: var(--arcane); + box-shadow: 0 0 8px rgba(176, 108, 232, 0.45); +} + +.hpbar { + height: 10px; + border: 1px solid var(--panel-edge); + border-radius: 5px; + overflow: hidden; + background: #120b18; +} + +.hpbar-fill { + height: 100%; + background: linear-gradient(90deg, #2e5c50, var(--holy)); + transition: width 120ms linear; +} + +.feed { + list-style: none; + margin: 8px 0 0; + padding: 0; + font-size: 0.75rem; + color: var(--holy); +} + +.feed li::before { content: "▲ "; color: var(--bone-dim); } + +.spawnbar { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.card { + font-family: var(--font-data); + background: var(--panel); + color: var(--bone); + border: 1px solid var(--panel-edge); + border-radius: 6px; + padding: 10px 14px; + display: flex; + flex-direction: column; + gap: 3px; + cursor: pointer; + min-width: 118px; + text-align: left; +} + +.card:hover:not(:disabled) { + border-color: var(--blood); + box-shadow: 0 0 10px rgba(201, 79, 124, 0.35); +} + +.card:focus-visible { + outline: 2px solid var(--arcane); + outline-offset: 2px; +} + +.card:disabled { opacity: 0.42; cursor: not-allowed; } + +.card-name { font-family: var(--font-display); letter-spacing: 0.05em; } + +.card-cost { font-size: 0.72rem; color: var(--blood); } + +.card-boss { border-color: #5c2440; } + +.card-wave { border-color: #3d2f63; } + +.overlay { + background: var(--panel); + border-radius: 6px; + padding: 16px; + text-align: center; +} + +.overlay h2 { font-family: var(--font-display); margin: 0 0 6px; } + +.overlay p { margin: 0 0 12px; font-size: 0.8rem; color: var(--bone-dim); } + +.overlay-win { border: 1px solid var(--blood); } + +.overlay-loss { border: 1px solid var(--holy); } + +.overlay button { + font-family: var(--font-display); + background: var(--blood); + color: var(--void); + border: none; + border-radius: 5px; + padding: 9px 16px; + cursor: pointer; + letter-spacing: 0.04em; +} + +.overlay-loss button { background: var(--holy); } + +@media (prefers-reduced-motion: reduce) { + .hpbar-fill { transition: none; } +} + +@media (max-width: 900px) { + .board { flex-direction: column; } + .hud { width: 100%; } +} diff --git a/reverse_survivors/src/test/harness.ts b/reverse_survivors/src/test/harness.ts new file mode 100644 index 00000000..9dfc76eb --- /dev/null +++ b/reverse_survivors/src/test/harness.ts @@ -0,0 +1,56 @@ +import type { Mock } from 'vitest' +import { vi } from 'vitest' +import type { Ctx2D } from '../render/draw' + +export interface RafHarness { + /** Fire all queued frame callbacks with the given timestamp (ms). */ + pump: (ts: number) => void + pending: () => number + restore: () => void +} + +export const installRaf = (): RafHarness => { + let queue: FrameRequestCallback[] = [] + let nextHandle = 0 + vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback): number => { + queue.push(cb) + nextHandle += 1 + return nextHandle + }) + vi.stubGlobal('cancelAnimationFrame', (): void => { + queue = [] + }) + return { + pump: (ts: number): void => { + const batch = queue + queue = [] + for (const cb of batch) { + cb(ts) + } + }, + pending: (): number => queue.length, + restore: (): void => { + vi.unstubAllGlobals() + }, + } +} + +export interface CtxStub extends Ctx2D { + fillRect: Mock<(x: number, y: number, w: number, h: number) => void> + beginPath: Mock<() => void> + arc: Mock<(x: number, y: number, r: number, a0: number, a1: number) => void> + fill: Mock<() => void> + stroke: Mock<() => void> +} + +export const makeCtxStub = (): CtxStub => ({ + fillStyle: '', + strokeStyle: '', + lineWidth: 0, + globalAlpha: 1, + fillRect: vi.fn<(x: number, y: number, w: number, h: number) => void>(), + beginPath: vi.fn<() => void>(), + arc: vi.fn<(x: number, y: number, r: number, a0: number, a1: number) => void>(), + fill: vi.fn<() => void>(), + stroke: vi.fn<() => void>(), +}) diff --git a/reverse_survivors/src/test/setup.ts b/reverse_survivors/src/test/setup.ts new file mode 100644 index 00000000..e87ca3f0 --- /dev/null +++ b/reverse_survivors/src/test/setup.ts @@ -0,0 +1,7 @@ +import '@testing-library/jest-dom/vitest' +import { cleanup } from '@testing-library/react' +import { afterEach } from 'vitest' + +afterEach(() => { + cleanup() +}) diff --git a/reverse_survivors/src/ui/CanvasStage.tsx b/reverse_survivors/src/ui/CanvasStage.tsx new file mode 100644 index 00000000..7488e99a --- /dev/null +++ b/reverse_survivors/src/ui/CanvasStage.tsx @@ -0,0 +1,18 @@ +import type { ReactElement } from 'react' +import type { Ctx2D } from '../render/draw' +import { ARENA } from '../core/types' + +interface Props { + onCtx: (ctx: Ctx2D | null) => void +} + +export const CanvasStage = ({ onCtx }: Props): ReactElement => ( + { + onCtx(el === null ? null : el.getContext('2d')) + }} + /> +) diff --git a/reverse_survivors/src/ui/Hud.tsx b/reverse_survivors/src/ui/Hud.tsx new file mode 100644 index 00000000..365b3cf2 --- /dev/null +++ b/reverse_survivors/src/ui/Hud.tsx @@ -0,0 +1,67 @@ +import type { ReactElement } from 'react' +import type { HudSnapshot } from '../core/snapshot' +import { fmtTime } from './format' + +const PHIALS: readonly number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +const UPGRADE_LABELS: Record = { + damage: 'sharper bolts', + fireRate: 'faster casting', + speed: 'fleeter feet', + vitality: 'thicker blood', + regen: 'knitting flesh', + multishot: 'split volley', +} + +interface Props { + snap: HudSnapshot + onRestart: () => void +} + +export const Hud = ({ snap, onRestart }: Props): ReactElement => { + const filled = Math.floor((snap.energy / snap.energyCap) * PHIALS.length) + const hpPct = (snap.survivorHp / snap.survivorMaxHp) * 100 + return ( + + ) +} diff --git a/reverse_survivors/src/ui/SpawnBar.tsx b/reverse_survivors/src/ui/SpawnBar.tsx new file mode 100644 index 00000000..ebb8cb9b --- /dev/null +++ b/reverse_survivors/src/ui/SpawnBar.tsx @@ -0,0 +1,60 @@ +import type { ReactElement } from 'react' +import type { BossButton } from '../core/snapshot' +import type { HudSnapshot } from '../core/snapshot' +import type { DirectorAction } from '../core/types' + +interface Props { + snap: HudSnapshot + onAction: (action: DirectorAction) => void +} + +const bossCaption = (b: BossButton): string => { + if (b.phase === 'locked') { + return `seals break in ${String(b.unlockIn)}s` + } + if (b.phase === 'cooling') { + return `recovering ${String(b.cooldown)}s` + } + return `${String(b.cost)} souls` +} + +export const SpawnBar = ({ snap, onAction }: Props): ReactElement => { + const running = snap.status === 'running' + return ( + + ) +} diff --git a/reverse_survivors/src/ui/format.ts b/reverse_survivors/src/ui/format.ts new file mode 100644 index 00000000..8f15dae2 --- /dev/null +++ b/reverse_survivors/src/ui/format.ts @@ -0,0 +1,6 @@ +export const fmtTime = (seconds: number): string => { + const whole = Math.floor(seconds) + const m = Math.floor(whole / 60) + const s = whole % 60 + return `${String(m)}:${String(s).padStart(2, '0')}` +} diff --git a/reverse_survivors/src/ui/panels.test.tsx b/reverse_survivors/src/ui/panels.test.tsx new file mode 100644 index 00000000..f75d9d25 --- /dev/null +++ b/reverse_survivors/src/ui/panels.test.tsx @@ -0,0 +1,100 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import { createInitialState } from '../core/sim' +import { snapshotOf } from '../core/snapshot' +import type { HudSnapshot } from '../core/snapshot' +import type { GameState } from '../core/types' +import { Hud } from './Hud' +import { SpawnBar } from './SpawnBar' + +const snapWith = (mutate: (s: GameState) => void = () => undefined): HudSnapshot => { + const s = createInitialState(1) + mutate(s) + return snapshotOf(s) +} + +describe('Hud', () => { + it('fills soul phials proportionally', () => { + const { container } = render( { s.director.energy = 200 })} onRestart={() => undefined} />) + expect(container.querySelectorAll('.phial')).toHaveLength(10) + expect(container.querySelectorAll('.phial-full')).toHaveLength(5) + expect(screen.getByText('200 / 400')).toBeInTheDocument() + }) + + it('reports the intruder and its recent upgrades', () => { + const snap = snapWith((s) => { + s.survivor.hp = 50 + s.survivor.kills = 4 + s.upgrades = ['damage', 'multishot'] + }) + const { container } = render( undefined} />) + expect(container.querySelector('.hpbar-fill')).toHaveStyle({ width: '50%' }) + expect(screen.getByText(/4 of yours slain/)).toBeInTheDocument() + expect(screen.getByText('sharper bolts')).toBeInTheDocument() + expect(screen.getByText('split volley')).toBeInTheDocument() + }) + + it('shows no overlay while the game runs', () => { + render( undefined} />) + expect(screen.queryByRole('button')).not.toBeInTheDocument() + }) + + it('celebrates a director victory and restarts on demand', () => { + const onRestart = vi.fn() + const snap = snapWith((s) => { s.status = 'directorWon'; s.outcomeTime = 83 }) + render() + expect(screen.getByText('The intruder falls')).toBeInTheDocument() + expect(screen.getByText(/1:23/)).toBeInTheDocument() + fireEvent.click(screen.getByRole('button', { name: /open the gates/i })) + expect(onRestart).toHaveBeenCalledOnce() + }) + + it('mourns a survivor victory and restarts on demand', () => { + const onRestart = vi.fn() + render( { s.status = 'survivorWon' })} onRestart={onRestart} />) + expect(screen.getByText('Dawn breaks')).toBeInTheDocument() + fireEvent.click(screen.getByRole('button', { name: /raise a new horde/i })) + expect(onRestart).toHaveBeenCalledOnce() + }) +}) + +describe('SpawnBar', () => { + it('dispatches unit spawns the director can afford', () => { + const onAction = vi.fn() + render() + fireEvent.click(screen.getByRole('button', { name: /rusher/i })) + expect(onAction).toHaveBeenCalledWith({ type: 'spawn', kind: 'rusher' }) + expect(screen.getByRole('button', { name: /tank/i })).toBeDisabled() + }) + + it('dispatches waves when funded and blocks them when broke', () => { + const onAction = vi.fn() + const { rerender } = render() + expect(screen.getByRole('button', { name: /wave 1/i })).toBeDisabled() + rerender( { s.director.energy = 100 })} onAction={onAction} />) + fireEvent.click(screen.getByRole('button', { name: /wave 1/i })) + expect(onAction).toHaveBeenCalledWith({ type: 'wave' }) + }) + + it('freezes all summoning once the game ends', () => { + render( { s.status = 'directorWon' })} onAction={() => undefined} />) + expect(screen.getByRole('button', { name: /rusher/i })).toBeDisabled() + }) + + it('walks a boss through locked, cooling, poor, and ready', () => { + const onAction = vi.fn() + const { rerender } = render() + expect(screen.getByText(/seals break in 60s/)).toBeInTheDocument() + + rerender( { s.t = 61; s.director.bossCooldowns.colossus = 5 })} onAction={onAction} />) + expect(screen.getByText(/recovering 5s/)).toBeInTheDocument() + + rerender( { s.t = 61; s.director.energy = 20 })} onAction={onAction} />) + expect(screen.getByRole('button', { name: /colossus/i })).toBeDisabled() + expect(screen.getByText('150 souls')).toBeInTheDocument() + + rerender( { s.t = 61; s.director.energy = 200 })} onAction={onAction} />) + fireEvent.click(screen.getByRole('button', { name: /colossus/i })) + expect(onAction).toHaveBeenCalledWith({ type: 'boss', kind: 'colossus' }) + }) +}) diff --git a/reverse_survivors/src/ui/ui-bits.test.tsx b/reverse_survivors/src/ui/ui-bits.test.tsx new file mode 100644 index 00000000..23c2f8d7 --- /dev/null +++ b/reverse_survivors/src/ui/ui-bits.test.tsx @@ -0,0 +1,71 @@ +import { render, renderHook } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { Ctx2D } from '../render/draw' +import { installRaf, makeCtxStub } from '../test/harness' +import { CanvasStage } from './CanvasStage' +import { fmtTime } from './format' +import { useGameLoop } from './useGameLoop' + +afterEach(() => { + vi.unstubAllGlobals() + vi.restoreAllMocks() +}) + +describe('fmtTime', () => { + it('formats minutes and zero-padded seconds', () => { + expect(fmtTime(0)).toBe('0:00') + expect(fmtTime(65)).toBe('1:05') + expect(fmtTime(300.9)).toBe('5:00') + }) +}) + +describe('useGameLoop', () => { + it('feeds clamped dt values and swaps callbacks without restarting', () => { + const raf = installRaf() + const dts: number[] = [] + const cb1 = (dt: number): void => { dts.push(dt) } + const { rerender } = renderHook(({ cb }: { cb: (dt: number) => void }) => { useGameLoop(cb) }, { + initialProps: { cb: cb1 }, + }) + expect(raf.pending()).toBe(1) + raf.pump(0) + raf.pump(16) + raf.pump(1016) + expect(dts[0]).toBe(0) + expect(dts[1]).toBeCloseTo(0.016) + expect(dts[2]).toBe(0.1) + + const dts2: number[] = [] + rerender({ cb: (dt: number): void => { dts2.push(dt) } }) + raf.pump(1032) + expect(dts2).toHaveLength(1) + expect(dts).toHaveLength(3) + }) + + it('cancels its frame on unmount', () => { + const raf = installRaf() + const { unmount } = renderHook(() => { useGameLoop(() => undefined) }) + expect(raf.pending()).toBe(1) + unmount() + expect(raf.pending()).toBe(0) + }) +}) + +describe('CanvasStage', () => { + it('hands over the 2d context when available, and null on unmount', () => { + const stub = makeCtxStub() + vi.spyOn(HTMLCanvasElement.prototype, 'getContext') + .mockImplementation(() => stub as unknown as CanvasRenderingContext2D) + const seen: (Ctx2D | null)[] = [] + const { unmount } = render( { seen.push(c) }} />) + expect(seen).toEqual([stub]) + unmount() + expect(seen).toEqual([stub, null]) + }) + + it('hands over null when the environment has no 2d context', () => { + const seen: (Ctx2D | null)[] = [] + render( { seen.push(c) }} />) + expect(seen).toEqual([null]) + }) +}) diff --git a/reverse_survivors/src/ui/useGameLoop.ts b/reverse_survivors/src/ui/useGameLoop.ts new file mode 100644 index 00000000..2178ecaa --- /dev/null +++ b/reverse_survivors/src/ui/useGameLoop.ts @@ -0,0 +1,26 @@ +import { useEffect, useRef } from 'react' + +/** Runs `onFrame(dtSeconds)` on every animation frame. dt is clamped to 100 ms. */ +export const useGameLoop = (onFrame: (dt: number) => void): void => { + const cbRef = useRef(onFrame) + useEffect(() => { + cbRef.current = onFrame + }) + useEffect(() => { + let handle = 0 + let last = -1 + const tick = (ts: number): void => { + if (last < 0) { + last = ts + } + const dt = Math.min((ts - last) / 1000, 0.1) + last = ts + cbRef.current(dt) + handle = requestAnimationFrame(tick) + } + handle = requestAnimationFrame(tick) + return (): void => { + cancelAnimationFrame(handle) + } + }, []) +} diff --git a/reverse_survivors/tsconfig.json b/reverse_survivors/tsconfig.json new file mode 100644 index 00000000..7bf5ff42 --- /dev/null +++ b/reverse_survivors/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2023", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true, + "isolatedModules": true, + "useDefineForClassFields": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["vite/client"] + }, + "include": ["src", "vite.config.ts"] +} diff --git a/reverse_survivors/vite.config.ts b/reverse_survivors/vite.config.ts new file mode 100644 index 00000000..f2279beb --- /dev/null +++ b/reverse_survivors/vite.config.ts @@ -0,0 +1,18 @@ +/// +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + setupFiles: ['./src/test/setup.ts'], + coverage: { + provider: 'v8', + include: ['src/**/*.{ts,tsx}'], + exclude: ['src/**/*.test.{ts,tsx}', 'src/test/**'], + thresholds: { lines: 100, functions: 100, branches: 100, statements: 100 }, + reporter: ['text', 'json-summary'], + }, + }, +})