diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 00000000..3ca6847f
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,8 @@
+node_modules
+dist
+*.local
+.DS_Store
+test-results
+playwright-report
+blob-report
+playwright/.cache
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 00000000..289d7e98
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,77 @@
+# OpenVerifiableLLM frontend
+
+A static site for the OpenVerifiableLLM public record: evidence, releases,
+verification profiles and an inference page. It displays a saved JSON snapshot.
+It has no backend and does not train, sign or run a model. The project is at
+an early stage and no model has been released yet.
+
+## Requirements
+
+Node 20.19+ or 22.12+ (required by Vite 7) and npm.
+
+## Commands
+
+```sh
+cd frontend
+npm ci
+npm run dev # http://localhost:5173
+npm run typecheck
+npm test
+npm run build # writes dist/
+npm run preview # serves dist/
+```
+
+Routing uses `HashRouter` (`/#/evidence`) and `vite.config.ts` sets
+`base: './'`, so the build can be served from any static host or sub-path.
+
+## Data
+
+Snapshots live in `src/data/fixtures/`. The app loads one, runs it through
+`validateSnapshot` in `src/data/validate.ts`, and shows a data error page if it
+is invalid rather than an empty or passing state. Choose a pack with
+`?snapshot=`, for example `/#/?snapshot=missing-parent`:
+
+| `?snapshot=` | File | Shows |
+| --- | --- | --- |
+| `public` (default) | `public-snapshot.json` | Draft project status: early practice-run checks passed, both models not released, replay not run |
+| `missing-parent` | `missing-parent.json` | An evidence item whose parent is not in the record |
+| `superseded` | `superseded.json` | A failed check kept after a later pass |
+| `empty` | `empty-checks.json` | No checks, shown as a data error |
+
+All of these are fixtures (`"mode": "fixture"`) and the site shows a banner
+while one is loaded. The default pack is a draft of the current project status
+and has not been approved by the maintainer. `public/data/` is reserved for the
+approved snapshot, which is the only file that should use
+`"mode": "public-snapshot"`.
+
+## Structure
+
+- `src/data/contracts.ts`: snapshot types.
+- `src/data/adapters/snapshotAdapter.ts`: loads the selected fixture.
+- `src/data/validate.ts`: checks the snapshot and fails closed.
+- `src/snapshot.tsx`: provides the validated snapshot to the pages.
+- `src/pages/`: Overview, Evidence, Releases, Verify and Inference.
+- `src/content/copy.ts`: shared wording for pipeline stages and the FAQ.
+- `src/inference/`: `DisabledAdapter` and `MockAdapter`. Neither is used by
+ the app yet; the Inference page is a disabled placeholder and `MockAdapter`
+ is only used in tests.
+- `tests/`: Vitest tests for validation, fixture loading, release gating,
+ stage results and the inference adapters.
+
+Everything is in `frontend/`. Nothing here touches the pipeline in `src/`,
+`project/` or signing keys.
+
+## Not done yet
+
+- Real project data, once the maintainer approves a public snapshot (M3).
+- An inference preview using `MockAdapter` behind a build flag (M4).
+- Copyable verification commands. The Verify page says instructions are
+ pending until a tested command exists.
+- Browser tests, a frontend CI workflow and deployment (M5).
+
+## Needed from the maintainer
+
+- An approved public snapshot and its source revision.
+- A tested verification command for each profile.
+- Release metadata for the base and chat models when they exist.
+- The hosting target and base path.
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 00000000..7415838b
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ OpenVerifiableLLM
+
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 00000000..feebd89c
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,3116 @@
+{
+ "name": "openverifiablellm-frontend",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "openverifiablellm-frontend",
+ "version": "0.1.0",
+ "dependencies": {
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-router-dom": "^7.8.2"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.63.0",
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^14.6.1",
+ "@types/react": "^19.1.12",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.2",
+ "jsdom": "^26.1.0",
+ "typescript": "~5.9.2",
+ "vite": "^7.1.5",
+ "vitest": "^3.2.4"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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/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-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-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.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.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.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/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "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/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz",
+ "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.63.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.4.tgz",
+ "integrity": "sha512-I+BSHzTAhKN2n7ZwGZsegGcZjDpLqFOMAtJz/u6uFGe0pUFbq56dEHjqJV/ZUdRJtNXNxA+hREUatZBvMR3Oiw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.4.tgz",
+ "integrity": "sha512-pu3BdjS2LtEzRu2elmGzS3fIeWSZy4BMDIaLNwjorO76+k2d0LMluijhsDx3KQyQBQ/lLUZCQA9/s6csvUfuhw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.4.tgz",
+ "integrity": "sha512-xfSrj9MHnWK9GaSqT9U0ImHtH/N8WZlHLx4cZHiuLcqs640hvZ3hLPd5UR2AZS57FaE8HrRUSpltbZdWRxHiDA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.4.tgz",
+ "integrity": "sha512-bqU99PLJb/dqb3S0GIMdeuyAEETSUgZBoqXYd3Sd+WCsV+MmPhnN6JrotWyir31+QgH7EvvE5/mwGJlEoci8Fw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.4.tgz",
+ "integrity": "sha512-JinsFZ5G40oXQb+sUuiA5x689vhr6dDYK0H0NL+rwKdL6CqnmYN8PE4ZwfRSoIjrCxqTQG/SLfTtSvHeGxoVlw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.4.tgz",
+ "integrity": "sha512-GAdA4UxpiNm27cLHr2GqXBpAD0x9FqwYBY7/YSP0Ss0/PNi4k8gbviqpIpYbVSRBaS2ZcegXEzgTQMbRNCwxCw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.4.tgz",
+ "integrity": "sha512-qDd6NoA1znaLjp4jR5U/KWCdLAKDJNB8W9ChbbDaKbo0xA+Atln5HK6LFCZ4oJQpemtRZA288DCirFRjrspptw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.4.tgz",
+ "integrity": "sha512-WtB5Tz5KTNINb8ZA+8sQ7bmjuS1JrRT7YverYIhUGdWWDlpzVWmIwuZE+jidkEXUn1l0zrEkaIMa8dHF3NGcsA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.4.tgz",
+ "integrity": "sha512-VcQ3L1tjnkKzWjryAVaFhHEWcqOfICX9uxVVoDzm2t0DpgKRHd2zOpVrJc0xsWeBZcBFyYROCIBdyR/fS174pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.4.tgz",
+ "integrity": "sha512-6+ZQX6P5s0cMDN2Ypb8Lbm2+/sZYmZjdaYny992ujUU9UKi/4CWoJWsl1pNvjWJHNHGK51m+jKGLlh1ylb2ifQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.4.tgz",
+ "integrity": "sha512-D72ZnvkFkBXOfzMMQLcwfPLyGkKb7HZ9/mf97B7v6/P5Lbv4oFOtSY/uHbS8lH6uKUOxoKiuokdb50XZSzzbJw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.4.tgz",
+ "integrity": "sha512-piU6BxeqA3O9KSu3kRCIQQtNqFFaTu21SEV4FwaRZowpnj3bLaWPZHw+xFqCs0XlJ+aOH3PTRWGoglH+mKA/OA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.4.tgz",
+ "integrity": "sha512-/5PGpHwqt2EEEOUs1XwzubE/ucr0dWDQ+to3zqi4Ds7EWpwtQ79wXc4JBoxqj/OwpawTsKWzJxHfSuBOq3DrWA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.4.tgz",
+ "integrity": "sha512-cX3beZDLWt7G2oJF+nhChiT+qtaihs+S2xi7ziGmVB+2pwPng6D0Ed0HmElQOgv2UsUmSJJLGwpBao/3TDx3VA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.4.tgz",
+ "integrity": "sha512-1uz2mGWHyptR7DgHHrlbdRAjXK7v7elGZ9lMja910/RP+ZYbX6xAmCiU9UZSX4hqmgtHMv6lr5l3kq1HIOpcag==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.4.tgz",
+ "integrity": "sha512-nLS8topojxyz7SRpKR2IODRpQ0XPZ+xaOXvT3+hqK/Uy8Lo5HFgkkIBiIrCu5tL5YqzTvgovGw55PwpahTAGig==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.4.tgz",
+ "integrity": "sha512-gs7DRKotr3l3q+jGPQBjH0ng1FjlEDm5ueQrkw5JtQvtLyEIcLASqAEaor56BhkKRzk+IcQzrcanBdb/bBQn8g==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.4.tgz",
+ "integrity": "sha512-791ET7W17NnScOZM7h4dX5hYspxE28htPFsb1awY/NRR8+PRNkS53e475rDdxXXDrP+kwnCcNWg9CX5ztn/Aqw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.4.tgz",
+ "integrity": "sha512-iwZQRcmj7g88g3tzefIrQY7qvmuA/cfYwhrDtTBhsmukO4U2huVO5W+86XacUMRvdSFVAc6kZUZy21JaRwiB9w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.4.tgz",
+ "integrity": "sha512-dVHFp9gRWrdTpnqQuGfCwd7hOQDatK1VCP2iWhLY/cGrOQs/ucFzJ6A5SRqbXX12ZDI8EUuejSM5kwg+ja7Png==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.4.tgz",
+ "integrity": "sha512-t3NlauOW6gxZVVFcBEnO62Cb4wbyDFL416gTg1uFI/2tgqYQlf69FbSE115Ajre9I+c26Lk4mcmdFUsS/DGifQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.4.tgz",
+ "integrity": "sha512-xWuIaSye5FWZF8+UYtVEcHtRJDN5kN9Kfgxx3Kq8XIov9KSKbc1fiqQCm90SKrgQbUXZelbnUhnlUJmfSE7P9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.4.tgz",
+ "integrity": "sha512-9ALJJUOg/ZflMJepVo2PlgsGxSaxN7SQ4Z8GoZfVlarWr6r3rkHUNsd/zAio7p4YMtChSMXPionxej4Hkf6CXQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.4.tgz",
+ "integrity": "sha512-blj9z5qx/Pv4WU0W1NMFDB97e0JH5ed+aZGywW8WCvp/NhWX/4PFAq5uu6Q0AebNn+Vo6KzUYDT++JzTT5ojlQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.4.tgz",
+ "integrity": "sha512-Erx822VRBwLa124shbj+wNXe//BOgMEctDV0m1aqTQdNO1S69DgNUCFKC1RCeZfixs1J31l6igk1ziyXErbigQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.2.tgz",
+ "integrity": "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==",
+ "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/react": {
+ "version": "16.3.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.3.tgz",
+ "integrity": "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==",
+ "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.7",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz",
+ "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/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/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/react": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.3.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
+ "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",
+ "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz",
+ "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.2.7",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz",
+ "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz",
+ "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.7",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz",
+ "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.7",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz",
+ "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz",
+ "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.7",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "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",
+ "peer": true,
+ "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/baseline-browser-mapping": {
+ "version": "2.11.25",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz",
+ "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.29.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz",
+ "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==",
+ "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.11.23",
+ "caniuse-lite": "^1.0.30001810",
+ "electron-to-chromium": "^1.5.427",
+ "node-releases": "^2.0.55",
+ "update-browserslist-db": "^1.3.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001810",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
+ "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
+ "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": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cssstyle": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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.433",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.433.tgz",
+ "integrity": "sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
+ "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.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/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/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/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "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/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": "26.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.2.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.5.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.16",
+ "parse5": "^7.2.1",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.1.1",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.1.1",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/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/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.19",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
+ "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.56",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz",
+ "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nwsapi": {
+ "version": "2.2.28",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.28.tgz",
+ "integrity": "sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "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.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
+ "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.63.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
+ "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
+ "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.18",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "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.3.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.28.0"
+ },
+ "peerDependencies": {
+ "react": "^19.3.0"
+ }
+ },
+ "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/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.18.4",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz",
+ "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.18.4",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz",
+ "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.18.4"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.4.tgz",
+ "integrity": "sha512-4U0liVayNIoLp3GFl1FcI8561WepLnZ1rqfraGh7S9B3Ur5F9S283y8Futii7RUU2C/97tOBmBy7nYvhoiOpbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.63.4",
+ "@rollup/rollup-android-arm64": "4.63.4",
+ "@rollup/rollup-darwin-arm64": "4.63.4",
+ "@rollup/rollup-darwin-x64": "4.63.4",
+ "@rollup/rollup-freebsd-arm64": "4.63.4",
+ "@rollup/rollup-freebsd-x64": "4.63.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.63.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.63.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.63.4",
+ "@rollup/rollup-linux-arm64-musl": "4.63.4",
+ "@rollup/rollup-linux-loong64-gnu": "4.63.4",
+ "@rollup/rollup-linux-loong64-musl": "4.63.4",
+ "@rollup/rollup-linux-ppc64-gnu": "4.63.4",
+ "@rollup/rollup-linux-ppc64-musl": "4.63.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.63.4",
+ "@rollup/rollup-linux-riscv64-musl": "4.63.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.63.4",
+ "@rollup/rollup-linux-x64-gnu": "4.63.4",
+ "@rollup/rollup-linux-x64-musl": "4.63.4",
+ "@rollup/rollup-openbsd-x64": "4.63.4",
+ "@rollup/rollup-openharmony-arm64": "4.63.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.63.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.63.4",
+ "@rollup/rollup-win32-x64-gnu": "4.63.4",
+ "@rollup/rollup-win32-x64-msvc": "4.63.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",
+ "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/strip-literal": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
+ "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz",
+ "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^6.1.86"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tough-cookie": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^6.1.32"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
+ "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
+ "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/vite": {
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
+ "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz",
+ "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.7",
+ "@vitest/mocker": "3.2.7",
+ "@vitest/pretty-format": "^3.2.7",
+ "@vitest/runner": "3.2.7",
+ "@vitest/snapshot": "3.2.7",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.7",
+ "@vitest/ui": "3.2.7",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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/ws": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
+ "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "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"
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 00000000..5897a49b
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "openverifiablellm-frontend",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview",
+ "typecheck": "tsc -b --noEmit",
+ "test": "vitest run",
+ "e2e": "playwright test"
+ },
+ "dependencies": {
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-router-dom": "^7.8.2"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.63.0",
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^14.6.1",
+ "@types/react": "^19.1.12",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.2",
+ "jsdom": "^26.1.0",
+ "typescript": "~5.9.2",
+ "vite": "^7.1.5",
+ "vitest": "^3.2.4"
+ }
+}
diff --git a/frontend/public/data/README.md b/frontend/public/data/README.md
new file mode 100644
index 00000000..91423486
--- /dev/null
+++ b/frontend/public/data/README.md
@@ -0,0 +1,2 @@
+Display snapshots live in `src/data/fixtures/`. This folder is reserved for a
+maintainer-approved public snapshot file later. Do not dump `project/` here.
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 00000000..7117944f
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,28 @@
+import { HashRouter, Route, Routes } from 'react-router-dom';
+import { AppShell } from './layout/AppShell';
+import { EvidenceDetailPage } from './pages/EvidenceDetailPage';
+import { EvidenceListPage } from './pages/EvidenceListPage';
+import { OverviewPage } from './pages/OverviewPage';
+import { ReleasesPage } from './pages/ReleasesPage';
+import { TryPage } from './pages/TryPage';
+import { VerifyPage } from './pages/VerifyPage';
+import { SnapshotProvider } from './snapshot';
+
+export default function App() {
+ return (
+
+
+
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+ );
+}
diff --git a/frontend/src/content/copy.ts b/frontend/src/content/copy.ts
new file mode 100644
index 00000000..7e754bee
--- /dev/null
+++ b/frontend/src/content/copy.ts
@@ -0,0 +1,164 @@
+import type { CheckResult, EvidenceScope } from '../data/contracts';
+
+export const SCOPE_LABEL: Record = {
+ fixture: 'test data',
+ pilot: 'practice run',
+ production: 'real run',
+};
+
+export const PHASE_LABEL: Record = {
+ source: 'Data',
+ prepare: 'Preparation',
+ train: 'Training',
+ replay: 'Replay',
+ release: 'Release',
+ other: 'Other',
+};
+
+export type JourneyKind = 'evidence' | 'verification' | 'inference';
+
+export interface JourneyStageDef {
+ id: string;
+ label: string;
+ what: string;
+ why: string;
+ pipelineIds: string[];
+ checkProfile: string | null;
+ kind: JourneyKind;
+}
+
+export const JOURNEY: JourneyStageDef[] = [
+ {
+ id: 'data',
+ label: 'Data',
+ what: 'Name the public Wikipedia files used as input.',
+ why: 'Later steps cannot be checked if the starting files are unnamed or private. Conversation data is planned for the chat model, not shown as done here.',
+ pipelineIds: ['wiki-source', 'wiki-acquire'],
+ checkProfile: null,
+ kind: 'evidence',
+ },
+ {
+ id: 'preparation',
+ label: 'Preparation',
+ what: 'Turn those files into the training text and tokens.',
+ why: 'Cleaning and tokenizing must be specified, or training cannot be reproduced.',
+ pipelineIds: ['extract', 'tokenizer'],
+ checkProfile: null,
+ kind: 'evidence',
+ },
+ {
+ id: 'training',
+ label: 'Training',
+ what: 'Train from scratch on that data; save checkpoints.',
+ why: 'Released weights should be the output of this run, not an unexplained extra process.',
+ pipelineIds: ['init', 'wiki-train', 'chat-train'],
+ checkProfile: null,
+ kind: 'evidence',
+ },
+ {
+ id: 'replay',
+ label: 'Replay',
+ what: 'Rebuild the data and run the same training again.',
+ why: 'A checksum or signature does not prove the training description is true. This is verification, not the project’s file list.',
+ pipelineIds: ['replay'],
+ checkProfile: 'full-replay',
+ kind: 'verification',
+ },
+ {
+ id: 'release',
+ label: 'Release',
+ what: 'Publish the model files with checksums.',
+ why: 'Without a public folder and version, there are no weights for others to download.',
+ pipelineIds: ['publish'],
+ checkProfile: null,
+ kind: 'evidence',
+ },
+ {
+ id: 'inference',
+ label: 'Inference',
+ what: 'Reproduce a saved example from released files.',
+ why: 'Generating text is not the same as proving how the model was trained. Live chat is not this check.',
+ pipelineIds: [],
+ checkProfile: 'inference-reproduction',
+ kind: 'inference',
+ },
+];
+
+export function worstResult(results: CheckResult[]): CheckResult {
+ if (results.includes('FAIL')) return 'FAIL';
+ if (results.includes('UNSUPPORTED')) return 'UNSUPPORTED';
+ if (results.includes('UNAVAILABLE')) return 'UNAVAILABLE';
+ if (results.length > 0 && results.every((r) => r === 'PASS')) return 'PASS';
+ if (results.includes('PASS') && results.some((r) => r !== 'PASS')) return 'NOT_RUN';
+ if (results.includes('NOT_RUN') || results.length === 0) return 'NOT_RUN';
+ return 'NOT_RUN';
+}
+
+export const WHY = [
+ {
+ title: 'A download is not a proof',
+ body: 'A hosted file and a checksum do not show which data was used or how training ran.',
+ },
+ {
+ title: 'A signature is not a replay',
+ body: 'A signature names who published a folder. It does not re-run training.',
+ },
+ {
+ title: 'Chat is not verification',
+ body: 'Generating text does not inspect data, checkpoints, or training.',
+ },
+];
+
+export const LAYERS = [
+ {
+ title: 'Evidence',
+ body: 'The public record: files, reports, and links the project published. This website displays that record. It does not re-run training.',
+ },
+ {
+ title: 'Verification',
+ body: 'Independent checks of that record — for example rebuilding the data or re-running training. Each check has its own meaning. There is no single green badge.',
+ },
+ {
+ title: 'Inference',
+ body: 'Using released weights to produce text. That is not enabled here, and it would not make answers into citations even if it were.',
+ },
+];
+
+export const FAQ = [
+ {
+ q: 'What is OpenVerifiableLLM?',
+ a: 'A project to train a small language model and keep a public record of how it was made: data, training steps, checkpoints (mid-training saves), and released weights (the downloadable model files). Other people should be able to inspect that record and later reproduce it. It is not a day-to-day chatbot.',
+ },
+ {
+ q: 'Why does that matter?',
+ a: 'Most open models give you files and a write-up. You can download the files. You usually cannot check the write-up. If a lab says “trained only on this Wikipedia dump,” you have to trust them. This project tries to make that checkable.',
+ },
+ {
+ q: 'What can I do on this website?',
+ a: 'Read the saved public record and follow links to original files. Listing Wikipedia files (G01) is marked as a practice-run pass. Reconstruction (G02) and later stages remain pending. This site does not train, sign, or generate text.',
+ },
+ {
+ q: 'What is a practice run versus the real run?',
+ a: 'A practice run (pilot) is the team testing the pipeline. The real run (production) is the Wikipedia-to-model release. A pass on a practice run does not finish the real run. Test data (fixture) is synthetic and is not project status.',
+ },
+ {
+ q: 'Is a pass here the same as the old ovllm GREEN verdict?',
+ a: 'No. An older command in the repo can print GREEN when file hashes and signatures match. That is artifact identity, not a full rebuild and re-run of Wikipedia training. This site keeps those checks separate.',
+ },
+ {
+ q: 'Who ran the checks shown?',
+ a: 'The project team, not an independent outsider, and not your browser. Opening this page does not recompute anything. If a check says it was not recomputed locally, that is correct.',
+ },
+ {
+ q: 'Would a later chat mean the answers are true?',
+ a: 'No. Checking how a model was made is not fact-checking what it says, and it does not mean a reply came from a particular Wikipedia article.',
+ },
+ {
+ q: 'Can I run the full check in the browser?',
+ a: 'No. Re-running training needs the stated computers and is slow and costly. It will never be a silent click here. A later, labelled checksum of a small published file would only prove that one file’s bytes.',
+ },
+ {
+ q: 'Where are the original files?',
+ a: 'On the Evidence page: GitHub and a public Hugging Face dataset. This website only shows those links. It does not host Wikipedia dumps or checkpoints.',
+ },
+];
diff --git a/frontend/src/data/adapters/snapshotAdapter.ts b/frontend/src/data/adapters/snapshotAdapter.ts
new file mode 100644
index 00000000..e885e345
--- /dev/null
+++ b/frontend/src/data/adapters/snapshotAdapter.ts
@@ -0,0 +1,31 @@
+import { SnapshotError, type Snapshot } from '../contracts';
+import { validateSnapshot } from '../validate';
+import emptyChecks from '../fixtures/empty-checks.json';
+import missingParent from '../fixtures/missing-parent.json';
+import publicSnapshot from '../fixtures/public-snapshot.json';
+import superseded from '../fixtures/superseded.json';
+
+export const SNAPSHOT_IDS = ['public', 'missing-parent', 'superseded', 'empty'] as const;
+export type SnapshotId = (typeof SNAPSHOT_IDS)[number];
+
+const RAW: Record = {
+ public: publicSnapshot,
+ 'missing-parent': missingParent,
+ superseded,
+ empty: emptyChecks,
+};
+
+export function loadSnapshot(id: SnapshotId = 'public'): Snapshot {
+ const raw = RAW[id];
+ if (raw === undefined) {
+ throw new SnapshotError(`Unknown snapshot ${id}`);
+ }
+ return validateSnapshot(raw);
+}
+
+export function parseSnapshotId(value: string | null): SnapshotId {
+ if (value && (SNAPSHOT_IDS as readonly string[]).includes(value)) {
+ return value as SnapshotId;
+ }
+ return 'public';
+}
diff --git a/frontend/src/data/contracts.ts b/frontend/src/data/contracts.ts
new file mode 100644
index 00000000..0b0bcb6c
--- /dev/null
+++ b/frontend/src/data/contracts.ts
@@ -0,0 +1,124 @@
+export const SNAPSHOT_SCHEMA = 'ovllm.frontend.snapshot.v1';
+
+export type CheckResult =
+ | 'PASS'
+ | 'FAIL'
+ | 'NOT_RUN'
+ | 'UNAVAILABLE'
+ | 'UNSUPPORTED';
+
+export type EvidenceScope = 'fixture' | 'pilot' | 'production';
+export type DataMode = 'fixture' | 'public-snapshot';
+export type ReleaseAvailability = 'not-released' | 'available' | 'withdrawn';
+export type ReleaseRole = 'base' | 'chat';
+
+export interface SourceReference {
+ url: string;
+ revision: string | null;
+ path: string | null;
+ sha256: string | null;
+}
+
+export interface CheckView {
+ id: string;
+ profile: string;
+ phase: string;
+ scope: EvidenceScope;
+ result: CheckResult;
+ explanation: string;
+ performedBy: string | null;
+ attestedBy: string | null;
+ locallyRecomputed: boolean;
+ evidence: SourceReference[];
+}
+
+export interface EvidenceItem {
+ id: string;
+ title: string;
+ kind: string;
+ phase: string;
+ scope: EvidenceScope;
+ result: CheckResult;
+ checkId: string | null;
+ source: SourceReference;
+ parentIds: string[];
+ childIds: string[];
+ sizeBytes: number | null;
+ timestamp: string | null;
+ supersededBy: string | null;
+ supersedes: string | null;
+}
+
+export interface PipelineStep {
+ id: string;
+ title: string;
+ phase: string;
+ result: CheckResult;
+ scope: EvidenceScope;
+ checkId: string | null;
+}
+
+export interface ReleaseView {
+ id: string;
+ role: ReleaseRole;
+ availability: ReleaseAvailability;
+ title: string;
+ parentReleaseId: string | null;
+ repository: string | null;
+ revision: string | null;
+ releaseRoot: string | null;
+ notes: string;
+}
+
+export interface CommandView {
+ profile: string;
+ text: string;
+ prerequisites: string;
+ sourceRevision: string | null;
+ expectedScope: string;
+ resourceNote: string;
+ validated: boolean;
+}
+
+export interface SnapshotSummary {
+ headline: string;
+ mayClaim: string[];
+ mayNotClaim: string[];
+ workflow: string;
+}
+
+export interface Snapshot {
+ schemaVersion: string;
+ generatedAt: string;
+ mode: DataMode;
+ source: SourceReference;
+ summary: SnapshotSummary;
+ pipeline: PipelineStep[];
+ releases: ReleaseView[];
+ checks: CheckView[];
+ evidence: EvidenceItem[];
+ commands: CommandView[];
+}
+
+export const CHECK_RESULTS: readonly CheckResult[] = [
+ 'PASS',
+ 'FAIL',
+ 'NOT_RUN',
+ 'UNAVAILABLE',
+ 'UNSUPPORTED',
+];
+
+export const SCOPES: readonly EvidenceScope[] = ['fixture', 'pilot', 'production'];
+export const DATA_MODES: readonly DataMode[] = ['fixture', 'public-snapshot'];
+export const AVAILABILITIES: readonly ReleaseAvailability[] = [
+ 'not-released',
+ 'available',
+ 'withdrawn',
+];
+
+export class SnapshotError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = 'SnapshotError';
+ }
+}
diff --git a/frontend/src/data/fixtures/empty-checks.json b/frontend/src/data/fixtures/empty-checks.json
new file mode 100644
index 00000000..e132b15f
--- /dev/null
+++ b/frontend/src/data/fixtures/empty-checks.json
@@ -0,0 +1,22 @@
+{
+ "schemaVersion": "ovllm.frontend.snapshot.v1",
+ "generatedAt": "2026-09-19T12:00:00Z",
+ "mode": "fixture",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "frontend/src/data/fixtures/empty-checks.json",
+ "sha256": null
+ },
+ "summary": {
+ "headline": "Test record with no checks.",
+ "mayClaim": [],
+ "mayNotClaim": ["anything"],
+ "workflow": "Must not render as success."
+ },
+ "pipeline": [],
+ "releases": [],
+ "checks": [],
+ "evidence": [],
+ "commands": []
+}
diff --git a/frontend/src/data/fixtures/missing-parent.json b/frontend/src/data/fixtures/missing-parent.json
new file mode 100644
index 00000000..424dea1f
--- /dev/null
+++ b/frontend/src/data/fixtures/missing-parent.json
@@ -0,0 +1,96 @@
+{
+ "schemaVersion": "ovllm.frontend.snapshot.v1",
+ "generatedAt": "2026-09-19T12:00:00Z",
+ "mode": "fixture",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "frontend/src/data/fixtures/missing-parent.json",
+ "sha256": null
+ },
+ "summary": {
+ "headline": "Test record: an item points at an earlier file that is missing.",
+ "mayClaim": ["this is a synthetic fixture"],
+ "mayNotClaim": ["production status", "a released model"],
+ "workflow": "Used to test a missing link. Not real project status."
+ },
+ "pipeline": [
+ {
+ "id": "hash",
+ "title": "Artifact hash",
+ "phase": "source",
+ "result": "FAIL",
+ "scope": "fixture",
+ "checkId": "chk-hash"
+ }
+ ],
+ "releases": [
+ {
+ "id": "base",
+ "role": "base",
+ "availability": "not-released",
+ "title": "Base",
+ "parentReleaseId": null,
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "Fixture. Not a release."
+ },
+ {
+ "id": "chat",
+ "role": "chat",
+ "availability": "not-released",
+ "title": "Chat",
+ "parentReleaseId": "base",
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "Fixture. Not a release."
+ }
+ ],
+ "checks": [
+ {
+ "id": "chk-hash",
+ "profile": "artifact-identity",
+ "phase": "source",
+ "scope": "fixture",
+ "result": "FAIL",
+ "explanation": "Declared parent digest is absent from the snapshot.",
+ "performedBy": "fixture",
+ "attestedBy": "fixture",
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "frontend/src/data/fixtures/missing-parent.json",
+ "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ }
+ ]
+ }
+ ],
+ "evidence": [
+ {
+ "id": "ev-child",
+ "title": "Prepared stream (child)",
+ "kind": "artifact",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "FAIL",
+ "checkId": "chk-hash",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "missing-parent",
+ "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+ },
+ "parentIds": ["ev-parent-absent"],
+ "childIds": [],
+ "sizeBytes": 2048,
+ "timestamp": "2026-09-19T12:00:00Z",
+ "supersededBy": null,
+ "supersedes": null
+ }
+ ],
+ "commands": []
+}
diff --git a/frontend/src/data/fixtures/public-snapshot.json b/frontend/src/data/fixtures/public-snapshot.json
new file mode 100644
index 00000000..f6a05e01
--- /dev/null
+++ b/frontend/src/data/fixtures/public-snapshot.json
@@ -0,0 +1,231 @@
+{
+ "schemaVersion": "ovllm.frontend.snapshot.v1",
+ "generatedAt": "2026-09-19T12:00:00Z",
+ "mode": "fixture",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "03551ddedf9e54ae4355cf7e3f85c83c27e5f971",
+ "path": null,
+ "sha256": null
+ },
+ "summary": {
+ "headline": "OpenVerifiableLLM keeps a public record of a small language model’s data, training, checkpoints, and released files so other people can inspect and later reproduce them. The models are not released yet.",
+ "mayClaim": [
+ "The project team listed the Wikipedia files for a practice run (G01)"
+ ],
+ "mayNotClaim": [
+ "that a model is available to download or chat with",
+ "that training was fully re-run and matched",
+ "that someone outside the team has independently rebuilt it",
+ "that generated answers are true"
+ ],
+ "workflow": "G01 (listing Wikipedia files) is recorded as passed on a practice run. G02–G10 and both models remain pending."
+ },
+ "pipeline": [
+ {
+ "id": "wiki-source",
+ "title": "List the Wikipedia files to use",
+ "phase": "source",
+ "result": "PASS",
+ "scope": "pilot",
+ "checkId": "chk-g01"
+ },
+ {
+ "id": "wiki-acquire",
+ "title": "Download those Wikipedia files",
+ "phase": "source",
+ "result": "NOT_RUN",
+ "scope": "pilot",
+ "checkId": "chk-g02"
+ },
+ {
+ "id": "extract",
+ "title": "Turn Wikipedia pages into clean text",
+ "phase": "prepare",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "tokenizer",
+ "title": "Turn that text into tokens the model can train on",
+ "phase": "prepare",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "init",
+ "title": "Start the model from scratch (no hidden pretrained weights)",
+ "phase": "train",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "wiki-train",
+ "title": "Train once on that Wikipedia text",
+ "phase": "train",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "chat-train",
+ "title": "Then train for chat, using public conversation data",
+ "phase": "train",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "replay",
+ "title": "Re-run training and check it matches the saved files",
+ "phase": "replay",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "publish",
+ "title": "Publish the model files and the evidence",
+ "phase": "release",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ }
+ ],
+ "releases": [
+ {
+ "id": "base",
+ "role": "base",
+ "availability": "not-released",
+ "title": "Base model (Wikipedia)",
+ "parentReleaseId": null,
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "The Wikipedia-trained model has not been published. Download and chat stay off until there is a public folder, a fixed version, and a published checksum for those files."
+ },
+ {
+ "id": "chat",
+ "role": "chat",
+ "availability": "not-released",
+ "title": "Chat model (built on the base model)",
+ "parentReleaseId": "base",
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "The chat model is trained after the Wikipedia model, so it is not “Wikipedia-only.” It is also not released."
+ }
+ ],
+ "checks": [
+ {
+ "id": "chk-g01",
+ "profile": "artifact-identity",
+ "phase": "source",
+ "scope": "pilot",
+ "result": "PASS",
+ "explanation": "The team listed the exact English Wikipedia files for a practice run and published links to that list. This only checks the file list, not training.",
+ "performedBy": "project-operator",
+ "attestedBy": "project-operator",
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://huggingface.co/datasets/AOSSIE/openverifiable-enwiki-20260901-20260918-r1-evidence",
+ "revision": null,
+ "path": null,
+ "sha256": null
+ }
+ ]
+ },
+ {
+ "id": "chk-g02",
+ "profile": "data-reconstruction",
+ "phase": "source",
+ "scope": "pilot",
+ "result": "NOT_RUN",
+ "explanation": "The public evidence dataset still lists G02 as pending. This site does not treat dump reconstruction as passed.",
+ "performedBy": null,
+ "attestedBy": null,
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM/pull/101",
+ "revision": null,
+ "path": null,
+ "sha256": null
+ }
+ ]
+ },
+ {
+ "id": "chk-replay",
+ "profile": "full-replay",
+ "phase": "replay",
+ "scope": "production",
+ "result": "NOT_RUN",
+ "explanation": "The real training has not been fully re-run and accepted. Practice GPU runs do not count as the finished real run.",
+ "performedBy": null,
+ "attestedBy": null,
+ "locallyRecomputed": false,
+ "evidence": []
+ },
+ {
+ "id": "chk-infer",
+ "profile": "inference-reproduction",
+ "phase": "release",
+ "scope": "production",
+ "result": "NOT_RUN",
+ "explanation": "No model files have been released, so there is nothing to re-run as a saved chat example.",
+ "performedBy": null,
+ "attestedBy": null,
+ "locallyRecomputed": false,
+ "evidence": []
+ }
+ ],
+ "evidence": [
+ {
+ "id": "ev-hf-dataset",
+ "title": "Public evidence files (practice run)",
+ "kind": "dataset",
+ "phase": "source",
+ "scope": "pilot",
+ "result": "PASS",
+ "checkId": "chk-g01",
+ "source": {
+ "url": "https://huggingface.co/datasets/AOSSIE/openverifiable-enwiki-20260901-20260918-r1-evidence",
+ "revision": null,
+ "path": null,
+ "sha256": null
+ },
+ "parentIds": [],
+ "childIds": ["ev-pr-101"],
+ "sizeBytes": null,
+ "timestamp": "2026-09-18T00:00:00Z",
+ "supersededBy": null,
+ "supersedes": null
+ },
+ {
+ "id": "ev-pr-101",
+ "title": "Training-pipeline work on GitHub (pull request 101, merged)",
+ "kind": "report",
+ "phase": "source",
+ "scope": "pilot",
+ "result": "NOT_RUN",
+ "checkId": "chk-g02",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM/pull/101",
+ "revision": null,
+ "path": null,
+ "sha256": null
+ },
+ "parentIds": ["ev-hf-dataset"],
+ "childIds": [],
+ "sizeBytes": null,
+ "timestamp": "2026-09-17T18:01:11Z",
+ "supersededBy": null,
+ "supersedes": null
+ }
+ ],
+ "commands": []
+}
diff --git a/frontend/src/data/fixtures/superseded.json b/frontend/src/data/fixtures/superseded.json
new file mode 100644
index 00000000..351363ed
--- /dev/null
+++ b/frontend/src/data/fixtures/superseded.json
@@ -0,0 +1,136 @@
+{
+ "schemaVersion": "ovllm.frontend.snapshot.v1",
+ "generatedAt": "2026-09-19T12:00:00Z",
+ "mode": "fixture",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "frontend/src/data/fixtures/superseded.json",
+ "sha256": null
+ },
+ "summary": {
+ "headline": "Test record: an old failed check stays listed after a later pass.",
+ "mayClaim": ["this is a synthetic fixture"],
+ "mayNotClaim": ["production status"],
+ "workflow": "History is not rewritten. Not project status."
+ },
+ "pipeline": [
+ {
+ "id": "recon",
+ "title": "Data reconstruction",
+ "phase": "prepare",
+ "result": "PASS",
+ "scope": "fixture",
+ "checkId": "chk-recon-2"
+ }
+ ],
+ "releases": [
+ {
+ "id": "base",
+ "role": "base",
+ "availability": "not-released",
+ "title": "Base",
+ "parentReleaseId": null,
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "Fixture. Not a release."
+ },
+ {
+ "id": "chat",
+ "role": "chat",
+ "availability": "not-released",
+ "title": "Chat",
+ "parentReleaseId": "base",
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "Fixture. Not a release."
+ }
+ ],
+ "checks": [
+ {
+ "id": "chk-recon-1",
+ "profile": "data-reconstruction",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "FAIL",
+ "explanation": "First reconstruction attempt did not match the committed root.",
+ "performedBy": "fixture",
+ "attestedBy": "fixture",
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "report-1",
+ "sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ }
+ ]
+ },
+ {
+ "id": "chk-recon-2",
+ "profile": "data-reconstruction",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "PASS",
+ "explanation": "Later reconstruction matched the committed root. This does not erase the earlier FAIL.",
+ "performedBy": "fixture",
+ "attestedBy": "fixture",
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "report-2",
+ "sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+ }
+ ]
+ }
+ ],
+ "evidence": [
+ {
+ "id": "ev-fail",
+ "title": "Reconstruction report v1",
+ "kind": "report",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "FAIL",
+ "checkId": "chk-recon-1",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "report-1",
+ "sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ },
+ "parentIds": [],
+ "childIds": ["ev-pass"],
+ "sizeBytes": 512,
+ "timestamp": "2026-09-10T00:00:00Z",
+ "supersededBy": "ev-pass",
+ "supersedes": null
+ },
+ {
+ "id": "ev-pass",
+ "title": "Reconstruction report v2",
+ "kind": "report",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "PASS",
+ "checkId": "chk-recon-2",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "report-2",
+ "sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+ },
+ "parentIds": ["ev-fail"],
+ "childIds": [],
+ "sizeBytes": 640,
+ "timestamp": "2026-09-12T00:00:00Z",
+ "supersededBy": null,
+ "supersedes": "ev-fail"
+ }
+ ],
+ "commands": []
+}
diff --git a/frontend/src/data/validate.ts b/frontend/src/data/validate.ts
new file mode 100644
index 00000000..95a43925
--- /dev/null
+++ b/frontend/src/data/validate.ts
@@ -0,0 +1,291 @@
+import {
+ AVAILABILITIES,
+ CHECK_RESULTS,
+ DATA_MODES,
+ SCOPES,
+ SNAPSHOT_SCHEMA,
+ SnapshotError,
+ type CheckResult,
+ type CheckView,
+ type CommandView,
+ type DataMode,
+ type EvidenceItem,
+ type EvidenceScope,
+ type PipelineStep,
+ type ReleaseAvailability,
+ type ReleaseRole,
+ type ReleaseView,
+ type Snapshot,
+ type SnapshotSummary,
+ type SourceReference,
+} from './contracts';
+
+const RESULT_ALIASES: Record = {
+ passed: 'PASS',
+ pass: 'PASS',
+ failed: 'FAIL',
+ fail: 'FAIL',
+ pending: 'NOT_RUN',
+ not_run: 'NOT_RUN',
+ unavailable: 'UNAVAILABLE',
+ unsupported: 'UNSUPPORTED',
+};
+
+function isRecord(value: unknown): value is Record {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+}
+
+function asString(value: unknown, field: string): string {
+ if (typeof value !== 'string' || value.trim() === '') {
+ throw new SnapshotError(`Missing or empty string: ${field}`);
+ }
+ return value;
+}
+
+function asNullableString(value: unknown, field: string): string | null {
+ if (value === null || value === undefined) return null;
+ if (typeof value !== 'string') {
+ throw new SnapshotError(`${field} must be a string or null`);
+ }
+ return value;
+}
+
+function asBoolean(value: unknown, field: string): boolean {
+ if (typeof value !== 'boolean') {
+ throw new SnapshotError(`${field} must be a boolean`);
+ }
+ return value;
+}
+
+function asNumberOrNull(value: unknown, field: string): number | null {
+ if (value === null || value === undefined) return null;
+ if (typeof value !== 'number' || Number.isNaN(value)) {
+ throw new SnapshotError(`${field} must be a number or null`);
+ }
+ return value;
+}
+
+function asArray(value: unknown, field: string): unknown[] {
+ if (!Array.isArray(value)) {
+ throw new SnapshotError(`${field} must be an array`);
+ }
+ return value;
+}
+
+function asStringList(value: unknown, field: string): string[] {
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
+ throw new SnapshotError(`${field} must be a list of strings`);
+ }
+ return value;
+}
+
+function parseResult(value: unknown, field: string): CheckResult {
+ if (typeof value !== 'string') {
+ throw new SnapshotError(`${field} is not a result string`);
+ }
+ if ((CHECK_RESULTS as readonly string[]).includes(value)) {
+ return value as CheckResult;
+ }
+ const alias = RESULT_ALIASES[value.toLowerCase()];
+ if (alias) return alias;
+ throw new SnapshotError(
+ `${field} value ${JSON.stringify(value)} is not a known check result`,
+ );
+}
+
+function parseScope(value: unknown, field: string): EvidenceScope {
+ if (typeof value !== 'string' || !(SCOPES as readonly string[]).includes(value)) {
+ throw new SnapshotError(`${field} must be fixture, pilot, or production`);
+ }
+ return value as EvidenceScope;
+}
+
+function parseMode(value: unknown): DataMode {
+ if (typeof value !== 'string' || !(DATA_MODES as readonly string[]).includes(value)) {
+ throw new SnapshotError('mode must be fixture or public-snapshot');
+ }
+ return value as DataMode;
+}
+
+function parseAvailability(value: unknown, field: string): ReleaseAvailability {
+ if (
+ typeof value !== 'string' ||
+ !(AVAILABILITIES as readonly string[]).includes(value)
+ ) {
+ throw new SnapshotError(`${field} is not a known availability`);
+ }
+ return value as ReleaseAvailability;
+}
+
+function parseSource(value: unknown, field: string): SourceReference {
+ if (!isRecord(value)) throw new SnapshotError(`${field} must be an object`);
+ const url = asString(value.url, `${field}.url`);
+ if (url.startsWith('javascript:') || url.startsWith('data:')) {
+ throw new SnapshotError(`${field}.url uses a disallowed scheme`);
+ }
+ return {
+ url,
+ revision: asNullableString(value.revision, `${field}.revision`),
+ path: asNullableString(value.path, `${field}.path`),
+ sha256: asNullableString(value.sha256, `${field}.sha256`),
+ };
+}
+
+function parseCheck(value: unknown, index: number): CheckView {
+ if (!isRecord(value)) throw new SnapshotError(`checks[${index}] must be an object`);
+ const result = parseResult(value.result, `checks[${index}].result`);
+ const check: CheckView = {
+ id: asString(value.id, `checks[${index}].id`),
+ profile: asString(value.profile, `checks[${index}].profile`),
+ phase: asString(value.phase, `checks[${index}].phase`),
+ scope: parseScope(value.scope, `checks[${index}].scope`),
+ result,
+ explanation: asString(value.explanation, `checks[${index}].explanation`),
+ performedBy: asNullableString(value.performedBy, `checks[${index}].performedBy`),
+ attestedBy: asNullableString(value.attestedBy, `checks[${index}].attestedBy`),
+ locallyRecomputed: asBoolean(
+ value.locallyRecomputed,
+ `checks[${index}].locallyRecomputed`,
+ ),
+ evidence: Array.isArray(value.evidence)
+ ? value.evidence.map((ref, j) => parseSource(ref, `checks[${index}].evidence[${j}]`))
+ : [],
+ };
+ if (result === 'PASS' && check.evidence.length === 0) {
+ throw new SnapshotError(`checks[${index}] PASS is missing evidence references`);
+ }
+ return check;
+}
+
+function parseEvidence(value: unknown, index: number): EvidenceItem {
+ if (!isRecord(value)) throw new SnapshotError(`evidence[${index}] must be an object`);
+ return {
+ id: asString(value.id, `evidence[${index}].id`),
+ title: asString(value.title, `evidence[${index}].title`),
+ kind: asString(value.kind, `evidence[${index}].kind`),
+ phase: asString(value.phase, `evidence[${index}].phase`),
+ scope: parseScope(value.scope, `evidence[${index}].scope`),
+ result: parseResult(value.result, `evidence[${index}].result`),
+ checkId: asNullableString(value.checkId, `evidence[${index}].checkId`),
+ source: parseSource(value.source, `evidence[${index}].source`),
+ parentIds: asStringList(value.parentIds ?? [], `evidence[${index}].parentIds`),
+ childIds: asStringList(value.childIds ?? [], `evidence[${index}].childIds`),
+ sizeBytes: asNumberOrNull(value.sizeBytes, `evidence[${index}].sizeBytes`),
+ timestamp: asNullableString(value.timestamp, `evidence[${index}].timestamp`),
+ supersededBy: asNullableString(value.supersededBy, `evidence[${index}].supersededBy`),
+ supersedes: asNullableString(value.supersedes, `evidence[${index}].supersedes`),
+ };
+}
+
+function parsePipeline(value: unknown, index: number): PipelineStep {
+ if (!isRecord(value)) throw new SnapshotError(`pipeline[${index}] must be an object`);
+ return {
+ id: asString(value.id, `pipeline[${index}].id`),
+ title: asString(value.title, `pipeline[${index}].title`),
+ phase: asString(value.phase, `pipeline[${index}].phase`),
+ result: parseResult(value.result, `pipeline[${index}].result`),
+ scope: parseScope(value.scope, `pipeline[${index}].scope`),
+ checkId: asNullableString(value.checkId, `pipeline[${index}].checkId`),
+ };
+}
+
+function parseRelease(value: unknown, index: number): ReleaseView {
+ if (!isRecord(value)) throw new SnapshotError(`releases[${index}] must be an object`);
+ const role = asString(value.role, `releases[${index}].role`);
+ if (role !== 'base' && role !== 'chat') {
+ throw new SnapshotError(`releases[${index}].role must be base or chat`);
+ }
+ return {
+ id: asString(value.id, `releases[${index}].id`),
+ role: role as ReleaseRole,
+ availability: parseAvailability(value.availability, `releases[${index}].availability`),
+ title: asString(value.title, `releases[${index}].title`),
+ parentReleaseId: asNullableString(
+ value.parentReleaseId,
+ `releases[${index}].parentReleaseId`,
+ ),
+ repository: asNullableString(value.repository, `releases[${index}].repository`),
+ revision: asNullableString(value.revision, `releases[${index}].revision`),
+ releaseRoot: asNullableString(value.releaseRoot, `releases[${index}].releaseRoot`),
+ notes: asString(value.notes, `releases[${index}].notes`),
+ };
+}
+
+function parseCommand(value: unknown, index: number): CommandView {
+ if (!isRecord(value)) throw new SnapshotError(`commands[${index}] must be an object`);
+ return {
+ profile: asString(value.profile, `commands[${index}].profile`),
+ text: asString(value.text, `commands[${index}].text`),
+ prerequisites: asString(value.prerequisites, `commands[${index}].prerequisites`),
+ sourceRevision: asNullableString(
+ value.sourceRevision,
+ `commands[${index}].sourceRevision`,
+ ),
+ expectedScope: asString(value.expectedScope, `commands[${index}].expectedScope`),
+ resourceNote: asString(value.resourceNote, `commands[${index}].resourceNote`),
+ validated: asBoolean(value.validated, `commands[${index}].validated`),
+ };
+}
+
+function parseSummary(value: unknown): SnapshotSummary {
+ if (!isRecord(value)) throw new SnapshotError('summary must be an object');
+ return {
+ headline: asString(value.headline, 'summary.headline'),
+ mayClaim: asStringList(value.mayClaim, 'summary.mayClaim'),
+ mayNotClaim: asStringList(value.mayNotClaim, 'summary.mayNotClaim'),
+ workflow: asString(value.workflow, 'summary.workflow'),
+ };
+}
+
+export function validateSnapshot(raw: unknown): Snapshot {
+ if (!isRecord(raw)) throw new SnapshotError('Snapshot must be a JSON object');
+ const schemaVersion = asString(raw.schemaVersion, 'schemaVersion');
+ if (schemaVersion !== SNAPSHOT_SCHEMA) {
+ throw new SnapshotError(
+ `Unsupported schema ${schemaVersion}; expected ${SNAPSHOT_SCHEMA}`,
+ );
+ }
+
+ const checks = asArray(raw.checks, 'checks').map((item, i) =>
+ parseCheck(item, i),
+ );
+ const evidence = asArray(raw.evidence, 'evidence').map((item, i) =>
+ parseEvidence(item, i),
+ );
+ const releases = asArray(raw.releases, 'releases').map((item, i) =>
+ parseRelease(item, i),
+ );
+ const pipeline = asArray(raw.pipeline, 'pipeline').map((item, i) =>
+ parsePipeline(item, i),
+ );
+ const commands = asArray(raw.commands, 'commands').map((item, i) =>
+ parseCommand(item, i),
+ );
+
+ if (checks.length === 0) {
+ throw new SnapshotError(
+ 'Snapshot has no checks; an empty docket cannot be shown as success',
+ );
+ }
+
+ return {
+ schemaVersion,
+ generatedAt: asString(raw.generatedAt, 'generatedAt'),
+ mode: parseMode(raw.mode),
+ source: parseSource(raw.source, 'source'),
+ summary: parseSummary(raw.summary),
+ pipeline,
+ releases,
+ checks,
+ evidence,
+ commands,
+ };
+}
+
+export function releaseIsDownloadable(release: ReleaseView): boolean {
+ return (
+ release.availability === 'available' &&
+ Boolean(release.repository) &&
+ Boolean(release.revision)
+ );
+}
diff --git a/frontend/src/inference/DisabledAdapter.ts b/frontend/src/inference/DisabledAdapter.ts
new file mode 100644
index 00000000..818bb34b
--- /dev/null
+++ b/frontend/src/inference/DisabledAdapter.ts
@@ -0,0 +1,17 @@
+import { GenerationUnavailable, type InferenceAdapter, type InferenceCapabilities } from './types';
+
+export class DisabledAdapter implements InferenceAdapter {
+ async capabilities(): Promise {
+ return {
+ mock: false,
+ available: false,
+ releaseIds: [],
+ modes: ['completion', 'conversation'],
+ note: 'Generation is unavailable until a verified model release is connected',
+ };
+ }
+
+ async generate(): Promise {
+ throw new GenerationUnavailable();
+ }
+}
diff --git a/frontend/src/inference/MockAdapter.ts b/frontend/src/inference/MockAdapter.ts
new file mode 100644
index 00000000..d3895695
--- /dev/null
+++ b/frontend/src/inference/MockAdapter.ts
@@ -0,0 +1,40 @@
+import {
+ IdentityMismatch,
+ type GenerationRequest,
+ type GenerationResponse,
+ type InferenceAdapter,
+ type InferenceCapabilities,
+} from './types';
+
+const DISCLAIMER =
+ 'Example UI response — not generated by a released OpenVerifiableLLM model';
+
+export class MockAdapter implements InferenceAdapter {
+ private readonly forceWrongId: boolean;
+
+ constructor(forceWrongId = false) {
+ this.forceWrongId = forceWrongId;
+ }
+
+ async capabilities(): Promise {
+ return {
+ mock: true,
+ available: true,
+ releaseIds: ['fixture-base'],
+ modes: ['completion', 'conversation'],
+ note: DISCLAIMER,
+ };
+ }
+
+ async generate(request: GenerationRequest): Promise {
+ const releaseId = this.forceWrongId ? 'other-release' : request.releaseId;
+ if (releaseId !== request.releaseId) {
+ throw new IdentityMismatch();
+ }
+ return {
+ text: 'This is a scripted fixture reply so the composer can be tested.',
+ releaseId,
+ disclaimer: DISCLAIMER,
+ };
+ }
+}
diff --git a/frontend/src/inference/types.ts b/frontend/src/inference/types.ts
new file mode 100644
index 00000000..dbf0ba74
--- /dev/null
+++ b/frontend/src/inference/types.ts
@@ -0,0 +1,41 @@
+export type InferenceMode = 'completion' | 'conversation';
+
+export interface InferenceCapabilities {
+ mock: boolean;
+ available: boolean;
+ releaseIds: string[];
+ modes: InferenceMode[];
+ note: string;
+}
+
+export interface GenerationRequest {
+ releaseId: string;
+ mode: InferenceMode;
+ prompt?: string;
+ messages?: { role: 'user' | 'assistant'; content: string }[];
+}
+
+export interface GenerationResponse {
+ text: string;
+ releaseId: string;
+ disclaimer: string;
+}
+
+export class GenerationUnavailable extends Error {
+ constructor(message = 'Generation is unavailable until a verified model release is connected') {
+ super(message);
+ this.name = 'GenerationUnavailable';
+ }
+}
+
+export class IdentityMismatch extends Error {
+ constructor() {
+ super('Adapter returned a different release identity than the one selected');
+ this.name = 'IdentityMismatch';
+ }
+}
+
+export interface InferenceAdapter {
+ capabilities(): Promise;
+ generate(request: GenerationRequest, signal?: AbortSignal): Promise;
+}
diff --git a/frontend/src/layout/AppShell.tsx b/frontend/src/layout/AppShell.tsx
new file mode 100644
index 00000000..5e8a2074
--- /dev/null
+++ b/frontend/src/layout/AppShell.tsx
@@ -0,0 +1,102 @@
+import { Link, NavLink, Outlet } from 'react-router-dom';
+import { SNAPSHOT_IDS } from '../data/adapters/snapshotAdapter';
+import { useSnapSearch } from '../nav';
+import { useSnapshotState } from '../snapshot';
+import { ClaimStrip } from './ClaimStrip';
+
+const LINKS = [
+ { to: '/', label: 'Overview', end: true },
+ { to: '/evidence', label: 'Evidence' },
+ { to: '/releases', label: 'Release' },
+ { to: '/verify', label: 'Verify' },
+ { to: '/try', label: 'Inference' },
+];
+
+export function AppShell() {
+ const { snapshot, error, id } = useSnapshotState();
+ const search = useSnapSearch();
+
+ return (
+ <>
+ {
+ event.preventDefault();
+ document.getElementById('main')?.focus();
+ }}
+ >
+ Skip to content
+
+
+
+ {snapshot?.mode === 'fixture' ? (
+
+ {id === 'public'
+ ? 'Draft status — waiting for maintainer approval'
+ : 'Test data — not the real project status'}
+
+ ) : null}
+
+ {snapshot ? : null}
+
+
+ {error ? (
+
+ Data error
+ This record cannot be shown as a pass
+ {error.message}
+
+ If the saved record is empty or broken, that is an error — not a successful check.
+
+
+ ) : (
+
+ )}
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/layout/ClaimStrip.tsx b/frontend/src/layout/ClaimStrip.tsx
new file mode 100644
index 00000000..2e3c29d5
--- /dev/null
+++ b/frontend/src/layout/ClaimStrip.tsx
@@ -0,0 +1,28 @@
+import type { Snapshot } from '../data/contracts';
+
+export function ClaimStrip({ snapshot }: { snapshot: Snapshot }) {
+ const revision = snapshot.source.revision ?? 'unspecified';
+ return (
+
+
+
+
What we can say today
+
+
+ {snapshot.summary.mayClaim.join(' · ') || 'nothing beyond the snapshot'}
+
+
+
+
+
What we cannot say
+
{snapshot.summary.mayNotClaim.join(' · ')}
+
+
+ Saved {snapshot.generatedAt} ·{' '}
+ {revision.slice(0, 7)}
+ {' · '}this website did not re-run the checks
+
+
+
+ );
+}
diff --git a/frontend/src/layout/StatusMark.tsx b/frontend/src/layout/StatusMark.tsx
new file mode 100644
index 00000000..a3fb2c6d
--- /dev/null
+++ b/frontend/src/layout/StatusMark.tsx
@@ -0,0 +1,26 @@
+import type { CheckResult } from '../data/contracts';
+
+const LABELS: Record = {
+ PASS: 'PASS',
+ FAIL: 'FAIL',
+ NOT_RUN: 'NOT RUN',
+ UNAVAILABLE: 'UNAVAILABLE',
+ UNSUPPORTED: 'UNSUPPORTED',
+};
+
+export function StatusMark({ result }: { result: CheckResult }) {
+ const tone =
+ result === 'PASS' ? 'pass' : result === 'FAIL' ? 'fail' : 'hold';
+ return (
+
+
+ {LABELS[result]}
+
+ );
+}
+
+export function pipelineClass(result: CheckResult): string {
+ if (result === 'PASS') return 'pass';
+ if (result === 'FAIL') return 'fail';
+ return 'hold';
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 00000000..82a4f9ec
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,13 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App';
+import './styles/base.css';
+
+const root = document.getElementById('root');
+if (!root) throw new Error('root element missing');
+
+createRoot(root).render(
+
+
+ ,
+);
diff --git a/frontend/src/nav.ts b/frontend/src/nav.ts
new file mode 100644
index 00000000..cdbd3184
--- /dev/null
+++ b/frontend/src/nav.ts
@@ -0,0 +1,7 @@
+import { useSearchParams } from 'react-router-dom';
+
+export function useSnapSearch(): string {
+ const [params] = useSearchParams();
+ const search = params.toString();
+ return search ? `?${search}` : '';
+}
diff --git a/frontend/src/pages/EvidenceDetailPage.tsx b/frontend/src/pages/EvidenceDetailPage.tsx
new file mode 100644
index 00000000..8668af55
--- /dev/null
+++ b/frontend/src/pages/EvidenceDetailPage.tsx
@@ -0,0 +1,138 @@
+import { useState } from 'react';
+import { Link, useParams } from 'react-router-dom';
+import { PHASE_LABEL, SCOPE_LABEL } from '../content/copy';
+import { StatusMark } from '../layout/StatusMark';
+import { useSnapSearch } from '../nav';
+import { useSnapshot } from '../snapshot';
+
+export function EvidenceDetailPage() {
+ const { id } = useParams();
+ const snapshot = useSnapshot();
+ const search = useSnapSearch();
+ const item = snapshot.evidence.find((row) => row.id === id);
+ const check = item?.checkId
+ ? snapshot.checks.find((row) => row.id === item.checkId)
+ : undefined;
+ const [copied, setCopied] = useState(false);
+
+ if (!item) {
+ return (
+
+ This item is not in the record
+ A missing item is shown as missing — not as a passed check.
+
+ Back to evidence
+
+
+ );
+ }
+
+ const digest = item.source.sha256;
+ const knownIds = new Set(snapshot.evidence.map((row) => row.id));
+
+ async function copyDigest() {
+ if (!digest) return;
+ await navigator.clipboard.writeText(digest);
+ setCopied(true);
+ }
+
+ return (
+
+
+ Evidence
+
+ {item.title}
+
+ · {SCOPE_LABEL[item.scope]} · {item.kind} ·{' '}
+ {PHASE_LABEL[item.phase] ?? item.phase}
+
+ {item.supersededBy ? (
+
+ Replaced by{' '}
+ {item.supersededBy}
+
+ ) : null}
+ {item.supersedes ? (
+
+ Replaces {item.supersedes}
+
+ ) : null}
+
+ What this sits on
+
+ {item.parentIds.length === 0 ? (
+ No earlier item linked
+ ) : (
+ item.parentIds.map((parent) =>
+ knownIds.has(parent) ? (
+
+ Comes from: {parent}
+
+ ) : (
+
+ Missing link — {parent} is not in this record
+
+ ),
+ )
+ )}
+
+ This item: {item.id}
+
+ {item.childIds.map((child) => (
+
+ Used by:{' '}
+ {knownIds.has(child) ? (
+ {child}
+ ) : (
+ child
+ )}
+
+ ))}
+
+
+ Original location
+
+
+ {item.source.url}
+
+
+ {item.source.path ? Path: {item.source.path}
: null}
+ {item.source.revision ? (
+ Revision {item.source.revision}
+ ) : null}
+ {digest ? (
+
+ {digest} {' '}
+ void copyDigest()}>
+ {copied ? 'Copied digest' : 'Copy digest'}
+
+
+ ) : (
+ No checksum (SHA-256) is listed in this record.
+ )}
+ {item.timestamp ? (
+ Timestamp {item.timestamp} (UTC as published)
+ ) : null}
+
+ {check ? (
+ <>
+ Linked check
+ {check.explanation}
+
+ Who ran it (technical)
+
+ Check type: {check.profile}. Run by {check.performedBy ?? 'not listed'}.
+ Signed off by {check.attestedBy ?? 'not listed'}. Recomputed in this browser:{' '}
+ {check.locallyRecomputed ? 'yes' : 'no'}.
+
+
+ >
+ ) : null}
+
+
+ Raw JSON (display only)
+ {JSON.stringify(item, null, 2)}
+
+
+ );
+}
diff --git a/frontend/src/pages/EvidenceListPage.tsx b/frontend/src/pages/EvidenceListPage.tsx
new file mode 100644
index 00000000..01c97409
--- /dev/null
+++ b/frontend/src/pages/EvidenceListPage.tsx
@@ -0,0 +1,196 @@
+import { useMemo, useState } from 'react';
+import { Link } from 'react-router-dom';
+import {
+ CHECK_RESULTS,
+ SCOPES,
+ type CheckResult,
+ type EvidenceItem,
+} from '../data/contracts';
+import { PHASE_LABEL, SCOPE_LABEL } from '../content/copy';
+import { StatusMark } from '../layout/StatusMark';
+import { useSnapSearch } from '../nav';
+import { useSnapshot } from '../snapshot';
+
+const PHASE_ORDER = [
+ 'source',
+ 'prepare',
+ 'train',
+ 'replay',
+ 'release',
+ 'other',
+] as const;
+
+const PHASE_TITLE: Record = {
+ source: PHASE_LABEL.source,
+ prepare: PHASE_LABEL.prepare,
+ train: PHASE_LABEL.train,
+ replay: PHASE_LABEL.replay,
+ release: PHASE_LABEL.release,
+ other: PHASE_LABEL.other,
+};
+
+function formatSize(bytes: number | null): string {
+ if (bytes === null) return '—';
+ if (bytes < 1024) return `${bytes} B`;
+ return `${(bytes / 1024).toFixed(1)} KB`;
+}
+
+function phaseKey(phase: string): string {
+ return (PHASE_ORDER as readonly string[]).includes(phase) ? phase : 'other';
+}
+
+function Table({ rows, search }: { rows: EvidenceItem[]; search: string }) {
+ if (rows.length === 0) return No rows in this group.
;
+ return (
+
+
+
+ Title
+ Kind
+ Scope
+ Result
+ When
+ Size
+
+
+
+ {rows.map((item) => (
+
+
+
+ {item.title}
+
+
+ {item.kind}
+ {SCOPE_LABEL[item.scope]}
+
+
+
+ {item.timestamp ?? '—'}
+ {formatSize(item.sizeBytes)}
+
+ ))}
+
+
+ );
+}
+
+export function EvidenceListPage() {
+ const snapshot = useSnapshot();
+ const search = useSnapSearch();
+ const [result, setResult] = useState('all');
+ const [scope, setScope] = useState('all');
+ const [kind, setKind] = useState('all');
+ const [phase, setPhase] = useState('all');
+
+ const kinds = useMemo(
+ () => [...new Set(snapshot.evidence.map((item) => item.kind))].sort(),
+ [snapshot.evidence],
+ );
+ const phases = useMemo(
+ () => [...new Set(snapshot.evidence.map((item) => item.phase))].sort(),
+ [snapshot.evidence],
+ );
+
+ const filtered = useMemo(() => {
+ return snapshot.evidence.filter((item) => {
+ if (result !== 'all' && item.result !== result) return false;
+ if (scope !== 'all' && item.scope !== scope) return false;
+ if (kind !== 'all' && item.kind !== kind) return false;
+ if (phase !== 'all' && item.phase !== phase) return false;
+ return true;
+ });
+ }, [snapshot.evidence, result, scope, kind, phase]);
+
+ const grouped = PHASE_ORDER.map((key) => ({
+ key,
+ title: PHASE_TITLE[key],
+ rows: filtered.filter((item) => phaseKey(item.phase) === key),
+ })).filter((g) => g.rows.length > 0);
+
+ const pass = filtered.filter((i) => i.result === 'PASS').length;
+
+ return (
+
+ Public record
+ Evidence
+
+ What. The public record: files and reports the project published.
+
+
+ Why. Evidence is what verification later checks. This page only shows
+ links; it does not re-run training or generate text.
+
+
+ Current status. Rows below are this saved record. Open one for who ran the
+ check and the technical fields.
+
+
+
+ {filtered.length}
+ items shown
+
+
+ {pass}
+ PASS in view
+
+
+
+
+ Result
+ setResult(e.target.value)}>
+ All
+ {CHECK_RESULTS.map((item) => (
+
+ {item}
+
+ ))}
+
+
+
+ Scope
+ setScope(e.target.value)}>
+ All
+ {SCOPES.map((item) => (
+
+ {SCOPE_LABEL[item]}
+
+ ))}
+
+
+
+ Kind
+ setKind(e.target.value)}>
+ All
+ {kinds.map((item) => (
+
+ {item}
+
+ ))}
+
+
+
+ Phase
+ setPhase(e.target.value)}>
+ All
+ {phases.map((item) => (
+
+ {item}
+
+ ))}
+
+
+
+ {filtered.length === 0 ? (
+ No rows match these filters. That is not a passed check.
+ ) : (
+ grouped.map((group) => (
+
+ ))
+ )}
+
+ );
+}
diff --git a/frontend/src/pages/Journey.tsx b/frontend/src/pages/Journey.tsx
new file mode 100644
index 00000000..628b544a
--- /dev/null
+++ b/frontend/src/pages/Journey.tsx
@@ -0,0 +1,73 @@
+import { useState } from 'react';
+import { JOURNEY, SCOPE_LABEL, worstResult } from '../content/copy';
+import type { CheckResult, PipelineStep, Snapshot } from '../data/contracts';
+import { StatusMark } from '../layout/StatusMark';
+
+function stageResult(snapshot: Snapshot, pipelineIds: string[], checkProfile: string | null): CheckResult {
+ const fromSteps = pipelineIds.map(
+ (id) => snapshot.pipeline.find((step) => step.id === id)?.result ?? 'NOT_RUN',
+ );
+ const fromCheck = checkProfile
+ ? snapshot.checks.filter((c) => c.profile === checkProfile).map((c) => c.result)
+ : [];
+ return worstResult([...fromSteps, ...fromCheck]);
+}
+
+function stepsFor(snapshot: Snapshot, ids: string[]): PipelineStep[] {
+ return ids
+ .map((id) => snapshot.pipeline.find((step) => step.id === id))
+ .filter((step): step is PipelineStep => Boolean(step));
+}
+
+const KIND_LINE = {
+ evidence: 'Project evidence',
+ verification: 'Verification',
+ inference: 'Inference',
+} as const;
+
+export function Journey({ snapshot }: { snapshot: Snapshot }) {
+ const [openId, setOpenId] = useState(null);
+
+ return (
+
+ {JOURNEY.map((stage) => {
+ const result = stageResult(snapshot, stage.pipelineIds, stage.checkProfile);
+ const steps = stepsFor(snapshot, stage.pipelineIds);
+ const isOpen = openId === stage.id;
+ return (
+
+ {stage.label}
+
+ {stage.what}
+
+ {
+ event.preventDefault();
+ setOpenId((current) => (current === stage.id ? null : stage.id));
+ }}
+ >
+ {isOpen ? 'Hide steps' : 'Steps'}
+
+ {isOpen ? (
+ <>
+ {stage.why}
+ {KIND_LINE[stage.kind]}
+ {steps.length === 0 && stage.checkProfile ? (
+ Check: {stage.checkProfile}
+ ) : null}
+
+ {steps.map((step) => (
+
+ {step.title} — · {SCOPE_LABEL[step.scope]}
+
+ ))}
+
+ >
+ ) : null}
+
+
+ );
+ })}
+
+ );
+}
diff --git a/frontend/src/pages/OverviewPage.tsx b/frontend/src/pages/OverviewPage.tsx
new file mode 100644
index 00000000..908c8912
--- /dev/null
+++ b/frontend/src/pages/OverviewPage.tsx
@@ -0,0 +1,82 @@
+import { Link } from 'react-router-dom';
+import { WHY } from '../content/copy';
+import { Journey } from './Journey';
+import { useSnapSearch } from '../nav';
+import { useSnapshot } from '../snapshot';
+
+export function OverviewPage() {
+ const snapshot = useSnapshot();
+ const search = useSnapSearch();
+ const pass = snapshot.checks.filter((c) => c.result === 'PASS').length;
+ const fail = snapshot.checks.filter((c) => c.result === 'FAIL').length;
+ const pending = snapshot.checks.filter((c) => c.result !== 'PASS' && c.result !== 'FAIL').length;
+ const released = snapshot.releases.filter((r) => r.availability === 'available').length;
+
+ return (
+
+
+
+
AOSSIE · OpenVerifiableLLM
+
A public record of how a small open model is made.
+
+ Data, training, checkpoints, and released weights — inspectable, and later
+ reproducible. This site shows the record; it does not train or chat.
+
+
+
+ Explore evidence
+
+
+ View releases
+
+
+ How verification works
+
+
+
+
+ Current status
+ No model release
+
+
+ {pass}
+ checks passed
+
+
+ {fail}
+ checks failed
+
+
+ {pending}
+ checks pending
+
+
+ {released}
+ releases
+
+
+
+ Record {snapshot.generatedAt} ·{' '}
+
+ {(snapshot.source.revision ?? 'source').slice(0, 7)}
+
+
+ Check counts are separate from the pipeline stages below.
+
+
+
+ Why
+
+ {WHY.map((item) => (
+
+ {item.title}
+ {item.body}
+
+ ))}
+
+
+ Data → Preparation → Training → Replay → Release → Inference
+
+
+ );
+}
diff --git a/frontend/src/pages/ReleasesPage.tsx b/frontend/src/pages/ReleasesPage.tsx
new file mode 100644
index 00000000..b9019d0e
--- /dev/null
+++ b/frontend/src/pages/ReleasesPage.tsx
@@ -0,0 +1,74 @@
+import { releaseIsDownloadable } from '../data/validate';
+import { useSnapshot } from '../snapshot';
+
+export function ReleasesPage() {
+ const snapshot = useSnapshot();
+ return (
+
+ Release
+ Model files
+
+ What. Two published models are planned: a base model trained on Wikipedia,
+ then a chat model trained on top of it.
+
+
+ Why. Without a public folder, a fixed version, and checksums, there are no
+ weights for anyone else to download or check.
+
+
+ Current status. Neither release is listed. Download and generate stay off.
+ Listing files here later would still not mean complete replay had passed.
+
+
+ {snapshot.releases.map((release) => {
+ const ready = releaseIsDownloadable(release);
+ const isChat = release.role === 'chat';
+ return (
+
+ {isChat ? 'Chat model' : 'Base model'}
+ {release.title}
+
+ Status: {release.availability === 'not-released' ? 'not released' : release.availability}
+
+ {release.parentReleaseId ? (
+ Depends on the base Wikipedia model.
+ ) : (
+ Wikipedia-trained model, when published.
+ )}
+ {release.notes}
+
+ Technical fields
+
+ Repository: {release.repository ?? 'not set'}
+
+ Revision: {release.revision ?? 'not set'}
+
+ Release root: {release.releaseRoot ?? 'not set'}
+
+
+
+ {ready && release.repository ? (
+
+ Download
+
+ ) : (
+
+ Download
+
+ )}{' '}
+
+ Generate
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/frontend/src/pages/TryPage.tsx b/frontend/src/pages/TryPage.tsx
new file mode 100644
index 00000000..7eae5e1b
--- /dev/null
+++ b/frontend/src/pages/TryPage.tsx
@@ -0,0 +1,45 @@
+import { useSnapshot } from '../snapshot';
+
+export function TryPage() {
+ const snapshot = useSnapshot();
+ const chat = snapshot.releases.find((item) => item.role === 'chat');
+
+ return (
+
+ Inference
+ Try
+
+ What. Inference is using released model files to produce text — including a
+ later, locked chat box on this page.
+
+
+ Why. That is a different claim from evidence (the public record) and from
+ verification (rebuilding or re-running training). A live chat would not inspect data or
+ checkpoints, and it would not make answers true.
+
+
+ Current status. Chat model: {chat?.availability === 'available' ? 'listed' : 'not released'}.
+ Send is off. This page will not fall back to another model.
+
+
+
+ Chat
+
+
+ Message
+
+
+
+ Send
+
+
+
+ Technical note
+
+ A labelled mock adapter exists in tests only. The public build does not call it.
+ Inference reproduction, when it exists, is a saved-example check — not this composer.
+
+
+
+ );
+}
diff --git a/frontend/src/pages/VerifyPage.tsx b/frontend/src/pages/VerifyPage.tsx
new file mode 100644
index 00000000..9543fd4a
--- /dev/null
+++ b/frontend/src/pages/VerifyPage.tsx
@@ -0,0 +1,109 @@
+import type { CheckResult } from '../data/contracts';
+import { FAQ, LAYERS, SCOPE_LABEL } from '../content/copy';
+import { StatusMark } from '../layout/StatusMark';
+import { useSnapshot } from '../snapshot';
+
+const PROFILES: {
+ id: string;
+ title: string;
+ means: string;
+ doesNot: string;
+ technical: string;
+}[] = [
+ {
+ id: 'artifact-identity',
+ title: 'Artifact identity',
+ means: 'The published files match their listed checksums, and they were published by the expected project account.',
+ doesNot: 'Does not prove how those files were trained.',
+ technical: 'artifact and publisher identity',
+ },
+ {
+ id: 'data-reconstruction',
+ title: 'Data reconstruction',
+ means: 'Starting from the public source files, the same cleaned training text can be produced.',
+ doesNot: 'Does not prove the downloaded model is the result of that training.',
+ technical: 'data reconstruction',
+ },
+ {
+ id: 'sampled-replay',
+ title: 'Sampled replay',
+ means: 'Only the listed slice of training was run again in the stated setup and matched.',
+ doesNot: 'Does not replace a complete replay.',
+ technical: 'sampled replay',
+ },
+ {
+ id: 'full-replay',
+ title: 'Complete replay',
+ means: 'Every listed step from data through training was rebuilt and run again on the stated computers, and the files matched.',
+ doesNot: 'This is not a click in the browser. It needs the stated machines and time. A cost estimate is not a bill.',
+ technical: 'complete end-to-end replay',
+ },
+ {
+ id: 'inference-reproduction',
+ title: 'Inference reproduction',
+ means: 'Using the identified model files and settings, a saved example output comes out again.',
+ doesNot: 'A live chat box is not this check, and it does not make answers true.',
+ technical: 'inference reproduction',
+ },
+];
+
+export function VerifyPage() {
+ const snapshot = useSnapshot();
+ return (
+
+ Verification
+ What each check means
+
+ Verification is not the same as the project’s evidence record, and not the same as
+ inference. Each profile below is a different claim. This page explains them; it does not
+ run them. No copy-paste command is shown until the maintainer supplies one that has been
+ tested.
+
+ {PROFILES.map((profile) => {
+ const check = snapshot.checks.find((item) => item.profile === profile.id);
+ const result: CheckResult = check?.result ?? 'NOT_RUN';
+ return (
+
+ {profile.title}
+
+ ·{' '}
+ {check ? SCOPE_LABEL[check.scope] : 'not in this record'}
+
+
+ If this passes: {profile.means}
+
+
+ It does not mean: {profile.doesNot}
+
+ {check ? {check.explanation}
: This record has no check of this kind yet.
}
+
+ Technical name
+ {profile.technical}
+
+ How to run it: instructions pending — no invented command.
+
+ );
+ })}
+
+ Evidence, verification, inference
+
+ {LAYERS.map((item) => (
+
+ {item.title}
+ {item.body}
+
+ ))}
+
+
+ Questions
+
+ {FAQ.map((item) => (
+
+ {item.q}
+ {item.a}
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/snapshot.tsx b/frontend/src/snapshot.tsx
new file mode 100644
index 00000000..40df8c1d
--- /dev/null
+++ b/frontend/src/snapshot.tsx
@@ -0,0 +1,53 @@
+import {
+ createContext,
+ useContext,
+ useMemo,
+ type ReactNode,
+} from 'react';
+import { useSearchParams } from 'react-router-dom';
+import {
+ loadSnapshot,
+ parseSnapshotId,
+ type SnapshotId,
+} from './data/adapters/snapshotAdapter';
+import { SnapshotError, type Snapshot } from './data/contracts';
+
+interface SnapshotState {
+ snapshot: Snapshot | null;
+ error: SnapshotError | null;
+ id: SnapshotId;
+}
+
+const Ctx = createContext(null);
+
+export function SnapshotProvider({ children }: { children: ReactNode }) {
+ const [params] = useSearchParams();
+ const id = parseSnapshotId(params.get('snapshot'));
+ const value = useMemo(() => {
+ try {
+ return { snapshot: loadSnapshot(id), error: null, id };
+ } catch (err) {
+ const error =
+ err instanceof SnapshotError
+ ? err
+ : new SnapshotError(err instanceof Error ? err.message : 'Invalid snapshot');
+ return { snapshot: null, error, id };
+ }
+ }, [id]);
+
+ return {children} ;
+}
+
+export function useSnapshotState(): SnapshotState {
+ const ctx = useContext(Ctx);
+ if (!ctx) throw new Error('SnapshotProvider missing');
+ return ctx;
+}
+
+export function useSnapshot(): Snapshot {
+ const { snapshot, error } = useSnapshotState();
+ if (error || !snapshot) {
+ throw error ?? new SnapshotError('Snapshot unavailable');
+ }
+ return snapshot;
+}
diff --git a/frontend/src/styles/base.css b/frontend/src/styles/base.css
new file mode 100644
index 00000000..b7a678f2
--- /dev/null
+++ b/frontend/src/styles/base.css
@@ -0,0 +1,676 @@
+@import './tokens.css';
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+ *,
+ *::before,
+ *::after {
+ animation: none !important;
+ transition: none !important;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--bg);
+ color: var(--ink);
+ font-family: var(--sans);
+ font-size: 1.05rem;
+ line-height: 1.55;
+}
+
+#root {
+ min-height: 100vh;
+}
+
+a {
+ color: var(--link);
+}
+
+a:focus-visible,
+button:focus-visible,
+select:focus-visible,
+summary:focus-visible {
+ outline: 2px solid var(--gold);
+ outline-offset: 3px;
+}
+
+.skip {
+ position: absolute;
+ left: 0.75rem;
+ top: -3.5rem;
+ z-index: 20;
+ background: var(--nav);
+ color: var(--nav-ink);
+ padding: 0.45rem 0.75rem;
+}
+
+.skip:focus {
+ top: 0.75rem;
+}
+
+.mast {
+ background: var(--nav);
+ color: var(--nav-ink);
+ position: sticky;
+ top: 0;
+ z-index: 10;
+}
+
+.mast-inner,
+.shell,
+.foot-inner {
+ max-width: var(--wide);
+ margin: 0 auto;
+ padding: 0 1.25rem;
+}
+
+.mast-inner {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem 1.5rem;
+ min-height: 4rem;
+}
+
+.wordmark {
+ color: inherit;
+ text-decoration: none;
+ font-family: var(--serif);
+ font-weight: 600;
+ font-size: 1.15rem;
+}
+
+.wordmark em {
+ font-style: normal;
+ color: var(--gold);
+}
+
+.wordmark span {
+ display: block;
+ font-family: var(--sans);
+ font-size: 0.72rem;
+ font-weight: 400;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: #b7c0b8;
+}
+
+.mast nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem 0.2rem;
+}
+
+.mast nav a {
+ color: #d7ddd8;
+ text-decoration: none;
+ padding: 0.35rem 0;
+ margin: 0 0.65rem;
+ border-bottom: 1px solid transparent;
+ font-size: 0.95rem;
+}
+
+.mast nav a:hover,
+.mast nav a[aria-current='page'] {
+ color: var(--nav-ink);
+ border-bottom-color: var(--gold);
+}
+
+.tape {
+ margin: 0;
+ padding: 0.4rem 1.25rem;
+ background: var(--tape);
+ color: #fff;
+ font-size: 0.8rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ text-align: center;
+}
+
+.claim {
+ background: var(--bg-2);
+ border-bottom: 1px solid var(--line);
+}
+
+.claim-inner {
+ max-width: var(--wide);
+ margin: 0 auto;
+ padding: 0.85rem 1.25rem;
+ display: grid;
+ gap: 0.35rem 2rem;
+}
+
+@media (min-width: 800px) {
+ .claim-inner {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+.claim .k {
+ margin: 0;
+ color: var(--mute);
+ font-size: 0.72rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.claim p {
+ margin: 0.15rem 0 0;
+}
+
+.claim .meta {
+ grid-column: 1 / -1;
+ margin-top: 0.35rem;
+ color: var(--mute);
+ font-family: var(--mono);
+ font-size: 0.78rem;
+}
+
+.page {
+ max-width: var(--wide);
+ margin: 0 auto;
+ padding: 1.75rem 1.25rem 4rem;
+}
+
+h1,
+h2,
+h3 {
+ font-family: var(--serif);
+ font-weight: 600;
+ line-height: 1.2;
+}
+
+h1 {
+ font-size: clamp(2rem, 4vw, 3rem);
+ margin: 0 0 0.75rem;
+}
+
+h2 {
+ font-size: 1.45rem;
+ margin: 2.4rem 0 0.8rem;
+}
+
+.kicker {
+ margin: 0 0 0.5rem;
+ color: var(--mute);
+ font-size: 0.8rem;
+ font-weight: 600;
+}
+
+.lede {
+ font-size: 1.2rem;
+ max-width: 40rem;
+ margin: 0 0 1rem;
+}
+
+.hold-line {
+ display: inline-block;
+ margin: 0 0 1rem;
+ padding: 0.2rem 0.55rem;
+ border: 1px solid var(--hold);
+ color: var(--hold);
+ font-weight: 600;
+ font-size: 0.88rem;
+}
+
+.hero {
+ display: grid;
+ gap: 1.5rem;
+}
+
+@media (min-width: 900px) {
+ .hero {
+ grid-template-columns: 1.3fr 0.9fr;
+ align-items: start;
+ }
+}
+
+.panel {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 1.1rem 1.15rem;
+}
+
+.panel h2,
+.panel h3 {
+ margin-top: 0;
+}
+
+.panel h3 {
+ font-size: 1.15rem;
+}
+
+.stats {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 0.75rem;
+}
+
+.stat strong {
+ display: block;
+ font-family: var(--mono);
+ font-size: 1.4rem;
+}
+
+.stat span {
+ color: var(--mute);
+ font-size: 0.85rem;
+}
+
+.actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.6rem;
+ margin: 1.2rem 0 0;
+}
+
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ text-decoration: none;
+ font-weight: 600;
+ padding: 0.55rem 0.95rem;
+ border-radius: 2px;
+}
+
+.btn-solid {
+ background: var(--ink);
+ color: var(--bg);
+ border: 1px solid var(--ink);
+}
+
+.btn-ghost {
+ border: 1px solid var(--ink);
+ color: var(--ink);
+}
+
+.why {
+ display: grid;
+ gap: 0;
+}
+
+.why article {
+ padding: 1rem 0;
+ border-top: 1px solid var(--line);
+}
+
+.why h3 {
+ margin: 0 0 0.35rem;
+ font-size: 1.15rem;
+}
+
+.pipeline {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.pipeline li {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ gap: 0.75rem;
+ align-items: center;
+ padding: 0.7rem 0;
+ border-top: 1px solid var(--line);
+}
+
+.journey {
+ list-style: none;
+ margin: 0.5rem 0 0;
+ padding: 0;
+ display: grid;
+ gap: 0.65rem;
+ align-items: start;
+}
+
+@media (min-width: 720px) {
+ .journey {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (min-width: 1100px) {
+ .journey {
+ grid-template-columns: repeat(6, minmax(0, 1fr));
+ gap: 0.5rem;
+ align-items: start;
+ }
+}
+
+.journey-card {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ border-top: 3px solid var(--line);
+ padding: 0.85rem 0.8rem 0.95rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+ min-width: 0;
+ align-self: start;
+}
+
+.journey-card.kind-evidence {
+ border-top-color: var(--ink);
+}
+
+.journey-card.kind-verification {
+ border-top-color: var(--green);
+}
+
+.journey-card.kind-inference {
+ border-top-color: var(--hold);
+}
+
+.journey-label {
+ margin: 0;
+ font-family: var(--sans);
+ font-size: 0.78rem;
+ font-weight: 700;
+}
+
+.journey-what,
+.journey-why,
+.journey-kind {
+ margin: 0;
+ font-size: 0.88rem;
+}
+
+.journey-kind {
+ color: var(--mute);
+ font-size: 0.78rem;
+}
+
+.journey-card details {
+ margin-top: auto;
+ font-size: 0.82rem;
+}
+
+.journey-steps {
+ margin: 0.4rem 0 0;
+ padding-left: 1.1rem;
+}
+
+.layers {
+ display: grid;
+ gap: 0.75rem;
+}
+
+@media (min-width: 800px) {
+ .layers {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+.idx {
+ font-family: var(--mono);
+ color: var(--mute);
+ font-size: 0.8rem;
+}
+
+.step-title {
+ font-weight: 600;
+}
+
+.step-meta {
+ color: var(--mute);
+ font-size: 0.9rem;
+}
+
+.faq details {
+ border-top: 1px solid var(--line);
+ padding: 0.85rem 0;
+}
+
+.faq summary {
+ cursor: pointer;
+ font-weight: 600;
+ font-size: 1.05rem;
+}
+
+.faq details p {
+ margin: 0.55rem 0 0;
+ color: var(--mute);
+ max-width: 46rem;
+}
+
+.mark {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ font-family: var(--mono);
+ font-size: 0.75rem;
+ letter-spacing: 0.04em;
+}
+
+.mark i {
+ width: 0.5rem;
+ height: 0.5rem;
+ border-radius: 50%;
+ background: currentColor;
+}
+
+.mark.pass {
+ color: var(--pass);
+}
+.mark.fail {
+ color: var(--fail);
+}
+.mark.hold {
+ color: var(--hold);
+}
+
+.filters {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ margin: 0.5rem 0 1rem;
+}
+
+.filters label {
+ display: flex;
+ flex-direction: column;
+ gap: 0.2rem;
+ font-size: 0.78rem;
+ color: var(--mute);
+}
+
+select,
+textarea,
+button {
+ font: inherit;
+}
+
+select,
+textarea {
+ color: var(--ink);
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 0.35rem 0.45rem;
+}
+
+button,
+.btn-solid {
+ font: inherit;
+ border: 1px solid var(--ink);
+ background: var(--ink);
+ color: var(--bg);
+ padding: 0.4rem 0.75rem;
+ cursor: pointer;
+}
+
+button:disabled {
+ cursor: not-allowed;
+ background: transparent;
+ color: var(--mute);
+ border-color: var(--line);
+}
+
+.docket {
+ width: 100%;
+ border-collapse: collapse;
+ background: var(--bg-2);
+}
+
+.docket th,
+.docket td {
+ text-align: left;
+ vertical-align: top;
+ padding: 0.7rem 0.65rem;
+ border-bottom: 1px solid var(--line);
+ font-size: 0.95rem;
+}
+
+.docket th {
+ color: var(--mute);
+ font-size: 0.72rem;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+}
+
+.docket a.title {
+ color: var(--ink);
+ font-weight: 600;
+ text-decoration: none;
+}
+
+.docket a.title:hover {
+ color: var(--link);
+}
+
+.docket tr.superseded td {
+ color: var(--mute);
+ text-decoration: line-through;
+}
+
+.group-h {
+ margin: 1.6rem 0 0.4rem;
+ font-size: 1.05rem;
+}
+
+.cards {
+ display: grid;
+ gap: 1rem;
+}
+
+@media (min-width: 800px) {
+ .cards {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+.card {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 1.15rem;
+}
+
+.card h2 {
+ margin-top: 0;
+}
+
+.digest {
+ font-family: var(--mono);
+ font-size: 0.82rem;
+ overflow-wrap: anywhere;
+}
+
+.chain {
+ list-style: none;
+ margin: 0.5rem 0 1.2rem;
+ padding: 0;
+ border-left: 2px solid var(--line);
+}
+
+.chain li {
+ padding: 0.45rem 0 0.45rem 1rem;
+}
+
+.chain .gap {
+ min-height: 2.5rem;
+ border-left: 2px dashed var(--fail);
+ margin-left: -2px;
+ color: var(--fail);
+ font-style: italic;
+}
+
+.profile {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 1rem 1.1rem;
+ margin: 0 0 0.75rem;
+}
+
+.profile h2 {
+ margin-top: 0;
+}
+
+.composer {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 1rem;
+ opacity: 0.72;
+}
+
+.composer textarea {
+ width: 100%;
+ margin: 0.35rem 0 0.7rem;
+}
+
+footer.site {
+ background: var(--nav);
+ color: #c5cdc6;
+ padding: 2rem 0 2.5rem;
+ margin-top: 2rem;
+}
+
+footer.site a {
+ color: var(--gold);
+}
+
+footer.site .packs a {
+ margin-right: 0.7rem;
+}
+
+.error {
+ color: var(--fail);
+}
+
+@media (max-width: 720px) {
+ .docket,
+ .docket tbody,
+ .docket tr,
+ .docket td,
+ .docket th {
+ display: block;
+ width: 100%;
+ }
+ .docket thead {
+ display: none;
+ }
+ .docket td {
+ padding: 0.2rem 0;
+ border: 0;
+ }
+ .docket tr {
+ padding: 0.8rem 0.65rem;
+ border-bottom: 1px solid var(--line);
+ }
+ .pipeline li {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css
new file mode 100644
index 00000000..004a9a10
--- /dev/null
+++ b/frontend/src/styles/tokens.css
@@ -0,0 +1,20 @@
+:root {
+ --bg: #f6f4ee;
+ --bg-2: #fffdf8;
+ --ink: #141c18;
+ --mute: #5a645e;
+ --line: #ddd6c8;
+ --gold: #e0b422;
+ --green: #1f7a3a;
+ --pass: #1f7a3a;
+ --fail: #b42318;
+ --hold: #9a6b12;
+ --link: #1c5f8a;
+ --tape: #c2410c;
+ --nav: #121814;
+ --nav-ink: #f6f4ee;
+ --serif: Georgia, 'Times New Roman', serif;
+ --sans: ui-sans-serif, system-ui, 'Segoe UI', sans-serif;
+ --mono: ui-monospace, 'Cascadia Code', 'Consolas', monospace;
+ --wide: 72rem;
+}
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/frontend/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/frontend/tests/adapter.test.ts b/frontend/tests/adapter.test.ts
new file mode 100644
index 00000000..985ff7cf
--- /dev/null
+++ b/frontend/tests/adapter.test.ts
@@ -0,0 +1,38 @@
+import { describe, expect, it } from 'vitest';
+import { DisabledAdapter } from '../src/inference/DisabledAdapter';
+import { MockAdapter } from '../src/inference/MockAdapter';
+import { GenerationUnavailable, IdentityMismatch } from '../src/inference/types';
+
+describe('DisabledAdapter', () => {
+ it('refuses generation', async () => {
+ const adapter = new DisabledAdapter();
+ const caps = await adapter.capabilities();
+ expect(caps.available).toBe(false);
+ expect(caps.mock).toBe(false);
+ await expect(adapter.generate()).rejects.toBeInstanceOf(GenerationUnavailable);
+ });
+});
+
+describe('MockAdapter', () => {
+ it('rejects a mismatched release identity', async () => {
+ const adapter = new MockAdapter(true);
+ await expect(
+ adapter.generate({
+ releaseId: 'fixture-base',
+ mode: 'completion',
+ prompt: 'hi',
+ }),
+ ).rejects.toBeInstanceOf(IdentityMismatch);
+ });
+
+ it('labels mock output', async () => {
+ const adapter = new MockAdapter();
+ const response = await adapter.generate({
+ releaseId: 'fixture-base',
+ mode: 'completion',
+ prompt: 'hi',
+ });
+ expect(response.disclaimer).toMatch(/not generated by a released/i);
+ expect(response.releaseId).toBe('fixture-base');
+ });
+});
diff --git a/frontend/tests/journey.test.ts b/frontend/tests/journey.test.ts
new file mode 100644
index 00000000..3106c79b
--- /dev/null
+++ b/frontend/tests/journey.test.ts
@@ -0,0 +1,16 @@
+import { describe, expect, it } from 'vitest';
+import { worstResult } from '../src/content/copy';
+
+describe('worstResult', () => {
+ it('lets FAIL dominate', () => {
+ expect(worstResult(['PASS', 'FAIL', 'NOT_RUN'])).toBe('FAIL');
+ });
+
+ it('does not treat mixed PASS and NOT_RUN as a full pass', () => {
+ expect(worstResult(['PASS', 'NOT_RUN'])).toBe('NOT_RUN');
+ });
+
+ it('returns PASS only when every result is PASS', () => {
+ expect(worstResult(['PASS', 'PASS'])).toBe('PASS');
+ });
+});
diff --git a/frontend/tests/validate.test.ts b/frontend/tests/validate.test.ts
new file mode 100644
index 00000000..c8ed4df5
--- /dev/null
+++ b/frontend/tests/validate.test.ts
@@ -0,0 +1,75 @@
+import { describe, expect, it } from 'vitest';
+import { loadSnapshot } from '../src/data/adapters/snapshotAdapter';
+import { SnapshotError } from '../src/data/contracts';
+import { releaseIsDownloadable, validateSnapshot } from '../src/data/validate';
+import emptyChecks from '../src/data/fixtures/empty-checks.json';
+import publicSnapshot from '../src/data/fixtures/public-snapshot.json';
+
+describe('validateSnapshot', () => {
+ it('accepts the unreleased draft snapshot as fixture data', () => {
+ const snap = validateSnapshot(publicSnapshot);
+ expect(snap.mode).toBe('fixture');
+ expect(snap.releases.every((item) => item.availability === 'not-released')).toBe(
+ true,
+ );
+ expect(snap.checks.some((item) => item.result === 'PASS')).toBe(true);
+ expect(snap.checks.find((item) => item.id === 'chk-g01')?.locallyRecomputed).toBe(
+ false,
+ );
+ });
+
+ it('refuses an empty check list', () => {
+ expect(() => validateSnapshot(emptyChecks)).toThrow(SnapshotError);
+ expect(() => validateSnapshot(emptyChecks)).toThrow(/no checks/);
+ });
+
+ it('does not treat ok as PASS', () => {
+ const raw = structuredClone(publicSnapshot) as Record;
+ const checks = raw.checks as Record[];
+ checks[0] = { ...checks[0], result: 'ok' };
+ expect(() => validateSnapshot(raw)).toThrow(/not a known check result/);
+ });
+
+ it('rejects PASS without evidence references', () => {
+ const raw = structuredClone(publicSnapshot) as Record;
+ const checks = raw.checks as Record[];
+ checks[0] = { ...checks[0], evidence: [] };
+ expect(() => validateSnapshot(raw)).toThrow(/missing evidence/);
+ });
+
+ it('rejects a javascript: locator', () => {
+ const raw = structuredClone(publicSnapshot) as Record;
+ raw.source = { ...(raw.source as object), url: 'javascript:alert(1)' };
+ expect(() => validateSnapshot(raw)).toThrow(/disallowed scheme/);
+ });
+
+ it('rejects non-array collection fields', () => {
+ const raw = structuredClone(publicSnapshot) as Record;
+ raw.evidence = 'invalid';
+ expect(() => validateSnapshot(raw)).toThrow(/evidence must be an array/);
+ });
+});
+
+describe('loadSnapshot', () => {
+ it('loads missing-parent without pretending the parent exists', () => {
+ const snap = loadSnapshot('missing-parent');
+ const child = snap.evidence[0];
+ const ids = new Set(snap.evidence.map((item) => item.id));
+ expect(child.parentIds.some((id) => !ids.has(id))).toBe(true);
+ });
+
+ it('keeps superseded FAIL rows', () => {
+ const snap = loadSnapshot('superseded');
+ const failed = snap.evidence.find((item) => item.result === 'FAIL');
+ const passed = snap.evidence.find((item) => item.result === 'PASS');
+ expect(failed?.supersededBy).toBe(passed?.id);
+ expect(passed?.supersedes).toBe(failed?.id);
+ });
+});
+
+describe('release gating', () => {
+ it('does not treat unreleased cards as downloadable', () => {
+ const snap = loadSnapshot('public');
+ expect(snap.releases.every((item) => !releaseIsDownloadable(item))).toBe(true);
+ });
+});
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
new file mode 100644
index 00000000..3f6cbc69
--- /dev/null
+++ b/frontend/tsconfig.app.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src", "tests"]
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 00000000..1ffef600
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json
new file mode 100644
index 00000000..953116cc
--- /dev/null
+++ b/frontend/tsconfig.node.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 00000000..e3c60235
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vitest/config';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ base: './',
+ plugins: [react()],
+ test: {
+ environment: 'jsdom',
+ globals: false,
+ exclude: ['e2e/**', 'node_modules/**'],
+ },
+});