diff --git a/projects/lumen-atelier-8f3c/.gitignore b/projects/lumen-atelier-8f3c/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/projects/lumen-atelier-8f3c/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/projects/lumen-atelier-8f3c/JOURNAL.md b/projects/lumen-atelier-8f3c/JOURNAL.md new file mode 100644 index 00000000..7710459b --- /dev/null +++ b/projects/lumen-atelier-8f3c/JOURNAL.md @@ -0,0 +1,41 @@ +# Lumen: The Lost Gardens — journal + +An atmospheric puzzle game about restoring floating gardens with light. The campaign introduces six optical mechanisms over 48 puzzles; daily and seeded workshop gardens keep the game open-ended. + +## Design + +- Palette: parchment mist `#f0f3ed`, limestone `#d5dfca`, deep lagoon `#133d3c`, forest ink `#243e36`, sunlight `#e8bd70`, faded lilac `#ada6c0`. +- Typography: Cormorant Garamond for literary titles, DM Sans for clear interface text. All fonts bundled locally. +- Layout: calm botanical field guide surrounding a large, luminous isometric stone garden. The playable garden is the hero. +- Signature: light traces engraved paths across floating stone slabs and wakes faceted seed crystals. +- Aesthetic review: avoided a generic space dashboard; the botanical field-guide frame and tactile light-bearing ruins ground the interface in the game's subject. + +## Ideas / backlog + +- [x] 48 verified campaign puzzles across six chapters +- [x] Isometric interactive garden with keyboard and touch support +- [x] Daily puzzle and shareable seeded workshop gardens +- [x] Undo, restart, two-stage hints, chapter selection, and completion awards +- [x] Local progress, backup/import, settings, and optional synthesized audio +- [x] Field guide, narrative memories, and achievements +- [x] Responsive design and reduced motion support +- [x] Custom static catalog thumbnail +- [x] Engine and browser interaction verification +- [x] Run exact repository gate and publish a PR +- [ ] Future: a freeform tile editor and community puzzle exchange + +## Session log + +- 2026-09-24 (codex, gpt-6): Started from the required template; established visual direction and parallelized puzzle engine, garden renderer, and persistence/audio work. + +- 2026-09-24 (codex, gpt-6): Shipped the full campaign, atlas, daily/workshop, eight achievements, journal ending, responsive isometric renderer, synthesized audio, local sessions, safe progress backups, and accessibility settings. Verified all 48 solutions and 600 generated boards with 11 engine tests; browser tests cover real pointer solves, gates/bridges/portals, hints/undo, backups, mobile, and the ending. Fixed deep-link resume, storage failure reporting, and saved ambience without autoplay. Exact repository conformance/lint/build/output gate passed. + +## Follow-up notes + +- The canonical guide turn count is a known valid solution budget; it is not an optimality proof. +- The workshop generates reproducible boards from seeds. A freeform tile editor remains a separate future feature. +- Save data is device-local. Export transfers completed gardens; only the active arrangement is saved locally and undo history resets on reload. +- Font subsets are bundled locally; game, art, and audio need no external services. +- See README.md for development and verification commands. + +- 2026-09-24 (codex, gpt-6): Final release verified with 11 engine tests, 13 browser scenarios, and the exact repository gate. Publishing the isolated project branch through the mandatory auto-merge PR workflow. diff --git a/projects/lumen-atelier-8f3c/README.md b/projects/lumen-atelier-8f3c/README.md new file mode 100644 index 00000000..1b96d317 --- /dev/null +++ b/projects/lumen-atelier-8f3c/README.md @@ -0,0 +1,60 @@ +# Lumen: The Lost Gardens + +An unhurried light-routing puzzle game. Rotate carved stone channels on a floating garden to connect a sun well to every sleeping crystal. + +## Play + +- **48 campaign gardens** across six chapters, with an ending and six recoverable journal memories. +- **Six evolving rules:** turning channels, branches, rooted stones, directional gates, isolated bridges, and paired portals. +- **A daily garden** shared by UTC date, with date links that remain replayable. +- **Seeded workshop gardens:** choose a chapter's rules and any 32-bit seed; share the resulting URL. +- **Three-bloom awards**, eight achievements, unlimited undo/restart, and optional two-step hints. +- **Local saves and portable backups**, keyboard support, reduced motion, contrast settings, and optional synthesized sound and ambience. +- Every chapter is open from the beginning. There are no timers, accounts, ads, or network game services. + +## Controls + +| Action | Mouse / touch | Keyboard | +| --- | --- | --- | +| Turn a stone clockwise | Click / tap | Tab to stone, then Enter or Space | +| Turn counterclockwise | Shift + click | Shift + Enter / Space | +| Move focus across stones | — | Arrow keys | +| Undo | Undo | Z or U | +| Restart | Restart | R | +| Hint | A gentle nudge | H | +| Field guide | Help / Field guide | ? | +| Close dialog / leave focus mode | Close | Escape | + +A hint first identifies a useful stone. Request it again to rotate that stone to the guide solution. Hints reduce the current attempt's award to one bloom. Three blooms require a solution within the displayed turn guide without hints; any other unassisted solution earns two. The guide is a known-solution budget, not a claim of a mathematically shortest solution. Undo preserves the hint count; restarting begins a new attempt. Best awards remain saved when replaying. + +Progress and the current garden's arrangement are saved locally. Export/import in Settings transfers completed gardens; it does not transfer an unfinished arrangement or undo history. Importing preserves existing best scores. If storage is unavailable, playing still works and the footer advises creating a backup. Saved ambience begins after the next player interaction; audio never starts on page load. + +## Development + +```sh +pnpm install +pnpm dev +pnpm lint +pnpm build +node --test tests/game.test.mjs +pnpm exec playwright install chromium +pnpm exec playwright test +``` + +Use Node 24 for the engine tests (native TypeScript stripping). Browser tests accept `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` when Chromium is already installed elsewhere. Playwright starts a local Vite server and keeps screenshots/traces under ignored `node_modules/.cache/`. + +Run the repository's exact release gate from the repository root: + +```sh +node scripts/verify-project.mjs lumen-atelier-8f3c +``` + +## Implementation + +- `src/game.ts`: deterministic generation, solutions, light propagation, and hints. Sources and receivers are fixed; light requires reciprocal ports. A bridge keeps its two channels isolated, gates accept only the correct incoming direction, and matched portals connect distant components. Visited entry states prevent infinite cycles. +- `src/Board.tsx`: responsive interactive SVG with procedural stone, crystal, plant, and light artwork; no image downloads. +- `src/App.tsx`: campaign, atlas, workshop, journal, settings, awards, hash routing, and current-session state. +- `src/progress.ts`: validated persistent records and portable, mergeable backups. +- `src/audio.ts`: small Web Audio instrument with lazy creation and explicit cleanup. + +All imagery, typography, and code are served from the app's relative Vite base. The only runtime persistence is browser local storage. No tracking or third-party requests. diff --git a/projects/lumen-atelier-8f3c/eslint.config.js b/projects/lumen-atelier-8f3c/eslint.config.js new file mode 100644 index 00000000..ef614d25 --- /dev/null +++ b/projects/lumen-atelier-8f3c/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/projects/lumen-atelier-8f3c/index.html b/projects/lumen-atelier-8f3c/index.html new file mode 100644 index 00000000..2b3ae055 --- /dev/null +++ b/projects/lumen-atelier-8f3c/index.html @@ -0,0 +1,15 @@ + + + + + + + + + Lumen — The Lost Gardens + + +
+ + + diff --git a/projects/lumen-atelier-8f3c/package.json b/projects/lumen-atelier-8f3c/package.json new file mode 100644 index 00000000..39fa338a --- /dev/null +++ b/projects/lumen-atelier-8f3c/package.json @@ -0,0 +1,36 @@ +{ + "name": "lumen-lost-gardens", + "private": true, + "version": "1.0.0", + "type": "module", + "packageManager": "pnpm@11.5.2", + "scripts": { + "preinstall": "npx only-allow pnpm", + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@fontsource/cormorant-garamond": "^5.3.0", + "@fontsource/dm-sans": "^5.3.0", + "lucide-react": "^1.47.0", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@playwright/test": "^1.63.0", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } +} diff --git a/projects/lumen-atelier-8f3c/playwright.config.ts b/projects/lumen-atelier-8f3c/playwright.config.ts new file mode 100644 index 00000000..f4d84fb0 --- /dev/null +++ b/projects/lumen-atelier-8f3c/playwright.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from '@playwright/test' + +export default defineConfig({ + testDir: './tests', + testMatch: '**/*.spec.ts', + timeout: 60000, + expect: { timeout: 8000 }, + fullyParallel: true, + workers: 3, + reporter: 'list', + outputDir: 'node_modules/.cache/playwright-results', + use: { + baseURL: 'http://127.0.0.1:5174', + viewport: { width: 1440, height: 1000 }, + reducedMotion: 'reduce', + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + launchOptions: { executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH }, + }, + webServer: { + command: 'pnpm dev --host 127.0.0.1 --port 5174 --strictPort', + url: 'http://127.0.0.1:5174', + reuseExistingServer: !process.env.CI, + }, +}) diff --git a/projects/lumen-atelier-8f3c/pnpm-lock.yaml b/projects/lumen-atelier-8f3c/pnpm-lock.yaml new file mode 100644 index 00000000..f9cafc5e --- /dev/null +++ b/projects/lumen-atelier-8f3c/pnpm-lock.yaml @@ -0,0 +1,1774 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@fontsource/cormorant-garamond': + specifier: ^5.3.0 + version: 5.3.0 + '@fontsource/dm-sans': + specifier: ^5.3.0 + version: 5.3.0 + lucide-react: + specifier: ^1.47.0 + version: 1.47.0(react@19.2.7) + react: + specifier: ^19.2.6 + version: 19.2.7 + react-dom: + specifier: ^19.2.6 + version: 19.2.7(react@19.2.7) + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.4.1) + '@playwright/test': + specifier: ^1.63.0 + version: 1.63.0 + '@types/node': + specifier: ^24.12.3 + version: 24.13.2 + '@types/react': + specifier: ^19.2.14 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.17) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.2(vite@8.0.16(@types/node@24.13.2)) + eslint: + specifier: ^10.3.0 + version: 10.4.1 + eslint-plugin-react-hooks: + specifier: ^7.1.1 + version: 7.1.1(eslint@10.4.1) + eslint-plugin-react-refresh: + specifier: ^0.5.2 + version: 0.5.2(eslint@10.4.1) + globals: + specifier: ^17.6.0 + version: 17.6.0 + typescript: + specifier: ~6.0.2 + version: 6.0.3 + typescript-eslint: + specifier: ^8.59.2 + version: 8.61.0(eslint@10.4.1)(typescript@6.0.3) + vite: + specifier: ^8.0.12 + version: 8.0.16(@types/node@24.13.2) + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@fontsource/cormorant-garamond@5.3.0': + resolution: {integrity: sha512-weuGsCirGVWBWqpt6YUp0yLThTe4G8YO45noq8wxeJCRvEpq5lFrxNMFSTxcyOyV5k5otzJ8guDzYegdYlcLMQ==} + + '@fontsource/dm-sans@5.3.0': + resolution: {integrity: sha512-lYJtMXXO28q1z+yz+z8XKd0s4hXaa9QdkETzkyD760sidCv5heI86weYA0sx0Nc4pAMAQTUuyf4gO44cYKKS9g==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.5': + resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@playwright/test@1.63.0': + resolution: {integrity: sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==} + engines: {node: '>=20'} + hasBin: true + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.13.2': + resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@typescript-eslint/eslint-plugin@8.61.0': + resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.61.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.61.0': + resolution: {integrity: sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.61.0': + resolution: {integrity: sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.61.0': + resolution: {integrity: sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.61.0': + resolution: {integrity: sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.61.0': + resolution: {integrity: sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.61.0': + resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.61.0': + resolution: {integrity: sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.61.0': + resolution: {integrity: sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.61.0': + resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@6.0.2': + resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.36: + resolution: {integrity: sha512-lVq/Df7LXlO79MVaaUHztSwWiG9oXoWHlgvNS51v8Dpd4+G4/VIy6qYePTw31nAVls33nUtnfezYeLkYAak9dg==} + engines: {node: '>=6.0.0'} + hasBin: true + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001799: + resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + electron-to-chromium@1.5.372: + resolution: {integrity: sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-react-refresh@0.5.2: + resolution: {integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==} + peerDependencies: + eslint: ^9 || ^10 + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.4.1: + resolution: {integrity: sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@17.6.0: + resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} + engines: {node: '>=18'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@1.47.0: + resolution: {integrity: sha512-o8C23aXpNQypRY73W7fW02EyvWJinEMXgKeGjFoKym0zj3Q73hD97A1IQps1g8C14HTGsOpZL0Am+xjfgFZAbg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.47: + resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + playwright-core@1.63.0: + resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.63.0: + resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==} + engines: {node: '>=20'} + hasBin: true + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.61.0: + resolution: {integrity: sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + 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/core@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.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1)': + dependencies: + eslint: 10.4.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.4.1)': + optionalDependencies: + eslint: 10.4.1 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@fontsource/cormorant-garamond@5.3.0': {} + + '@fontsource/dm-sans@5.3.0': {} + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-project/types@0.133.0': {} + + '@playwright/test@1.63.0': + dependencies: + playwright: 1.63.0 + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.13.2': + dependencies: + undici-types: 7.18.2 + + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1)(typescript@6.0.3))(eslint@10.4.1)(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.61.0(eslint@10.4.1)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/type-utils': 8.61.0(eslint@10.4.1)(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.0(eslint@10.4.1)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.61.0 + eslint: 10.4.1 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.61.0(eslint@10.4.1)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.61.0 + debug: 4.4.3 + eslint: 10.4.1 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.61.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3) + '@typescript-eslint/types': 8.61.0 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.61.0': + dependencies: + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/visitor-keys': 8.61.0 + + '@typescript-eslint/tsconfig-utils@8.61.0(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.61.0(eslint@10.4.1)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.0(eslint@10.4.1)(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.4.1 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.61.0': {} + + '@typescript-eslint/typescript-estree@8.61.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.61.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3) + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/visitor-keys': 8.61.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.4 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.61.0(eslint@10.4.1)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1) + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) + eslint: 10.4.1 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.61.0': + dependencies: + '@typescript-eslint/types': 8.61.0 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@24.13.2))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.16(@types/node@24.13.2) + + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.36: {} + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.36 + caniuse-lite: 1.0.30001799 + electron-to-chromium: 1.5.372 + node-releases: 2.0.47 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + caniuse-lite@1.0.30001799: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + detect-libc@2.1.2: {} + + electron-to-chromium@1.5.372: {} + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.1.1(eslint@10.4.1): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 10.4.1 + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.2(eslint@10.4.1): + dependencies: + eslint: 10.4.1 + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.4.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@17.6.0: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@1.47.0(react@19.2.7): + dependencies: + react: 19.2.7 + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.47: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + playwright-core@1.63.0: {} + + playwright@1.63.0: + dependencies: + playwright-core: 1.63.0 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react@19.2.7: {} + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + source-map-js@1.2.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tslib@2.8.1: + optional: true + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.61.0(eslint@10.4.1)(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1)(typescript@6.0.3))(eslint@10.4.1)(typescript@6.0.3) + '@typescript-eslint/parser': 8.61.0(eslint@10.4.1)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.0(eslint@10.4.1)(typescript@6.0.3) + eslint: 10.4.1 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@6.0.3: {} + + undici-types@7.18.2: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite@8.0.16(@types/node@24.13.2): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.2 + fsevents: 2.3.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} diff --git a/projects/lumen-atelier-8f3c/project.json b/projects/lumen-atelier-8f3c/project.json new file mode 100644 index 00000000..cc846604 --- /dev/null +++ b/projects/lumen-atelier-8f3c/project.json @@ -0,0 +1,8 @@ +{ + "title": "Lumen: The Lost Gardens", + "description": "Restore floating gardens with light in 48 atmospheric puzzles. Discover six mechanics, a daily ritual, and endless shareable gardens.", + "agent": "codex", + "model": "gpt-6", + "tags": ["puzzle", "games", "creative"], + "createdAt": "2026-09-24" +} diff --git a/projects/lumen-atelier-8f3c/public/favicon.svg b/projects/lumen-atelier-8f3c/public/favicon.svg new file mode 100644 index 00000000..4cdfaba4 --- /dev/null +++ b/projects/lumen-atelier-8f3c/public/favicon.svg @@ -0,0 +1 @@ + diff --git a/projects/lumen-atelier-8f3c/public/thumbnail.svg b/projects/lumen-atelier-8f3c/public/thumbnail.svg new file mode 100644 index 00000000..676c6a9c --- /dev/null +++ b/projects/lumen-atelier-8f3c/public/thumbnail.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + A LITTLE LIGHT. A LOST WORLD. + LUMEN + THE LOST GARDENS + + Follow the light. + Bring a forgotten world to life. + A PUZZLE ODYSSEY + + + + + + + + + + + + + + + + + + + + + + 48 GARDENS + 6 WORLDS + ENDLESS DISCOVERY + ✦ + diff --git a/projects/lumen-atelier-8f3c/src/App.css b/projects/lumen-atelier-8f3c/src/App.css new file mode 100644 index 00000000..90e4666f --- /dev/null +++ b/projects/lumen-atelier-8f3c/src/App.css @@ -0,0 +1,342 @@ +.app-shell{min-height:100vh;background:radial-gradient(ellipse at 85% 0%,#f9faf3 0,transparent 55%),var(--paper)} +.topbar{height:89px;display:flex;align-items:center;justify-content:space-between;padding:0 43px 0 32px;border-bottom:1px solid var(--line);gap:25px;background:#f7f8f2c9;position:relative;z-index:5} +.brand{display:flex;align-items:center;gap:16px;background:none;padding:0;text-align:left;min-width:198px} +.brand-mark{width:26px;height:40px;position:relative;display:block;margin:0 5px} +.brand-mark:before{content:'';position:absolute;width:23px;height:23px;transform:rotate(45deg) skew(13deg,13deg);top:9px;border:1px solid #789278;background:#dde3cc} +.brand-mark:after{content:'';position:absolute;top:0;left:12px;height:41px;width:1px;background:#8c9c76} +.brand-mark span{position:absolute;width:44px;height:1px;background:#a4b092;left:-9px;top:20px} +.brand-type{font-family:var(--serif);font-size:31px;line-height:1;letter-spacing:5px;font-weight:500} +.brand-type small{display:block;font-family:'DM Sans',sans-serif;font-weight:500;font-size:7.5px;letter-spacing:2.2px;margin-top:7px} +.main-nav{display:flex;gap:32px;height:100%;align-items:center} +.main-nav button{display:flex;align-items:center;gap:9px;position:relative;height:100%;padding:0 1px;color:#798374;background:none;font-size:12px;white-space:nowrap} +.main-nav button:after{content:'';position:absolute;bottom:0;left:50%;right:50%;height:2px;background:#426449;transition:left .2s,right .2s} +.main-nav button.active{color:#2a4534} +.main-nav button.active:after{left:0;right:0} +.main-nav button:hover{color:#294432} +.nav-dot{width:4px;height:4px;background:#b4995d;border-radius:50%;margin-left:-3px} +.topbar-tools{display:flex;gap:13px;align-items:center} +.icon-button{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;background:transparent;border-radius:50%;color:#6e7e6b;transition:background .2s,color .2s;flex-shrink:0} +.icon-button:hover,.icon-button.enabled{background:#e5ebdf;color:#344f38} +.app-body{display:flex;min-height:calc(100vh - 89px)} +.sidebar{flex:0 0 238px;width:238px;border-right:1px solid var(--line);padding:33px 17px 0 19px;display:flex;flex-direction:column;background:#f0f3eeb3} +.eyebrow{display:block;font-size:9px;letter-spacing:1.8px;line-height:1.6;font-weight:600;color:#85917a} +.sidebar-title{display:flex;align-items:center;justify-content:space-between;margin:0 12px} +.sidebar-title .eyebrow{font-size:8px;letter-spacing:1.4px} +.sidebar-count{font-size:9px;letter-spacing:1px;color:#899381;font-variant-numeric:tabular-nums} +.journey-progress{height:2px;background:#dce4d5;margin:16px 12px 27px} +.journey-progress>span{height:100%;display:block;background:#6e8863;min-width:3px;transition:width .4s} +.chapter-list{display:flex;flex-direction:column;gap:8px} +.chapter-button{position:relative;display:flex;gap:12px;align-items:center;min-height:89px;padding:12px 11px;width:100%;text-align:left;border:1px solid transparent;border-radius:5px;background:none;transition:background .2s,border-color .2s} +.chapter-button:hover{background:#e8eee3} +.chapter-button.selected{background:#e3e9db;border-color:#d5decb} +.chapter-icon{position:relative;width:41px;flex:0 0 41px;color:#a5ae94} +.sigil{display:block;width:100%;height:auto} +.chapter-button.selected .chapter-icon{color:#65805b} +.chapter-copy{display:flex;flex-direction:column;gap:4px} +.chapter-copy small{font-size:7px;letter-spacing:1.5px;color:#9ba48e;font-weight:600} +.chapter-copy strong{font-size:11px;font-weight:500;color:#60735b;line-height:1.35} +.chapter-copy>span{font-size:8px;color:#97a08c} +.chapter-button.selected .chapter-copy strong{color:#304b33} +.chapter-button.selected .chapter-copy small{color:#6e865f} +.chapter-button.selected .chapter-copy>span{color:#879578} +.chapter-active-dot{position:absolute;right:10px;top:17px;width:4px;height:4px;border-radius:50%;background:#7f955d} +.chapter-check{position:absolute;bottom:0;right:0;background:#6e885e;color:#fff;border-radius:50%;width:15px;height:15px;display:grid;place-items:center;border:2px solid var(--paper)} +.sidebar-link{display:flex;align-items:center;gap:10px;padding:15px 12px;background:none;color:#7c8d71;font-size:10px;width:100%;text-align:left;border-radius:5px} +.sidebar-link>svg:last-child:not(:first-child){margin-left:auto} +.sidebar-link:hover,.sidebar-link.active{background:#e5ebdf;color:#3f5d37} +.sidebar>.sidebar-link{margin-top:12px} +.sidebar-bottom{border-top:1px solid var(--line);margin-top:29px;padding-top:17px} +.small-count{font-size:9px;margin-left:auto;color:#9ca68f} +.sidebar-note{display:flex;flex-direction:column;align-items:center;padding:29px 0;color:#9baa8e;gap:0} +.sidebar-note p{font-family:var(--serif);font-style:italic;font-size:15px;text-align:center;line-height:1.5;color:#98a28c;margin:10px 0} +.main-content{min-width:0;flex:1;padding:36px 40px 0;max-width:1710px;margin:auto;outline:none!important} +.page-heading{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-bottom:29px;min-height:90px} +.breadcrumb{display:flex;align-items:center;gap:10px;border:0;background:none;padding:0;font-size:8px;font-weight:600;color:#8a957f;letter-spacing:1.6px;text-transform:uppercase} +.breadcrumb>span:last-child{color:#6e8160;letter-spacing:1px} +.breadcrumb-line{height:1px;width:18px;background:#adb79f} +.page-heading h1{font-family:var(--serif);font-weight:500;font-size:46px;letter-spacing:-1.4px;margin:9px 0 5px;line-height:1.03;color:#2d4736} +.page-heading p{font-size:11px;color:#919988;line-height:1.6;margin:8px 0 0} +.title-flower{font-family:Georgia,serif;display:inline-block;font-size:19px;vertical-align:middle;margin-left:16px;color:#a5b092;font-weight:400} +.quiet-button{background:none;display:inline-flex;align-items:center;gap:9px;padding:10px 0;font-size:10px;white-space:nowrap;color:#77866b} +.quiet-button:hover{color:#385331} +.guide-link{align-self:flex-end;margin-bottom:6px} +.play-layout{display:grid;grid-template-columns:minmax(0,1fr) 221px;gap:30px;align-items:start} +.garden-container{min-width:0} +.garden-stage{position:relative;isolation:isolate;overflow:hidden;background:#133d3c;border-radius:7px;box-shadow:0 9px 25px #284c3820;border:1px solid #1b4541} +.stage-top{position:absolute;inset:22px 23px auto;z-index:2;display:flex;align-items:flex-start;justify-content:space-between;pointer-events:none} +.stage-coordinate{color:#d4ddbe;letter-spacing:1.7px;font-size:8px;font-weight:500;line-height:1.8} +.live-dot{display:inline-block;width:4px;height:4px;border-radius:50%;background:#b5c692;margin-right:8px;box-shadow:0 0 5px #c1d58d55} +.stage-coordinate small{display:block;letter-spacing:.4px;color:#809d8d;font-size:8px;padding-left:12px;margin-top:2px} +.stage-focus{pointer-events:auto;width:30px;height:30px;border:1px solid #75948245;color:#a7bea2;background:#163b3466;display:grid;place-items:center;border-radius:4px} +.stage-focus:hover{background:#45654c55;color:#fff} +.garden-stage .garden-board{width:100%;display:block} +.stage-bottom{position:absolute;bottom:21px;left:24px;right:24px;display:flex;justify-content:space-between;align-items:center;pointer-events:none;z-index:2;gap:10px} +.light-status{color:#8ba38d;font-size:9px;display:flex;align-items:center;gap:8px} +.light-status strong{color:#d1dcb8;font-size:11px;font-weight:500;letter-spacing:1px} +.light-status strong span{color:#8ba38d;font-size:9px} +.status-beacon{display:inline-block;width:7px;height:10px;background:#5f7e70;transform:rotate(0deg);clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%)} +.status-beacon.lit{background:#f2d087;filter:drop-shadow(0 0 4px #e7c373)} +.stage-weather{display:flex;align-items:center;gap:7px;font-family:var(--serif);font-style:italic;color:#a6baa0;font-size:13px} +.garden-toolbar{display:flex;align-items:center;justify-content:space-between;padding:17px 3px 12px;gap:10px} +.tool-group,.toolbar-end{display:flex;align-items:center;gap:15px} +.garden-toolbar button{background:transparent;display:inline-flex;align-items:center;gap:7px;padding:4px;color:#76866b;font-size:10px} +.garden-toolbar button:hover:not(:disabled){color:#385831} +.garden-toolbar button:disabled{opacity:.38} +.garden-toolbar kbd{font-family:inherit;font-size:8px;border:1px solid #dae2d0;width:15px;height:16px;border-radius:3px;display:grid;place-items:center;margin-left:4px;color:#9ba78d} +.tool-divider{height:14px;width:1px;background:#dce3d3} +.toolbar-end .hint-button{padding:8px 10px;border:1px solid #d2ddc6;border-radius:4px;background:#e7eddf;color:#5c734b} +.hint-button svg{color:#9a9964} +.interaction-note{display:flex;align-items:center;justify-content:center;gap:10px;color:#a1aa93;font-size:8px;line-height:1.6;margin:3px 0 0} +.interaction-note>span{display:inline-flex;align-items:center;gap:6px;white-space:nowrap} +.interaction-note i{display:inline-block;width:2px;height:2px;background:#aeb59e;border-radius:50%} +.mouse-symbol{width:7px;height:11px;border:1px solid #a5af97;display:inline-block;border-radius:4px;position:relative} +.mouse-symbol:after{position:absolute;content:'';width:1px;height:3px;background:#a5af97;top:1px;left:2px} +.garden-notes{padding:5px 0 0;text-align:center} +.level-counter{display:flex;justify-content:space-between;align-items:center;font-size:8px;letter-spacing:1.5px;color:#a4ad97} +.level-counter>span:first-child{color:#859575} +.objective-sigil{width:88px;margin:27px auto 17px;color:#859968} +.objective-sigil.is-awake{color:#b7954b} +.garden-notes>.eyebrow{font-size:7px;letter-spacing:1.35px} +.garden-notes h2{font-family:var(--serif);font-weight:500;font-size:30px;line-height:1.1;letter-spacing:-.6px;margin:9px 0 11px} +.objective-copy{font-size:10px;line-height:1.9;color:#85917d;max-width:205px;margin:0 auto} +.objective-progress{margin-top:23px;text-align:left} +.objective-progress>span{font-size:9px;color:#88957a;display:flex;justify-content:space-between} +.objective-progress strong{font-size:9px;font-weight:500;color:#657b54;letter-spacing:1px} +.objective-progress>div{display:flex;gap:5px;margin-top:10px} +.objective-progress>div>span{height:3px;background:#dce3d2;flex:1;border-radius:3px} +.objective-progress>div>span.lit{background:#9caa66} +.move-stats{display:flex;align-items:center;justify-content:center;margin:24px 0 17px} +.move-stats>div{flex:1;display:flex;align-items:center;flex-direction:column;gap:4px} +.stat-number{font-family:var(--serif);font-weight:400;font-size:37px;font-variant-numeric:tabular-nums;line-height:1} +.stat-number.target{color:#99a78b} +.move-stats small{font-size:6px;letter-spacing:1.15px;color:#8c9a7b;font-weight:500} +.stat-separator{height:30px;width:1px;background:#dce3d2} +.reward-guide{display:flex;align-items:center;flex-direction:column;gap:7px} +.blooms{display:inline-flex;align-items:center;gap:5px;color:#d6ddc8} +.blooms svg.filled{color:#b29e64;fill:#d2bd7922} +.reward-guide>span:last-child{font-size:8px;line-height:1.8;color:#a2ab94} +.garden-story{position:relative;border-top:1px solid #dde4d4;margin-top:29px;padding-top:23px} +.story-leaf{position:absolute;left:calc(50% - 15px);width:30px;top:-8px;display:flex;justify-content:center;background:var(--paper);color:#9da98e} +.garden-story p{font-family:var(--serif);font-size:16px;line-height:1.5;font-style:italic;color:#839277;margin:0 0 12px} +.garden-story>span:last-child{font-size:6px;letter-spacing:1.7px;color:#a0ab92} +.first-help{background:none;color:#9caa88;font-size:8px;display:flex;align-items:center;gap:4px;justify-content:center;width:100%;padding:22px 0 0} +.first-help:hover{color:#526a45} +.hint-note{display:flex;gap:8px;align-items:flex-start;margin-top:19px;color:#8e8051;border:1px solid #d7cfaa;background:#ecebd944;padding:10px;border-radius:4px;text-align:left} +.hint-note svg{flex-shrink:0;margin-top:2px} +.hint-note p{font-size:9px;line-height:1.6;margin:0} +.chapter-trail{margin-top:30px;border-top:1px solid var(--line);padding-top:22px} +.trail-heading{display:flex;justify-content:space-between;align-items:center;margin-bottom:19px} +.trail-heading .eyebrow{font-size:8px;letter-spacing:1.7px} +.trail-heading>span:last-child{font-size:8px;color:#9aa68b} +.trail-levels{display:grid;grid-template-columns:repeat(8,minmax(0,1fr));position:relative;gap:5px} +.trail-levels:before{content:'';position:absolute;left:6%;right:6%;height:1px;top:19px;background:#dce3d3} +.trail-level{background:none;display:flex;align-items:center;flex-direction:column;position:relative;z-index:1;padding:0 3px} +.trail-node{position:relative;width:39px;height:39px;border:1px solid #d6dfcc;background:#eef2e9;border-radius:50%;display:grid;place-items:center;color:#a2ad91;font-family:var(--serif);font-size:15px;transition:background .2s,transform .2s} +.trail-level:hover .trail-node{transform:translateY(-3px);border-color:#8aa177;color:#526943} +.trail-level.current .trail-node{background:#567050;color:#f3f4e1;border-color:#567050;box-shadow:0 0 0 4px #f0f3ed,0 0 0 5px #b5c2a2} +.trail-level.completed .trail-node{color:#80905e;background:#e5ebd9;border-color:#b6c49f} +.trail-level.completed.current .trail-node{background:#657d50;color:#f0e9c6} +.trail-name{font-size:8px;color:#96a087;margin-top:12px;line-height:1.4;text-align:center;min-height:22px} +.trail-level.current .trail-name{color:#506a43} +.trail-dots{font-size:14px;color:#c2ccb4;line-height:14px} +.trail-level .blooms{gap:2px;margin-top:3px} +.trail-level .blooms svg{width:9px;height:9px} +.page-footer{margin-top:31px;border-top:1px solid var(--line);padding:19px 0;display:flex;align-items:center;justify-content:space-between;gap:16px;color:#a4ad98;font-size:7px} +.page-footer>span{display:flex;align-items:center;gap:7px} +.page-footer>span:first-child{letter-spacing:1.5px;font-size:6px} +.footer-diamond{width:4px;height:7px;border:1px solid #9dab8b;transform:rotate(25deg)} +.save-dot{background:#a4b78c;width:4px;height:4px;border-radius:50%;margin-left:2px} +.save-dot.warning{background:#ad743b} +.primary-button{display:flex;align-items:center;justify-content:center;gap:10px;padding:13px 19px;background:#47673e;color:#f4f3df;font-size:11px;font-weight:500;border:1px solid #47673e;border-radius:4px;transition:background .2s,transform .2s} +.primary-button:hover{background:#35532d;transform:translateY(-1px)} +.primary-button>svg:last-child:not(:first-child){margin-left:auto} +.secondary-button{display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:10px 13px;background:#e6ecdf;color:#556d47;font-size:10px;border:1px solid #d4dfc8;border-radius:4px} +.secondary-button:hover{background:#dae4d0} +.text-button{background:transparent;padding:9px 0;display:inline-flex;align-items:center;gap:8px;color:#6e875b;font-size:10px} +.text-button:hover{color:#344f2b} +.win-panel{margin-top:20px} +.win-panel>.blooms{gap:9px} +.win-panel>.blooms svg{width:23px;height:23px} +.win-panel>p{color:#929c82;font-size:9px;margin:11px 0 20px;line-height:1.5} +.win-panel>.primary-button{width:100%} +.win-panel>.text-button{margin-top:6px;font-size:8px} +.restored-banner{position:absolute;bottom:58px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:10px;white-space:nowrap;background:#183e34e6;backdrop-filter:blur(6px);padding:11px 17px;border:1px solid #a7bc7544;border-radius:30px;color:#e1d6a2;font-family:var(--serif);font-size:16px;box-shadow:0 4px 22px #08251b55;animation:banner-appear .6s ease-out} +@keyframes banner-appear{from{opacity:0;translate:0 10px}to{opacity:1;translate:0 0}} +.special-footer{margin-top:26px;padding:21px 0;border-top:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:20px} +.special-footer>div{display:flex;align-items:center;gap:12px;color:#869874;font-family:var(--serif);font-style:italic;font-size:17px} +.atlas-total{display:flex;flex-direction:column;gap:4px;text-align:right} +.atlas-total strong{font-family:var(--serif);font-size:40px;font-weight:400} +.atlas-total strong span{font-size:23px;color:#a4ae95} +.atlas-total small{font-size:7px;letter-spacing:1.3px;color:#8d9c7b} +.atlas-intro{display:flex;align-items:center;gap:12px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:13px 0;margin-bottom:26px;color:#7c8c6c} +.atlas-intro p{font-size:10px;line-height:1.7;margin:0;flex:1} +.atlas-intro>span{font-size:9px;color:#9ba989;white-space:nowrap} +.atlas-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:23px} +.chapter-card{overflow:hidden;border:1px solid #dce3d3;border-radius:5px;background:#f5f7ef} +.chapter-card-art{width:100%;height:185px;display:block;position:relative;overflow:hidden;background:#24463f;color:#b7c29c} +.chapter-card-1 .chapter-card-art{background:#344f4b;color:#cbd4ad} +.chapter-card-2 .chapter-card-art{background:#3a5047;color:#c5c5a8} +.chapter-card-3 .chapter-card-art{background:#34464d;color:#aebfc0} +.chapter-card-4 .chapter-card-art{background:#484e52;color:#c7bca4} +.chapter-card-5 .chapter-card-art{background:#44475b;color:#c1b2c9} +.chapter-card-art:after{content:'';position:absolute;inset:0;background:radial-gradient(ellipse at 50% 100%,#bdc68f18,transparent 70%)} +.chapter-card-art .sigil{width:125px;height:125px;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);filter:drop-shadow(0 0 14px #d0c89c44);transition:transform .5s} +.chapter-card-art:hover .sigil{transform:translate(-50%,-50%) rotate(8deg) scale(1.04)} +.card-roman{position:absolute;top:18px;left:21px;font-family:var(--serif);font-size:19px;opacity:.6} +.art-caption{position:absolute;bottom:15px;left:20px;font-size:7px;letter-spacing:1.5px;text-transform:uppercase;opacity:.7} +.art-arrow{position:absolute;bottom:13px;right:18px;opacity:.6} +.card-orbit{position:absolute;border:1px solid currentColor;opacity:.07;width:230px;height:230px;border-radius:50%;top:-15px;left:calc(50% - 115px)} +.chapter-card-body{padding:20px 20px 14px} +.chapter-card-body .eyebrow{font-size:7px;letter-spacing:1.4px} +.chapter-card-body h2{font-family:var(--serif);font-size:29px;line-height:1.1;font-weight:500;margin:8px 0 10px} +.chapter-card-body>p{font-size:10px;line-height:1.8;color:#89977c;margin:0;min-height:53px} +.card-progress{display:flex;align-items:center;gap:12px;margin:14px 0 8px} +.card-progress>span{display:block;height:2px;background:#e0e6d7;flex:1} +.card-progress>span>span{display:block;height:100%;background:#96aa7c} +.card-progress small{font-size:8px;color:#9ba88a} +.card-level-list{border-top:1px solid #dce3d3;padding:10px 20px} +.card-level-list button{display:flex;align-items:center;gap:12px;background:none;width:100%;padding:9px 0;color:#6d835d;font-size:10px;text-align:left} +.card-level-list button>span{font-size:9px;color:#a3af92} +.card-level-list button>svg{margin-left:auto} +.card-level-list button:hover{color:#304d28} +.page-heading-icon{color:#a1b18b;margin-right:9px} +.workshop-layout{display:grid;grid-template-columns:1fr 1fr;border:1px solid #d7e0cf;border-radius:7px;overflow:hidden;background:#f6f7ef} +.workshop-art{position:relative;overflow:hidden;min-height:460px;background:radial-gradient(ellipse at 50% 80%,#48604655,transparent),#173e38;color:#c3c6a2;display:flex;align-items:center;flex-direction:column;justify-content:center;padding:40px;text-align:center} +.workshop-art>.sigil{width:160px;margin-bottom:30px;color:#bbbf8d;filter:drop-shadow(0 0 25px #bcd99544)} +.workshop-art>.eyebrow{color:#929e78;font-size:7px;letter-spacing:2.2px} +.workshop-art h2{font-family:var(--serif);font-weight:400;font-size:39px;line-height:1.2;color:#dce0bf;margin:13px 0} +.workshop-art h2 i{font-weight:400;color:#b5c5a0} +.seed-art-number{font-family:var(--serif);font-size:14px;color:#809c7e;margin-top:16px;letter-spacing:3px} +.workshop-orbit{position:absolute;border:1px solid #b9c69c12;width:430px;height:430px;border-radius:50%;top:calc(50% - 215px);left:calc(50% - 215px);pointer-events:none} +.workshop-orbit.orbit-two{width:370px;height:370px;top:calc(50% - 185px);left:calc(50% - 185px);border-style:dashed} +.workshop-form{padding:42px 39px} +.workshop-form h2{font-family:var(--serif);font-size:34px;line-height:1.1;margin:9px 0 12px;font-weight:500} +.workshop-form>p{font-size:11px;line-height:1.8;color:#8c9980;margin:0 0 26px} +.workshop-form label,.backup-section label{display:block;font-size:10px;color:#6e835e;font-weight:500;margin-bottom:9px} +.select-wrap{position:relative;margin-bottom:24px} +.select-wrap select{width:100%;padding:12px 32px 12px 12px;border:1px solid #d6e0cd;color:#637b55;border-radius:4px;background:#eef2e8;font-size:10px;appearance:none} +.select-wrap>svg{position:absolute;right:12px;top:12px;pointer-events:none;color:#829a70} +.seed-input{display:flex;align-items:center;border:1px solid #d6e0cd;border-radius:4px;background:#eef2e8;padding:3px 7px 3px 12px} +.seed-input input{padding:9px 0;border:0;outline:none;background:none;color:#536d44;width:100%;min-width:0;letter-spacing:2px;font-size:13px} +.workshop-form .input-note{font-size:8px;color:#a0ac92;margin:9px 0 28px} +.workshop-form>.primary-button{width:100%} +.workshop-promise{display:flex;gap:8px;align-items:center;margin-top:17px;color:#93a37f;font-size:8px;justify-content:center} +.workshop-presets{margin-top:31px} +.workshop-presets>div{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px;margin-top:15px} +.workshop-presets button{display:flex;gap:11px;align-items:center;text-align:left;background:#e8eedf99;padding:18px 13px;border:1px solid #dce4d1;border-radius:4px} +.workshop-presets button:hover{background:#e2ead7} +.workshop-presets .sigil{flex-shrink:0;width:38px;color:#8ea272} +.workshop-presets button>span{display:flex;flex-direction:column;gap:6px} +.workshop-presets strong{font-size:11px;font-weight:500;color:#657f55} +.workshop-presets small{font-size:8px;line-height:1.6;color:#8c9c7b} +.workshop-presets button>svg:last-child{margin-left:auto;flex-shrink:0;color:#8ea272} +.journal-intro{display:flex;align-items:center;gap:18px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:26px 10px;margin:0 0 28px} +.journal-dropcap{font-family:var(--serif);font-size:72px;line-height:1;color:#899f70} +.journal-intro p{font-family:var(--serif);font-size:20px;line-height:1.5;max-width:550px;color:#809470;margin:0 35px 0 0} +.journal-intro>div{margin-left:auto;min-width:110px;text-align:center} +.journal-intro>div strong{font-family:var(--serif);font-size:42px;display:block;font-weight:400;color:#819868} +.journal-intro>div span{display:block;color:#9caa8c;font-size:7px;letter-spacing:1.2px;margin-top:6px} +.memory-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px} +.memory{background:#eef2e8;border:1px dashed #d6e0cd;padding:24px;display:flex;flex-direction:column;border-radius:3px} +.memory.unlocked{background:#f9f9ef;border:1px solid #dce2cf} +.memory-top{display:flex;align-items:center;justify-content:space-between} +.memory-top .eyebrow{font-size:7px} +.memory-top .sigil{width:43px;color:#afb99b} +.memory.unlocked .memory-top .sigil{color:#879d69} +.memory h2{font-family:var(--serif);font-size:27px;font-weight:400;line-height:1.1;color:#92a081;margin:18px 0 13px} +.memory.unlocked h2{color:#526d43} +.memory>p{font-size:11px;line-height:1.9;color:#9ba88d;flex:1;margin:0 0 18px} +.memory.unlocked>p{color:#869875} +.memory .text-button{font-size:9px;justify-content:space-between} +.final-memory{margin:50px auto 20px;max-width:570px;text-align:center} +.final-memory>.sigil{width:100px;margin:auto auto 25px;color:#a7a060} +.final-memory h2{font-family:var(--serif);font-weight:500;font-size:43px;color:#4b683d} +.final-memory p{font-family:var(--serif);font-size:22px;line-height:1.7;color:#8a9a76} +.final-memory .primary-button{margin:30px auto 0} +.modal-backdrop{position:fixed;inset:0;z-index:30;display:flex;align-items:center;justify-content:center;background:#162e2877;backdrop-filter:blur(9px);padding:25px;animation:fade-in .2s ease} +.dialog{background:#f2f5ec;border:1px solid #ecf0df;border-radius:9px;width:570px;max-height:calc(100dvh - 50px);overflow-y:auto;box-shadow:0 20px 80px #061d2855;padding:31px;outline:none;scrollbar-color:#b8c5a6 transparent;scrollbar-width:thin} +.dialog.dialog-wide{width:780px} +.dialog-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px} +.dialog-header .eyebrow{font-size:7px;letter-spacing:1.6px} +.dialog-header h2{font-family:var(--serif);font-weight:500;font-size:37px;line-height:1.1;margin:7px 0 0} +.dialog-intro{font-size:11px;line-height:1.9;color:#8b9b7c;margin:13px 0 25px} +.guide-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;padding:8px 0 25px;border-bottom:1px solid var(--line)} +.guide-steps>div{position:relative;padding:18px;background:#e8efdf;border-radius:4px;border:1px solid #dce6d1} +.guide-steps>div>span{position:absolute;right:16px;top:19px;font-size:10px;color:#a2b68c} +.guide-steps svg{width:25px;height:25px;stroke-width:1.3;color:#859a67;margin-bottom:15px} +.guide-steps h3{font-size:11px;font-weight:600;margin:0 0 9px;color:#5a7348} +.guide-steps p{font-size:10px;line-height:1.8;color:#819670;margin:0} +.guide-section-title{font-family:var(--serif);font-size:24px;font-weight:500;margin:24px 0 18px} +.mechanics-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px 30px} +.mechanics-grid>div{display:flex;align-items:flex-start;gap:12px} +.mechanic-symbol{display:flex;align-items:center;justify-content:center;width:35px;height:35px;flex:0 0 35px;font-size:28px;line-height:1;background:#e2ead5;border:1px solid #d1ddc4;border-radius:4px;color:#728a58;font-family:monospace} +.mechanics-grid h4{font-size:10px;margin:0 0 5px;font-weight:500;color:#5a7348} +.mechanics-grid p{font-size:9px;line-height:1.8;color:#8a9a79;margin:0} +.guide-bottom{display:flex;gap:32px;border-top:1px solid var(--line);padding-top:20px;margin-top:25px} +.guide-bottom>div:first-child{flex:1} +.guide-bottom h3{font-family:var(--serif);font-size:22px;font-weight:500;margin:0 0 9px} +.guide-bottom p{font-size:10px;color:#879a77;line-height:1.8;margin:0 0 20px} +.keyboard-guide{display:flex;flex-direction:column;gap:10px;min-width:185px;justify-content:center;padding-bottom:20px} +.keyboard-guide>span{display:flex;gap:5px;align-items:center;font-size:8px;color:#8c9d7d} +.keyboard-guide kbd{font-family:inherit;padding:3px 5px;border:1px solid #d5dfcb;border-radius:3px;background:#e9efdf;color:#6b8456;font-size:8px} +.guide-content>.primary-button{margin-top:5px;margin-left:auto;min-width:190px} +.setting-row{display:flex;align-items:center;gap:14px;border-bottom:1px solid var(--line);padding:19px 0} +.setting-row>svg{color:#82996c;flex-shrink:0} +.setting-row>div{flex:1} +.setting-row strong{font-size:11px;font-weight:500;color:#5e7750} +.setting-row p{font-size:9px;line-height:1.6;color:#95a588;margin:5px 0 0} +.switch{position:relative;width:34px;height:20px;border-radius:12px;background:#d9e3cf;flex:0 0 34px;padding:3px;transition:background .2s} +.switch>span{display:block;width:14px;height:14px;background:#f9faf1;border-radius:50%;box-shadow:0 1px 4px #52644825;transition:transform .2s} +.switch.on{background:#839d65} +.switch.on>span{transform:translateX(14px)} +.backup-section{margin-top:28px} +.backup-section h3{font-family:var(--serif);font-size:26px;font-weight:500;color:#567346;margin:0 0 9px} +.backup-section>p{font-size:10px;line-height:1.9;color:#8e9f7e;margin:0 0 17px} +.backup-actions{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap} +.backup-section label.secondary-button{display:inline-flex;align-items:center;gap:7px;font-size:10px;margin:0;cursor:pointer} +.file-input{position:relative} +.file-input input{position:absolute;inset:0;opacity:0;width:100%;cursor:pointer} +.backup-section textarea{display:block;width:100%;min-height:66px;max-height:140px;padding:11px;border:1px solid #d6e2ca;border-radius:4px;background:#f9faf4;color:#647f50;font-size:10px;line-height:1.7;resize:vertical;margin-bottom:15px} +.backup-section textarea::placeholder{color:#a5b598} +.settings-footnote{font-family:var(--serif);text-align:center;font-size:16px;font-style:italic;color:#a1af91;margin:27px 0 0} +.achievement-grid{display:grid;grid-template-columns:1fr;gap:9px} +.achievement{display:flex;align-items:center;gap:15px;padding:13px 15px;background:#eaf0e1;border:1px solid #e0e8d6;border-radius:5px;color:#a8b798} +.achievement.achieved{background:#e4ecd7;border-color:#cddaaf;color:#728a52} +.achievement-icon{width:35px;height:35px;display:flex;align-items:center;justify-content:center;flex-shrink:0} +.achievement h3{font-size:11px;font-weight:500;margin:0 0 5px} +.achievement p{font-size:9px;line-height:1.5;margin:0;opacity:.8} +.achievement>svg:last-child{margin-left:auto} +.achievement-total{display:flex;justify-content:center;align-items:center;gap:10px;margin-top:23px;color:#859c69;font-size:10px} +.toast{position:fixed;bottom:26px;left:50%;transform:translateX(-50%);z-index:40;display:flex;gap:12px;align-items:center;max-width:calc(100vw - 30px);width:max-content;background:#2d4e38;color:#e4edd6;border:1px solid #829b6177;border-radius:7px;padding:15px 17px;font-size:11px;box-shadow:0 8px 28px #102b2b44;animation:fade-in .25s ease} +.toast>svg{color:#b6c995;flex-shrink:0} +.toast>button{background:none;color:#b5c998;padding:3px;display:flex} +@keyframes fade-in{from{opacity:0}to{opacity:1}} +.mobile-toggle{display:none} +.skip-link{position:fixed;top:-80px;left:10px;z-index:100;background:#fff;padding:14px;color:#253b29;text-decoration:none;border:1px solid #6e865d} +.skip-link:focus{top:10px} +.focus-mode .topbar,.focus-mode .sidebar,.focus-mode .page-heading,.focus-mode .chapter-trail,.focus-mode .page-footer,.focus-mode .special-footer{display:none} +.focus-mode .main-content{padding:25px;width:100%;max-width:1350px} +.focus-mode .app-body{min-height:100vh;align-items:center} +.focus-mode .play-layout{grid-template-columns:minmax(0,900px) 245px;gap:50px;align-items:center} +.reduce-motion *,.reduce-motion *:before,.reduce-motion *:after{animation:none!important;transition:none!important;scroll-behavior:auto!important} +.high-contrast{--muted:#5b6f51;--line:#bdcbaa} +.high-contrast .chapter-copy>span,.high-contrast .chapter-copy small,.high-contrast .page-heading p,.high-contrast .objective-copy,.high-contrast .reward-guide>span:last-child,.high-contrast .trail-name,.high-contrast .interaction-note,.high-contrast .page-footer,.high-contrast .garden-story p,.high-contrast .eyebrow{color:#5c704d} +.high-contrast .garden-stage{border:2px solid #456445} +.high-contrast .board-tile path{stroke-opacity:1} +@media(min-width:1600px){.main-content{padding:42px 58px 0}.play-layout{grid-template-columns:minmax(0,1fr) 258px;gap:40px}.garden-stage{max-height:725px}.garden-stage svg{max-height:725px}.sidebar{width:255px;flex-basis:255px;padding-left:23px;padding-right:23px}.page-heading h1{font-size:53px}.garden-notes{padding-top:8px}.objective-sigil{width:105px;margin-top:40px}.garden-notes h2{font-size:35px}.objective-copy{font-size:12px;max-width:250px}.garden-notes>.eyebrow{font-size:8px}.garden-story{margin-top:39px}.garden-story p{font-size:19px}.move-stats{margin:32px 0 22px}.stat-number{font-size:44px}.page-footer{padding:23px 0}.chapter-button{min-height:96px}.chapter-copy strong{font-size:12px}} +@media(max-width:1250px){.topbar{padding:0 27px}.sidebar{flex-basis:209px;width:209px;padding:30px 12px 0}.chapter-button{gap:8px;padding:10px 7px;min-height:80px}.chapter-icon{width:34px;flex-basis:34px}.chapter-copy strong{font-size:10px}.chapter-copy>span{font-size:7px}.chapter-copy small{font-size:6px}.chapter-active-dot{right:7px;top:14px}.main-content{padding:30px 28px 0}.play-layout{grid-template-columns:minmax(0,1fr) 185px;gap:23px}.main-nav{gap:24px}.page-heading h1{font-size:42px}.objective-sigil{width:72px;margin-top:22px;margin-bottom:14px}.garden-notes h2{font-size:28px}.garden-notes>.eyebrow{font-size:6px}.objective-copy{font-size:9px}.objective-progress{margin-top:20px}.move-stats{margin:19px 0 12px}.garden-story{margin-top:22px;padding-top:19px}.garden-story p{font-size:14px}.first-help{font-size:7px;padding-top:15px}.garden-toolbar{gap:5px}.tool-group,.toolbar-end{gap:8px}.garden-toolbar kbd{display:none}.atlas-grid,.memory-grid{gap:16px}.chapter-card-body{padding:17px}.chapter-card-body h2{font-size:25px}.chapter-card-body>p{font-size:9px}.workshop-form{padding:32px 26px}.workshop-presets>div{gap:12px}.workshop-presets button{padding:14px 10px;gap:8px}.workshop-presets .sigil{width:30px}.workshop-presets strong{font-size:10px}.workshop-presets small{font-size:7px}.workshop-presets button>svg:last-child{width:13px}.journal-intro p{font-size:18px;margin-right:0}.memory{padding:18px}.memory h2{font-size:25px}} +@media(max-width:1060px){.topbar{height:78px;padding:0 23px}.brand{min-width:178px;gap:12px}.brand-type{font-size:27px}.brand-type small{font-size:6px}.topbar-tools{gap:3px}.main-nav{gap:24px}.main-nav button{font-size:10px;gap:6px}.main-nav button>svg{width:14px}.sidebar{width:177px;flex-basis:177px;padding:26px 9px 0}.sidebar-title{margin:0 7px}.sidebar-title .eyebrow{font-size:6px}.sidebar-count{font-size:7px}.chapter-icon{width:29px;flex-basis:29px}.chapter-copy strong{font-size:9px}.chapter-copy>span{font-size:6px}.chapter-copy small{font-size:6px;letter-spacing:1px}.chapter-button{gap:6px;min-height:75px;padding:8px 6px}.sidebar-link{font-size:8px;gap:7px;padding-left:8px;padding-right:8px}.sidebar-link svg{width:14px}.sidebar-note p{font-size:13px}.small-count{font-size:7px}.main-content{padding:28px 25px 0}.page-heading{margin-bottom:24px}.page-heading h1{font-size:39px}.page-heading p{font-size:10px}.play-layout{grid-template-columns:minmax(0,1fr) 174px;gap:19px}.stage-top{inset:17px 17px auto}.stage-bottom{left:17px;right:17px;bottom:16px}.stage-coordinate{font-size:7px}.stage-coordinate small{font-size:6px}.stage-weather{font-size:11px}.light-status{font-size:7px;gap:5px}.garden-notes h2{font-size:26px}.objective-sigil{width:58px;margin:16px auto 12px}.objective-progress{margin-top:16px}.move-stats{margin:16px 0 11px}.stat-number{font-size:32px}.reward-guide>span:last-child{font-size:7px}.garden-story{margin-top:19px;padding-top:17px}.garden-story p{font-size:13px}.garden-story>span:last-child{font-size:5px}.first-help{font-size:6px;padding-top:14px}.interaction-note{font-size:6px;gap:7px}.interaction-note>span{gap:4px}.trail-name{font-size:7px}.trail-levels{gap:3px}.page-footer>span:first-child{font-size:5px}.page-footer{font-size:6px}.atlas-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.memory-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.workshop-art{padding:30px 20px;min-height:455px}.workshop-art h2{font-size:33px}.workshop-form{padding:30px 24px}.workshop-form h2{font-size:28px}.workshop-form>p{font-size:10px}.workshop-form .eyebrow{font-size:7px}.workshop-presets>div{grid-template-columns:1fr;gap:10px}.workshop-presets button{padding:14px 18px}.workshop-presets .sigil{width:42px}.workshop-presets strong{font-size:11px}.workshop-presets small{font-size:9px}.workshop-presets button>svg:last-child{width:18px}} +@media(max-width:900px){.main-nav{gap:17px}.sidebar{width:167px;flex-basis:167px}.main-content{padding:27px 24px 0}.play-layout{grid-template-columns:1fr;gap:23px}.garden-notes{display:grid;grid-template-columns:65px 1fr 120px;text-align:left;gap:0 18px;padding:20px 0;border-top:1px solid var(--line);position:relative;align-items:center}.garden-notes .level-counter{display:none}.garden-notes .objective-sigil{grid-column:1;grid-row:1/4;width:65px;margin:0}.garden-notes>.eyebrow{grid-column:2;grid-row:1;font-size:6px;margin-top:0;align-self:end}.garden-notes>h2{grid-column:2;grid-row:2;font-size:26px;margin:7px 0}.garden-notes>.objective-copy{grid-column:2;grid-row:3;font-size:9px;max-width:none;margin:0;align-self:start;line-height:1.7}.garden-notes .move-stats{grid-column:3;grid-row:1/3;margin:0 0 10px;align-self:end}.garden-notes .reward-guide{grid-column:3;grid-row:3;gap:5px;align-self:start}.garden-notes .reward-guide>span:last-child{font-size:6px;text-align:center}.garden-notes .objective-progress{grid-column:1/4;grid-row:4;margin-top:18px}.garden-notes .garden-story,.garden-notes .first-help{display:none}.garden-notes .hint-note{grid-column:1/4;grid-row:5}.garden-notes .win-panel{grid-column:3;grid-row:1/4;margin:0;text-align:center}.win-panel>p{display:none}.win-panel>.blooms svg{width:18px;height:18px}.win-panel>.primary-button{font-size:9px;padding:10px;margin-top:10px;gap:5px}.win-panel>.text-button{font-size:7px}.chapter-trail{margin-top:5px}.garden-toolbar{padding-top:16px}.interaction-note{font-size:8px}.stage-coordinate{font-size:8px}.stage-coordinate small{font-size:7px}.light-status{font-size:9px}.stage-weather{font-size:13px}.page-heading h1{font-size:39px}.workshop-layout{grid-template-columns:1fr}.workshop-art{min-height:300px;padding:28px}.workshop-art>.sigil{width:110px;margin-bottom:16px}.workshop-art h2{font-size:30px}.workshop-art h2 br{display:none}.workshop-art h2 i{margin-left:8px}.workshop-form{padding:32px}.workshop-form h2{font-size:32px}.workshop-form>p{font-size:11px}.journal-intro>div{display:none}.guide-link{font-size:8px}.guide-link>svg:first-child{display:none}.focus-mode .play-layout{grid-template-columns:1fr;gap:10px}.focus-mode .garden-stage{max-width:850px;margin:auto}.focus-mode .garden-notes{display:none}} +@media(max-width:700px){.topbar{height:74px;padding:0 19px;gap:10px;flex-wrap:wrap}.brand{min-width:0;gap:10px}.brand-type{font-size:25px;letter-spacing:4px}.brand-type small{font-size:5.7px;letter-spacing:1.6px}.brand-mark{transform:scale(.83);margin:0 2px}.topbar-tools{gap:3px}.topbar-tools .icon-button{width:30px}.topbar-tools .ambience-button{display:none}.main-nav{position:absolute;top:74px;left:0;right:0;height:47px;justify-content:center;gap:36px;border-bottom:1px solid var(--line);background:#f2f5ed;z-index:4}.main-nav button{font-size:10px;gap:7px}.main-nav button>svg{width:14px}.app-body{padding-top:47px;flex-direction:column}.sidebar{width:100%;flex-basis:auto;border-right:0;border-bottom:1px solid var(--line);padding:0 20px;display:block;background:#e9eee1}.sidebar-title{height:43px;margin:0;justify-content:flex-start;gap:8px}.sidebar-title .eyebrow{font-size:7px;letter-spacing:1.3px}.mobile-toggle{display:flex;width:25px;height:25px}.sidebar-count{margin-left:auto;font-size:8px}.journey-progress,.chapter-list,.sidebar>.sidebar-link,.sidebar-bottom{display:none}.mobile-open .chapter-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding-bottom:13px}.mobile-open .chapter-icon{width:39px;flex-basis:39px}.mobile-open .chapter-copy strong{font-size:11px}.mobile-open .chapter-copy small{font-size:7px}.mobile-open .chapter-copy>span{font-size:8px}.mobile-open .sidebar>.sidebar-link{display:flex;margin:0 0 10px;padding:13px}.mobile-open .sidebar-bottom{display:flex;border:0;margin:0;padding:0 0 12px}.mobile-open .sidebar-note{display:none}.mobile-open .sidebar-link{font-size:10px}.main-content{width:100%;padding:26px 20px 0}.page-heading{margin-bottom:22px;min-height:auto}.page-heading h1{font-size:39px;margin-top:9px}.page-heading p{font-size:10px;max-width:85%}.breadcrumb{font-size:7px;gap:7px;letter-spacing:1.2px}.title-flower{font-size:15px;margin-left:11px}.guide-link{font-size:0;align-self:center;padding:10px;gap:0}.guide-link svg:first-child{display:block;width:18px;height:18px}.guide-link svg:last-child{display:none}.stage-top{inset:19px 20px auto}.stage-coordinate{font-size:7px;letter-spacing:1.3px}.stage-coordinate small{font-size:6px}.stage-focus{height:27px;width:27px}.stage-bottom{left:20px;right:20px;bottom:17px}.garden-stage{border-radius:5px}.light-status{font-size:8px}.stage-weather{font-size:12px}.garden-toolbar button{font-size:10px}.garden-toolbar{padding:14px 0 12px}.tool-group,.toolbar-end{gap:11px}.toolbar-end .hint-button{padding:9px 10px}.garden-toolbar kbd{display:none}.interaction-note{font-size:7px;gap:7px;margin-top:0}.garden-notes{grid-template-columns:60px 1fr 117px;gap:0 13px;padding:19px 0}.garden-notes .objective-sigil{width:60px}.garden-notes>.eyebrow{font-size:5.5px;letter-spacing:1px}.garden-notes h2{font-size:25px}.garden-notes>.objective-copy{font-size:8px}.garden-notes .reward-guide>span:last-child{font-size:5.5px}.garden-notes .stat-number{font-size:29px}.garden-notes .move-stats small{font-size:5px;letter-spacing:.8px}.garden-notes .stat-separator{height:25px}.chapter-trail{padding-top:18px}.trail-heading{margin-bottom:18px}.trail-heading .eyebrow{font-size:7px}.trail-heading>span:last-child{font-size:7px}.trail-node{width:33px;height:33px;font-size:14px}.trail-levels:before{top:16px}.trail-name{font-size:6px;margin-top:11px;min-height:26px;overflow-wrap:anywhere}.trail-level.current .trail-node{box-shadow:0 0 0 3px #f0f3ed,0 0 0 4px #b5c2a2}.trail-level .blooms svg{width:7px;height:7px}.page-footer{margin-top:24px;gap:8px;padding:17px 0}.page-footer>span:first-child{font-size:5px;letter-spacing:.9px}.page-footer>span:last-child{font-size:5px}.atlas-total strong{font-size:35px}.atlas-total small{font-size:6px;white-space:nowrap}.atlas-intro{gap:9px}.atlas-intro p{font-size:9px}.atlas-intro>span{display:none}.atlas-intro>svg{flex-shrink:0}.atlas-grid{gap:13px}.chapter-card-art{height:145px}.chapter-card-art .sigil{width:105px}.chapter-card-body{padding:15px 13px 11px}.chapter-card-body .eyebrow{font-size:5px;letter-spacing:1px}.chapter-card-body h2{font-size:25px;margin:7px 0 9px}.chapter-card-body>p{font-size:8px;min-height:58px}.chapter-card-body .text-button{font-size:8px}.art-caption{font-size:5px;left:14px}.art-arrow{right:13px}.card-roman{left:14px;top:14px;font-size:16px}.card-level-list{padding:8px 13px}.card-level-list button{font-size:8px;gap:8px}.page-heading-icon{width:28px}.workshop-form{padding:27px}.workshop-presets{margin-top:27px}.workshop-presets>div{gap:10px}.workshop-presets .sigil{width:45px}.workshop-presets button{gap:14px}.workshop-presets strong{font-size:12px}.workshop-presets small{font-size:9px}.memory-grid{gap:13px}.memory{padding:17px}.memory h2{font-size:25px}.memory>p{font-size:10px}.memory .text-button{font-size:8px;gap:5px}.memory-top .sigil{width:34px}.memory-top .eyebrow{font-size:6px;letter-spacing:1px}.journal-intro{gap:13px;padding:20px 0}.journal-intro p{font-size:18px}.journal-dropcap{font-size:65px}.special-footer>div{font-size:16px}.special-footer .quiet-button{font-size:8px}.modal-backdrop{padding:14px}.dialog{padding:25px 21px;max-height:calc(100dvh - 28px)}.dialog-header h2{font-size:33px}.dialog-header .eyebrow{font-size:6px}.guide-steps{gap:9px}.guide-steps>div{padding:13px}.guide-steps h3{font-size:10px}.guide-steps p{font-size:9px}.guide-steps svg{width:23px;height:23px}.guide-steps>div>span{right:12px;font-size:8px}.mechanics-grid{gap:18px 16px}.mechanics-grid>div{gap:9px}.mechanic-symbol{flex-basis:28px;width:28px;height:28px;font-size:23px}.mechanics-grid h4{font-size:9px}.mechanics-grid p{font-size:8px}.guide-bottom{gap:20px}.keyboard-guide{min-width:160px}.guide-bottom h3{font-size:22px}.guide-bottom p{font-size:9px}.keyboard-guide>span{font-size:7px}.toast{font-size:10px;padding:13px 14px;bottom:17px;gap:9px}.focus-mode .app-body{padding:0}.focus-mode .main-content{padding:12px}.restored-banner{font-size:13px;padding:9px 13px;bottom:50px}.final-memory h2{font-size:35px}.final-memory p{font-size:19px}.high-contrast .chapter-copy>span{font-size:8px}} +@media(max-width:420px){.main-content{padding-left:15px;padding-right:15px}.main-nav{gap:26px}.page-heading h1{font-size:35px}.page-heading p{font-size:9px;max-width:95%}.garden-notes{grid-template-columns:44px 1fr 107px;gap:0 10px}.garden-notes .objective-sigil{width:44px}.garden-notes>h2{font-size:23px}.garden-notes>.eyebrow{font-size:5px}.garden-notes>.objective-copy{font-size:8px}.garden-notes .reward-guide>span:last-child{font-size:5px}.toolbar-end .hint-button{font-size:9px;padding:9px 8px}.tool-group,.toolbar-end{gap:7px}.garden-toolbar button{font-size:9px;gap:5px}.garden-toolbar button svg{width:15px}.interaction-note{font-size:6px;gap:6px}.stage-coordinate{font-size:6px}.stage-coordinate small{font-size:5px}.stage-top{inset:16px 15px auto}.stage-bottom{left:15px;right:15px;bottom:14px}.stage-weather{font-size:11px}.light-status{font-size:7px}.light-status strong{font-size:9px}.trail-node{width:30px;height:30px}.trail-levels:before{top:15px}.trail-name{font-size:5px}.page-footer>span:first-child{font-size:4.5px}.page-footer>span:last-child{font-size:4.5px}.chapter-card-body h2{font-size:22px}.chapter-card-body>p{font-size:7px;min-height:54px}.chapter-card-body .text-button{font-size:7px}.memory-grid{grid-template-columns:1fr}.memory h2{font-size:29px}.memory>p{font-size:11px}.guide-steps{grid-template-columns:1fr;gap:9px}.guide-steps>div{padding:14px 14px 14px 53px;min-height:80px}.guide-steps svg{position:absolute;left:15px;top:17px}.guide-steps>div>span{display:none}.guide-steps p{font-size:9px}.guide-bottom{flex-direction:column;gap:0}.keyboard-guide{padding:0 0 20px}.guide-content>.primary-button{width:100%}.dialog-header h2{font-size:30px}.mechanics-grid{column-gap:10px}.mechanic-symbol{flex-basis:24px;width:24px;height:24px;font-size:21px}.workshop-art h2{font-size:29px}.workshop-art h2 br{display:block}.workshop-art h2 i{margin-left:0}} +/* Reading comfort: keep the field guide quiet without making its controls microscopic. */ +@media(min-width:1061px){ + .chapter-copy strong{font-size:12px}.chapter-copy small{font-size:8px}.chapter-copy>span{font-size:9px}.chapter-button{min-height:88px}.chapter-icon{width:37px;flex-basis:37px}.chapter-button{gap:10px}.sidebar-link{font-size:11px}.sidebar-count,.small-count{font-size:10px}.sidebar-title .eyebrow{font-size:8px} + .main-nav button{font-size:13px}.page-heading p{font-size:12px}.breadcrumb{font-size:9px}.quiet-button{font-size:11px}.garden-notes>.eyebrow{font-size:8px;letter-spacing:1.1px}.objective-copy{font-size:12px;line-height:1.8}.objective-progress>span,.objective-progress strong{font-size:11px}.move-stats small{font-size:7px}.reward-guide>span:last-child{font-size:10px}.garden-story p{font-size:17px}.garden-story>span:last-child{font-size:7px}.first-help{font-size:9px;line-height:1.6}.level-counter{font-size:9px}.garden-toolbar button{font-size:12px}.interaction-note{font-size:9px}.stage-coordinate{font-size:9px}.stage-coordinate small{font-size:8px}.light-status{font-size:10px}.trail-name{font-size:9px}.trail-heading .eyebrow,.trail-heading>span:last-child{font-size:9px}.page-footer{font-size:9px}.page-footer>span:first-child{font-size:7px} + .objective-sigil{width:76px;margin:23px auto 14px}.garden-story{margin-top:23px}.move-stats{margin:22px 0 16px}.garden-notes h2{font-size:31px} + .chapter-card-body .eyebrow{font-size:8px}.chapter-card-body>p{font-size:12px}.chapter-card-body .text-button{font-size:12px}.atlas-intro p{font-size:12px}.atlas-intro>span{font-size:11px}.art-caption{font-size:8px}.chapter-card-body>p{min-height:65px}.card-level-list button{font-size:12px}.workshop-form>p{font-size:13px}.workshop-form label{font-size:12px}.select-wrap select{font-size:12px}.workshop-form .input-note,.workshop-promise{font-size:10px}.workshop-presets strong{font-size:12px}.workshop-presets small{font-size:10px}.memory>p{font-size:13px}.memory .text-button{font-size:11px} +} +.dialog-intro{font-size:13px}.guide-steps h3{font-size:13px}.guide-steps p{font-size:12px}.mechanics-grid h4{font-size:12px}.mechanics-grid p{font-size:11px}.guide-bottom p{font-size:12px}.keyboard-guide>span{font-size:10px}.setting-row strong{font-size:13px}.setting-row p{font-size:11px}.backup-section>p{font-size:12px}.backup-section label{font-size:12px}.achievement h3{font-size:13px}.achievement p{font-size:11px}.achievement-total{font-size:12px}.primary-button{font-size:12px}.secondary-button{font-size:11px}.settings-content .backup-actions label.secondary-button{font-size:11px} +@media(max-width:700px){ + .main-nav button{font-size:11px}.page-heading p{font-size:11px}.stage-coordinate{font-size:8px}.stage-coordinate small{font-size:7px}.stage-weather{font-size:12px}.light-status{font-size:9px}.interaction-note{font-size:8px;flex-wrap:wrap;row-gap:3px}.interaction-note>span:last-child,.interaction-note>i:last-of-type{display:none}.garden-toolbar button{font-size:11px}.toolbar-end .hint-button{font-size:10px}.garden-notes{grid-template-columns:43px 1fr 108px;gap:0 10px}.garden-notes .objective-sigil{width:43px}.garden-notes>.eyebrow{font-size:6px;letter-spacing:.7px}.garden-notes>.objective-copy{font-size:10px}.garden-notes>h2{font-size:26px}.garden-notes .reward-guide>span:last-child{font-size:7px;line-height:1.6}.garden-notes .move-stats small{font-size:6px}.trail-name{font-size:7px;line-height:1.4;min-height:28px}.trail-heading .eyebrow,.trail-heading>span:last-child{font-size:8px}.page-footer>span:first-child{display:none}.page-footer>span:last-child{font-size:8px;margin:auto}.trail-levels{row-gap:17px}.stage-top{inset:15px 16px auto}.stage-bottom{left:16px;right:16px;bottom:13px}.chapter-card-body>p{font-size:10px;min-height:72px}.chapter-card-body .text-button{font-size:9px}.atlas-intro p{font-size:11px}.card-level-list button{font-size:10px}.workshop-form>p{font-size:12px}.workshop-form label{font-size:11px}.select-wrap select{font-size:11px}.workshop-form .input-note{font-size:10px}.workshop-promise{font-size:9px}.memory>p{font-size:12px}.memory .text-button{font-size:10px}.guide-steps p{font-size:11px}.guide-steps h3{font-size:12px}.mechanics-grid{grid-template-columns:1fr}.mechanics-grid p{font-size:12px}.mechanics-grid h4{font-size:13px}.guide-bottom{flex-direction:column;gap:0}.keyboard-guide{padding-top:0}.guide-bottom p{font-size:12px}.guide-content>.primary-button{width:100%}.guide-steps{grid-template-columns:1fr;gap:9px}.guide-steps>div{padding:15px 15px 15px 56px;min-height:82px}.guide-steps svg{position:absolute;left:17px;top:19px}.guide-steps>div>span{display:none}.toast{font-size:12px}.win-panel>.primary-button{font-size:10px} +} diff --git a/projects/lumen-atelier-8f3c/src/App.tsx b/projects/lumen-atelier-8f3c/src/App.tsx new file mode 100644 index 00000000..61384c30 --- /dev/null +++ b/projects/lumen-atelier-8f3c/src/App.tsx @@ -0,0 +1,421 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import type { CSSProperties, ReactNode } from 'react' +import { ArrowDownToLine, ArrowRight, ArrowUpFromLine, BookOpen, Check, ChevronDown, ChevronRight, CircleHelp, Compass, Copy, Flower2, Grid2X2, Headphones, Leaf, Lightbulb, Maximize, Moon, RotateCcw, Settings2, Shuffle, Sparkles, Sprout, Sun, Trophy, Undo2, Volume2, VolumeX, WandSparkles, Wind, X } from 'lucide-react' +import Board from './Board' +import { CHAPTERS, LEVELS, dailyLevel, generateLevel, getHint, rotateTile, simulate } from './game' +import type { Level, Tile } from './game' +import { exportProgress, importProgress, loadProgress, saveProgress } from './progress' +import type { Progress } from './progress' +import { GardenAudio } from './audio' +import './App.css' + +type Page = 'play' | 'atlas' | 'daily' | 'workshop' | 'journal' +type Modal = 'guide' | 'settings' | 'achievements' | null +type Snapshot = { tiles: Tile[]; moves: number } +type Session = Snapshot & { level: Level; history: Snapshot[]; hints: number; hintId: string | null; hintRotation: number | null; won: boolean } +const SESSION_KEY = 'lumen-garden-session-v1' +const ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI'] +const MECHANICS = ['Light & turns', 'Branching paths', 'Rooted stones', 'One-way gates', 'Woven channels', 'Paired portals'] +const today = () => new Date().toISOString().slice(0, 10) +const countCompleted = (progress: Progress) => LEVELS.filter(level => progress.completed[level.id]).length + +function makeSession(level: Level, restore = false): Session { + const base: Session = { level, tiles: level.tiles.map(tile => ({ ...tile })), moves: 0, history: [], hints: 0, hintId: null, hintRotation: null, won: false } + if (restore) { + try { + const saved = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null') + if (saved?.id === level.id && Array.isArray(saved.rotations) && saved.rotations.length === level.tiles.length && saved.rotations.every((n: unknown) => Number.isInteger(n) && Number(n) >= 0 && Number(n) <= 3) && Number.isInteger(saved.moves) && saved.moves >= 0 && Number.isInteger(saved.hints) && saved.hints >= 0) { + base.tiles = base.tiles.map((tile, i) => ({ ...tile, rotation: tile.fixed || ['source', 'crystal', 'rock'].includes(tile.kind) ? tile.rotation : saved.rotations[i] })) + base.moves = saved.moves + base.hints = saved.hints + base.won = simulate(base.tiles).solved + } + } catch { /* A damaged session must never prevent a fresh garden. */ } + } + return base +} + +function parseRoute(hash: string, progress: Progress): { page: Page; level: Level } { + const parts = hash.replace(/^#\/?/, '').split('/') + const fallback = LEVELS.find(level => level.id === progress.currentLevel) || LEVELS[0] + if (parts[0] === 'daily') return { page: 'daily', level: dailyLevel(/^\d{4}-\d{2}-\d{2}$/.test(parts[1] || '') ? parts[1] : today()) } + if (parts[0] === 'seed') { + const seed = /^\d{1,10}$/.test(parts[1] || '') ? Number(parts[1]) >>> 0 : 108 + const chapter = /^[0-5]$/.test(parts[2] || '') ? Number(parts[2]) : 0 + return { page: 'play', level: generateLevel(seed, chapter) } + } + if (['atlas', 'workshop', 'journal'].includes(parts[0])) return { page: parts[0] as Page, level: fallback } + return { page: 'play', level: LEVELS.find(level => level.id === parts[1]) || fallback } +} + +function Sigil({ chapter = 0, className = '' }: { chapter?: number; className?: string }) { + return +} + +function Blooms({ count, className = '' }: { count: number; className?: string }) { + return {[1, 2, 3].map(n => )} +} + +function SettingRow({ title, description, enabled, onToggle, icon }: { title: string; description: string; enabled: boolean; onToggle: () => void; icon: ReactNode }) { + return
{icon}
{title}

{description}

+} + +function Dialog({ title, children, onClose, wide = false }: { title: string; children: ReactNode; onClose: () => void; wide?: boolean }) { + const ref = useRef(null) + useEffect(() => { + const previous = document.activeElement as HTMLElement | null + const element = ref.current + element?.focus() + const trap = (event: KeyboardEvent) => { + if (event.key === 'Escape') onClose() + if (event.key !== 'Tab' || !element) return + const items = Array.from(element.querySelectorAll('button:not(:disabled),a[href],input,select,textarea,[tabindex="0"]')) + const first = items[0], last = items[items.length - 1] + if (event.shiftKey && (document.activeElement === first || document.activeElement === element)) { event.preventDefault(); last?.focus() } + else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus() } + } + document.addEventListener('keydown', trap) + const oldOverflow = document.body.style.overflow + document.body.style.overflow = 'hidden' + return () => { document.removeEventListener('keydown', trap); document.body.style.overflow = oldOverflow; previous?.focus() } + }, [onClose]) + return
{ if (event.target === event.currentTarget) onClose() }}> +
+
THE GARDENER’S COMPANION

{title}

+ {children} +
+
+} + +const memories = [ + ['The first keeper', 'Before there were roads between the stars, there were gardens. Their keepers left a light burning for anyone who had lost their way. You have found the first.'], + ['What light remembers', 'The gardener never planted a seed alone. One light became two; two became a thousand. Here, generosity was a kind of architecture.'], + ['The patient stones', 'Some stones have stood in the same place for centuries. Do not ask them to move. Listen to the shape of the space they leave you.'], + ['A river only forward', 'A traveler asked the keeper how to go back. She offered a seed instead. Some paths only open when we stop looking behind us.'], + ['Almost touching', 'Two rivers of light cross without meeting. Perhaps this is what it means to share a sky: to make room for another journey beside your own.'], + ['The distance between', 'The last garden was never far away. It was waiting on the other side of an impossible door. You brought the light. The garden remembered the rest.'], +] + +export default function App() { + const [initial] = useState(() => { + const saved = loadProgress() + const route = parseRoute(window.location.hash, saved) + const currentLevel = route.page === 'play' && LEVELS.some(level => level.id === route.level.id) + ? route.level.id : saved.currentLevel + return { route, progress: { ...saved, currentLevel } } + }) + const [progress, setProgress] = useState(initial.progress) + const [page, setPage] = useState(initial.route.page) + const [session, setSession] = useState(() => makeSession(initial.route.level, true)) + const [modal, setModal] = useState(null) + const [toast, setToast] = useState('') + const [focusMode, setFocusMode] = useState(false) + const [atlasChapter, setAtlasChapter] = useState(null) + const [workshopChapter, setWorkshopChapter] = useState(2) + const [seed, setSeed] = useState('314159') + const [importText, setImportText] = useState('') + const [backupText, setBackupText] = useState('') + const [progressStorageOk, setProgressStorageOk] = useState(true) + const [sessionStorageOk, setSessionStorageOk] = useState(true) + const storageOk = progressStorageOk && sessionStorageOk + const ambient = progress.ambience + const [mobileChapters, setMobileChapters] = useState(false) + const audio = useRef(null) + const gameArea = useRef(null) + const progressRef = useRef(progress) + const stateRef = useRef(session) + const simulation = useMemo(() => simulate(session.tiles), [session.tiles]) + const chapter = CHAPTERS[session.level.chapter] || CHAPTERS[0] + const completed = countCompleted(progress) + const totalBlooms = LEVELS.reduce((sum, level) => sum + (progress.completed[level.id]?.stars || 0), 0) + const isGarden = page === 'play' || page === 'daily' + const isCampaign = LEVELS.some(level => level.id === session.level.id) + const chapterLevels = LEVELS.filter(level => level.chapter === session.level.chapter) + const chapterComplete = chapterLevels.filter(level => progress.completed[level.id]).length + const activeLevelNumber = isCampaign ? session.level.index % 8 + 1 : null + const earnedStars = session.hints > 0 ? 1 : session.moves <= session.level.par ? 3 : 2 + const allRecords = Object.values(progress.completed) + const achievements = [ + { title: 'A little light', detail: 'Restore your first garden.', achieved: completed >= 1, icon: Sprout }, + { title: 'In full bloom', detail: 'Earn three blooms in a garden.', achieved: allRecords.some(record => record.stars === 3), icon: Flower2 }, + { title: 'The patient keeper', detail: 'Restore eight campaign gardens.', achieved: completed >= 8, icon: Leaf }, + { title: 'Morning ritual', detail: 'Restore a daily garden.', achieved: Object.keys(progress.completed).some(id => id.startsWith('daily-')), icon: Sun }, + { title: 'Uncharted paths', detail: 'Restore a workshop garden.', achieved: Object.keys(progress.completed).some(id => !id.startsWith('daily-') && !LEVELS.some(level => level.id === id)), icon: Compass }, + { title: 'A sky of gardens', detail: 'Restore all 48 campaign gardens.', achieved: completed === 48, icon: Sparkles }, + { title: 'Golden hands', detail: 'Earn 72 campaign blooms.', achieved: totalBlooms >= 72, icon: Trophy }, + { title: 'A perfect constellation', detail: 'Earn all 144 campaign blooms.', achieved: totalBlooms === 144, icon: Moon }, + ] + + useEffect(() => { progressRef.current = progress }, [progress]) + useEffect(() => { stateRef.current = session }, [session]) + useEffect(() => { + const handleHash = () => { + const route = parseRoute(window.location.hash, progressRef.current) + setPage(route.page) + setMobileChapters(false) + if (route.page === 'play' || route.page === 'daily') { + setSession(makeSession(route.level, true)) + if (LEVELS.some(level => level.id === route.level.id)) setProgress(current => ({ ...current, currentLevel: route.level.id })) + } + } + window.addEventListener('hashchange', handleHash) + return () => window.removeEventListener('hashchange', handleHash) + }, []) + useEffect(() => { + const engine = new GardenAudio() + audio.current = engine + return () => { engine.dispose(); audio.current = null } + }, []) + useEffect(() => { + const saved = saveProgress(progress) + let active = true + queueMicrotask(() => { if (active) setProgressStorageOk(saved) }) + audio.current?.setSound(progress.sound) + audio.current?.setAmbience(progress.ambience) + return () => { active = false } + }, [progress]) + useEffect(() => { + if (!isGarden) return + let saved = true + try { localStorage.setItem(SESSION_KEY, JSON.stringify({ id: session.level.id, rotations: session.tiles.map(tile => tile.rotation), moves: session.moves, hints: session.hints })) } catch { saved = false } + let active = true + queueMicrotask(() => { if (active) setSessionStorageOk(saved) }) + return () => { active = false } + }, [session, isGarden]) + useEffect(() => { + if (!toast) return + const timeout = window.setTimeout(() => setToast(''), 4200) + return () => window.clearTimeout(timeout) + }, [toast]) + + function navigate(path: string) { + setModal(null) + setFocusMode(false) + setMobileChapters(false) + if (window.location.hash === `#/${path}`) { + const route = parseRoute(`#/${path}`, progress) + setPage(route.page) + setSession(makeSession(route.level, true)) + } else window.location.hash = `/${path}` + window.scrollTo({ top: 0, behavior: 'instant' }) + } + + function rememberWin(next: Session) { + const stars = next.hints > 0 ? 1 : next.moves <= next.level.par ? 3 : 2 + setProgress(current => { + const old = current.completed[next.level.id] + const updated = { ...current, completed: { ...current.completed, [next.level.id]: { moves: old ? Math.min(old.moves, next.moves) : next.moves, stars: old ? Math.max(old.stars, stars) : stars, hints: old ? Math.min(old.hints, next.hints) : next.hints, date: today() } } } + return updated + }) + audio.current?.play('complete') + } + + function commitRotation(id: string, delta = 1, target?: number) { + const current = stateRef.current + if (current.won) return + const tile = current.tiles.find(item => item.id === id) + if (!tile || tile.fixed || ['source', 'crystal', 'rock'].includes(tile.kind)) return + const difference = target === undefined ? 1 : Math.min((target - tile.rotation + 4) % 4, (tile.rotation - target + 4) % 4) + const tiles = current.tiles.map(item => item.id === id ? target === undefined ? rotateTile(item, delta) : { ...item, rotation: target } : item) + const next: Session = { ...current, tiles, moves: current.moves + difference, history: [...current.history.slice(-499), { tiles: current.tiles, moves: current.moves }], hintId: null, hintRotation: null, won: simulate(tiles).solved } + stateRef.current = next + setSession(next) + if (next.won) rememberWin(next) + else audio.current?.play('rotate') + } + + function undo() { + const current = stateRef.current + const previous = current.history.at(-1) + if (!previous) return + const next = { ...current, ...previous, history: current.history.slice(0, -1), won: false, hintId: null, hintRotation: null } + stateRef.current = next + setSession(next) + audio.current?.play('rotate') + } + + function restart() { + const next = makeSession(stateRef.current.level) + stateRef.current = next + setSession(next) + audio.current?.play('reset') + setToast('A fresh start. The light is waiting for you.') + } + + function hint() { + const current = stateRef.current + if (current.won) return + if (current.hintId && current.hintRotation !== null) { + commitRotation(current.hintId, 1, current.hintRotation) + return + } + const nextHint = getHint(current.level, current.tiles) + if (!nextHint) return + const next = { ...current, hintId: nextHint.tileId, hintRotation: nextHint.rotation, hints: current.hints + 1 } + stateRef.current = next + setSession(next) + audio.current?.play('hint') + setToast('Try the highlighted stone. Tap the hint again to turn it into place.') + } + + useEffect(() => { + const handleKey = (event: KeyboardEvent) => { + if (event.ctrlKey || event.metaKey || event.altKey || modal || !isGarden || (event.target instanceof HTMLElement && ['INPUT', 'TEXTAREA', 'SELECT'].includes(event.target.tagName))) return + if (event.key.toLowerCase() === 'z' || event.key.toLowerCase() === 'u') { event.preventDefault(); undo() } + if (event.key.toLowerCase() === 'r') { event.preventDefault(); restart() } + if (event.key.toLowerCase() === 'h') { event.preventDefault(); hint() } + if (event.key === '?') setModal('guide') + if (event.key === 'Escape') setFocusMode(false) + } + window.addEventListener('keydown', handleKey) + return () => window.removeEventListener('keydown', handleKey) + }) + + function changeSetting(key: K, value: Progress[K]) { + const next = { ...progress, [key]: value } + setProgress(next) + setProgressStorageOk(saveProgress(next)) + if (key === 'sound') audio.current?.setSound(Boolean(value)) + } + + function toggleAmbience() { + const next = !ambient + changeSetting('ambience', next) + audio.current?.setAmbience(next) + } + + async function copyText(value: string, message: string) { + try { await navigator.clipboard.writeText(value); setToast(message) } + catch { setBackupText(value); setModal('settings'); setToast('Copy the text in the backup field below.') } + } + + function shareGarden() { + const hash = session.level.id.startsWith('daily-') ? `/daily/${session.level.id.slice(6)}` : window.location.hash.slice(1) || `/play/${session.level.id}` + void copyText(`${window.location.href.split('#')[0]}#${hash}`, 'Garden link copied. Share a little light.') + } + + function nextGarden() { + if (!isCampaign) { navigate(page === 'daily' ? 'atlas' : 'workshop'); return } + const next = LEVELS[session.level.index + 1] + if (next) navigate(`play/${next.id}`) + else navigate('journal') + } + + function launchWorkshop() { + if (!/^\d{1,10}$/.test(seed) || Number(seed) > 4294967295) { setToast('Choose a seed from 0 to 4,294,967,295.'); return } + navigate(`seed/${Number(seed)}/${workshopChapter}`) + } + + const onCloseModal = useMemo(() => () => setModal(null), []) + + return
+ { event.preventDefault(); document.getElementById('main-content')?.focus() }}>Skip to garden +
+ + +
+
+ +
+ + +
+ {isGarden && <> +

{session.level.title}✳

{session.level.subtitle}

+ +
+
+
+
{page === 'daily' ? 'TODAY’S GARDEN' : isCampaign ? `GARDEN ${String(session.level.index + 1).padStart(2, '0')}` : 'THE WORKSHOP'}{session.level.size} × {session.level.size} · {MECHANICS[session.level.chapter]}
+ +
0 ? 'lit' : ''}`} />{simulation.litCrystals} / {simulation.totalCrystals} crystals awake{session.won ? 'The garden remembers.' : 'Follow the light.'}
+ {session.won &&
A little corner of the world, alight.
} +
+
+

Click a stone to rotateShift + click to turn backTake your time.

+
+ + +
+ + {isCampaign ?
{chapter.title.toUpperCase()}{chapterComplete} of 8 restored
{chapterLevels.map((level, i) => )}
:
{page === 'daily' ? 'The same sky. The same garden. A new possibility every day.' : 'Every seed is a different place to begin.'}
} + } + + {page === 'atlas' && <> +
AN EXPEDITION IN SIX CHAPTERS

A world waiting for light.

Forty-eight quiet places. A thousand ways to begin.

{completed}/48GARDENS RESTORED
+

Every chapter is open. Follow the journey, or wander wherever your curiosity takes you.

{totalBlooms} / 144 blooms
+
{CHAPTERS.map((item, i) => { + const levels = LEVELS.filter(level => level.chapter === i) + const done = levels.filter(level => progress.completed[level.id]).length + return
CHAPTER {ROMAN[i]} · 8 GARDENS

{item.title}

{item.description}

{done} / 8
{atlasChapter === i &&
{levels.map((level, n) => )}
}
+ })}
+ } + + {page === 'workshop' && <> +
BEYOND THE KNOWN GARDENS

Make room for wonder.

A seed, a little curiosity, and a garden that has never been yours before.

+
POSSIBILITY TAKES ROOT

One seed.
Endless beginnings.

Nº {seed || '0'}
YOUR GARDEN RECIPE

Plant a new puzzle.

Choose a chapter’s rules and give your garden a seed. The same recipe always grows the same puzzle, ready to share.

setSeed(event.target.value.replace(/\D/g, ''))} onKeyDown={event => { if (event.key === 'Enter') launchWorkshop() }} />

Any whole number from 0 to 4,294,967,295.

Every garden has a verified path to the light.
+
A FEW SEEDS TO GET YOU STARTED
{[{ seed: 1729, chapter: 0, title: 'An easy morning', description: 'A small, quiet place to find your rhythm.' }, { seed: 271828, chapter: 3, title: 'The long way home', description: 'One-way paths reward a patient eye.' }, { seed: 1618033, chapter: 5, title: 'Somewhere, elsewhere', description: 'Step through a door in the light.' }].map(item => )}
+ } + + {page === 'journal' && <> +
FRAGMENTS FROM AN OLD FIELD BOOK

The keeper’s journal.

Some things return slowly. A garden. A memory. A little hope.

+
Y

ou arrived with nothing but a lantern and a feeling that something here was still alive. With every garden you restore, another page finds its way back into this book.

{completed}GARDENS REMEMBERED
+
{memories.map(([title, text], i) => { + const unlocked = LEVELS.some(level => level.chapter === i && progress.completed[level.id]) + return
MEMORY {ROMAN[i]}

{unlocked ? title : 'A page yet to return.'}

{unlocked ? text : `Restore a garden in ${CHAPTERS[i].title} to recover this memory.`}

+ })}
+ {completed === 48 &&
THE LAST PAGE

The keeper was always you.

Forty-eight gardens. Forty-eight small acts of care. There is no grand secret at the end of this journey, only this: a world becomes beautiful when someone chooses to tend it.

Thank you for bringing the light.

} + } + +
A QUIET ADVENTURE IN LIGHT{storageOk ? 'Your journey is saved on this device' : 'Storage unavailable — keep a backup in Settings'}
+
+
+ + {modal && + {modal === 'guide' &&

Nothing is lost. Nothing is timed. Turn the channels until the light reaches every crystal.

01

Find the source

The golden lantern is always alight. Follow its channel into the garden.

02

Turn the stones

Click a stone to rotate it clockwise. Its channel must meet the next stone’s channel.

03

Wake every crystal

Connect all the crystals to the source. Light travels automatically along joined channels.

The shapes of a garden

{[{ symbol: '└', name: 'Channels', text: 'Straight stones and corners carry light through their open ends.' }, { symbol: '┬', name: 'Branches', text: 'A branch sends light down every connected arm. Find more than one path.' }, { symbol: '◈', name: 'Rooted stones', text: 'Stones marked with small pins cannot turn. Sources and crystals stay still, too.' }, { symbol: '↑', name: 'One-way gates', text: 'Light can only pass in the arrow’s direction. Turning the gate changes its direction.' }, { symbol: '╪', name: 'Bridges', text: 'Two channels cross but never join. North connects south; east connects west.' }, { symbol: '◎', name: 'Portals', text: 'Matching letters share a doorway. Light entering one leaves through its partner.' }].map(item =>
{item.symbol}

{item.name}

{item.text}

)}

A little help is always here.

Hints first mark a stone, then offer to turn it for you. Earn three blooms within the gardener’s guide without hints; two for any unassisted solution; one with a helping hand. Every garden remains open.

Tab Select stoneEnter / Space TurnShift + click / Enter · ReverseZ Undo R Restart H Hint
} + {modal === 'settings' &&

A little space, just the way you like it.

changeSetting('sound', !progress.sound)} icon={} />} /> changeSetting('reduceMotion', !progress.reduceMotion)} icon={} /> changeSetting('highContrast', !progress.highContrast)} icon={} />

Keep your journey close.

Your progress stays in this browser. Copy a backup to take your completed gardens to another device.

{backupText && <>