commit cc7aec8db51782eec53ec85e8c72fa7a772bb69d Author: Yuriy Mayatnikov Date: Sat Jun 27 08:37:40 2026 +0300 Initialize calm game project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94e88ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +# Codex/local agent state +.codex/logs/ +.codex/cache/ +.agents/logs/ +.agents/cache/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..c0c80ba --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=false diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2a715a6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,45 @@ +# AGENTS.md + +SvelteKit + TypeScript family quiz game. + +This file is the durable project contract for AI coding agents. Keep reusable +agent workflow guidance here or under `development/_reference/ai/`. + +## Workflow + +- Codex is the orchestrator, verifier, reviewer, and committer. +- Use `npm run agent:loop -- --task ""` when Codex delegates + implementation to the Pi worker harness. +- Worker agents receive one concrete task. They must not broaden scope, commit, + push, deploy, rewrite git history, or edit agent workflow files unless the task + explicitly asks. +- Progress is captured through reviewed git checkpoint commits, not through + long-lived execution logs. +- Technical run artifacts live in `.codex/logs/agent-runs/` and are ignored. + +## Verification + +Run the narrowest relevant checks after changes: + +- `npm run check` for Svelte/TypeScript changes. +- `npm run build` for production-build confidence. +- Use a real local browser smoke for visible UI behavior changes. + +Never claim work is done until there is a code diff and observed verification. + +## Project Shape + +- `src/lib/types.ts` holds domain types. +- `src/lib/question-sets/` holds question data. +- `src/lib/stores/game.ts` owns game state and localStorage persistence. +- `src/lib/components/` holds UI components. +- `src/routes/+page.svelte` orchestrates screens. + +## Product Rules + +- Keep the game calm, family-friendly, and host-led. +- Preserve the separation between question data, state, and UI. +- Do not add backend, auth, database, multiplayer, or an editor unless explicitly + requested. +- Keep user-facing copy consistent with the language and tone of the touched UI. +- Prefer small, focused changes and checkpoint commits. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f569b0a --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +# 🌙 Уютная викторина — Calm Family Quiz + +Спокойная семейная игра-викторина в стиле «Своей игры», созданная для уютного +вечера перед сном. Все смотрят на одно табло, ведущий управляет игрой, команда +отвечает вместе — без таймеров, давления и спешки. + +## 🚀 Запуск + +```bash +npm install +npm run dev +``` + +Откройте адрес из консоли (обычно `http://localhost:5173`). + +### Прочее + +```bash +npm run build # production-сборка +npm run preview # предпросмотр сборки +npm run check # проверка типов (svelte-check) +``` + +## 🎮 Как играть + +1. **Выбор набора** — выберите тему вопросов. +2. **Табло** — категории и карточки очков (100 / 200 / 300 / 500 / 1000). + Чем дороже карточка, тем сложнее вопрос. +3. Ведущий открывает вопрос, затем «Показать ответ», и решает: + - **Засчитать +N** — команде идут очки; + - **Не засчитывать** — без очков, но вопрос закрыт. + - Штрафов и минусов нет. +4. Для творческих вопросов (type `creative`) кнопка мягче: + «Засчитать творческий ответ +N». +5. Сыгранный вопрос затемняется и больше не выбирается. +6. Когда сыграны все вопросы — экран результата с мягкой финальной фразой. + +Прогресс сохраняется автоматически — после перезагрузки игра продолжится с того же места. + +## 🧩 Добавление нового набора вопросов + +Архитектура задумана так, чтобы наборы добавлялись без правок UI. + +1. Создайте файл `src/lib/question-sets/.ts`: + + ```ts + import type { QuestionSet } from '$lib/types'; + + export const mySet: QuestionSet = { + id: 'my-set', // уникальный + title: 'Моя тема', + description: 'Короткое описание', + ageRange: '6+ лет', + categories: [ + { + id: 'cat-1', // уникально в пределах набора + title: 'Категория', + questions: [ + { id: 'q1', points: 100, question: '...', answer: '...', type: 'strict' }, + { id: 'q2', points: 200, question: '...', answer: '...', type: 'strict' }, + { id: 'q3', points: 300, question: '...', answer: '...', type: 'strict' }, + { id: 'q4', points: 500, question: '...', answer: '...', type: 'creative' }, + { id: 'q5', points: 1000, question: '...', answer: '...', type: 'strict' } + // ровно 5 вопросов с очками 100/200/300/500/1000 + ] + } + // …остальные категории + ] + }; + ``` + +2. Зарегистрируйте набор в `src/lib/question-sets/index.ts`: + + ```ts + import { mySet } from './my-set'; + export const questionSets = [animalsSet, cozySet, mySet]; + ``` + +Готово — набор автоматически появится на экране выбора. + +## 🏗️ Архитектура + +Код аккуратно разделён по слоям: + +``` +src/ +├─ lib/ +│ ├─ types.ts # Доменные типы (Question, Category, QuestionSet, GameState) +│ ├─ question-sets/ # ДАННЫЕ (отделены от UI) +│ │ ├─ animals.ts # набор-заглушка +│ │ ├─ cozy.ts # набор-заглушка +│ │ └─ index.ts # реестр наборов + хелперы поиска +│ ├─ stores/ +│ │ └─ game.ts # СОСТОЯНИЕ игры (Svelte store + localStorage) +│ └─ components/ # UI-компоненты +│ ├─ QuestionSetSelector.svelte +│ ├─ GameBoard.svelte +│ ├─ QuestionCard.svelte +│ ├─ ScorePanel.svelte +│ ├─ QuestionModal.svelte +│ ├─ HostControls.svelte +│ ├─ GameResult.svelte +│ └─ ConfirmDialog.svelte +└─ routes/ + ├─ +layout.svelte # подключение глобальных стилей + ├─ +layout.ts # CSR-only (localStorage) + └─ +page.svelte # оркестрация экранов +``` + +**Принципы:** +- Данные вопросов полностью отделены от состояния и UI. +- Состояние игры живёт в Svelte store с автосохранением в `localStorage` + и восстанавливается после перезагрузки. +- Очки берутся из данных вопроса — ничего не хардкодится в UI. +- Никакого бэкенда, БД, авторизации, мультиплеера или редактора в MVP. + +## 🎨 Стек + +- **SvelteKit + TypeScript** +- **Tailwind CSS v4** (через `@tailwindcss/vite`) +- Без тяжёлых UI-библиотек — только лёгкие зависимости. + +Лицензия: личный/семейный проект. diff --git a/development/_reference/ai/README.md b/development/_reference/ai/README.md new file mode 100644 index 0000000..0454cd2 --- /dev/null +++ b/development/_reference/ai/README.md @@ -0,0 +1,16 @@ +# AI Reference + +Shared references for Codex and other AI coding agents in this project. + +## Entry Points + +| Topic | Location | +|---|---| +| Project contract | [AGENTS.md](../../../AGENTS.md) | +| Autonomous agent harness | [autonomous-agent-harness.md](autonomous-agent-harness.md) | +| Worker loop command | [scripts/run-agent-loop.mjs](scripts/run-agent-loop.mjs) | +| Pi worker wrapper | [scripts/run-pi-worker.mjs](scripts/run-pi-worker.mjs) | + +Use `npm run agent:loop -- --task ""` when Codex delegates +implementation to Pi. Codex remains the orchestrator, verifier, reviewer, and +committer. diff --git a/development/_reference/ai/autonomous-agent-harness.md b/development/_reference/ai/autonomous-agent-harness.md new file mode 100644 index 0000000..10069b2 --- /dev/null +++ b/development/_reference/ai/autonomous-agent-harness.md @@ -0,0 +1,68 @@ +# Autonomous Agent Harness + +This project uses a Codex-led development loop with independent worker, +verifier, and reviewer roles. + +## Core Model + +1. Codex reads `AGENTS.md`, the current git state, relevant code, and any + project notes. +2. Codex defines one concrete worker task from the user's request. +3. Codex may use independent reviewer/verifier subagents for focused checks. +4. Pi is used as the implementation worker through `npm run agent:loop`. +5. Codex inspects the diff, runs checks, performs review, and asks for a worker + fix pass if needed. +6. Once accepted, Codex stages only the expected files and creates a git + checkpoint commit. + +The worker is autonomous inside its assignment, but it does not own "done". It +must not commit, push, deploy, rewrite history, or start adjacent tasks. + +## State + +Git checkpoint commits are the durable state. Avoid long-lived execution logs for +ordinary implementation progress. + +Pi run artifacts are technical traces only and are ignored under +`.codex/logs/agent-runs/`. + +## Command + +```bash +npm run agent:loop -- \ + --task "Add a calm end-game reset flow and verify the Svelte app." +``` + +Use `--name` only when a short run label helps: + +```bash +npm run agent:loop -- \ + --name "End game reset" \ + --task "Add a reset flow on the result screen. Keep the copy calm and run the relevant checks." +``` + +## Gate + +After a worker run, Codex accepts output only when `summary.json` reports +`orchestratorStatus=WORKER_DONE_PENDING_CODEX_GATE`. Then Codex runs: + +- `git status --short` +- `git diff --check` +- `git status --short -- .pi .agents .codex AGENTS.md development/_reference/ai` +- automatic checks selected from changed files + +For app changes the automatic checks are: + +- `npm run check` +- `npm run build` + +Visible UI changes still need a real local browser smoke before a commit. + +## Stop Conditions + +Stop and ask the user when: + +- the task needs product/design judgment not present in context; +- credentials, deployment, external services, or destructive commands are needed; +- unrelated local changes block safe progress; +- reviewer returns the same blocking concern twice after fix attempts. diff --git a/development/_reference/ai/scripts/run-agent-loop.mjs b/development/_reference/ai/scripts/run-agent-loop.mjs new file mode 100755 index 0000000..c28ab21 --- /dev/null +++ b/development/_reference/ai/scripts/run-agent-loop.mjs @@ -0,0 +1,454 @@ +#!/usr/bin/env node + +import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; +import { basename, join, resolve } from 'node:path'; +import { spawnSync } from 'node:child_process'; + +const DONE_STATUS = 'WORKER_DONE_PENDING_CODEX_GATE'; + +function parseArgs(argv) { + const out = {}; + const booleans = new Set(['dry-run', 'gate-only', 'skip-worker', 'help']); + + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + if (!arg.startsWith('--')) { + out._ = out._ || []; + out._.push(arg); + continue; + } + + const key = arg.slice(2); + if (booleans.has(key)) { + out[key] = true; + continue; + } + + const value = argv[i + 1]; + if (!value || value.startsWith('--')) { + throw new Error(`Missing value for --${key}`); + } + out[key] = value; + i += 1; + } + + return out; +} + +function usage() { + return `Usage: + npm run agent:loop -- --task "Add a calm end-game reset flow and verify the Svelte app." + +Options: + --version Development version. Defaults to v. + --name Optional run label. Defaults to a slug from --task. + --task Worker assignment text. Required unless --prompt-file is used. + --prompt-file Optional file with a longer Codex-authored assignment. + --run-dir Existing .codex/logs/agent-runs//... directory for --gate-only. + --runs-dir Optional base directory for run artifacts. Defaults to .codex/logs/agent-runs. + --run-checks none, auto, or full. Defaults to auto. + auto runs git gate and existing npm check/build scripts for app changes. + full is currently the same as auto unless more project checks are added. + --model Optional Pi model. Forwarded to run-pi-worker.mjs. + --thinking Optional Pi thinking level. Forwarded to run-pi-worker.mjs. + --pi Optional pi binary path. Forwarded to run-pi-worker.mjs. + --session-id Optional Pi session id. Forwarded to run-pi-worker.mjs. + --event-idle-timeout-ms + Optional Pi idle watchdog. Forwarded to run-pi-worker.mjs. + --dry-run Generate the Pi prompt/artifacts only; skip the Codex gate. + --gate-only Do not launch Pi; run the Codex gate for --run-dir or latest run. +`; +} + +function readJson(path) { + return JSON.parse(readFileSync(path, 'utf8')); +} + +function rootVersion(cwd) { + const pkg = readJson(resolve(cwd, 'package.json')); + if (!pkg.version) throw new Error('Root package.json has no version'); + return pkg.version; +} + +function normalizeVersion(value) { + const raw = String(value || '').trim(); + if (!raw) return raw; + return raw.startsWith('v') ? raw : `v${raw}`; +} + +function slugify(value) { + return String(value || 'worker') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 80) || 'worker'; +} + +function shellQuote(value) { + const text = String(value); + if (/^[a-zA-Z0-9_./:=@+-]+$/.test(text)) return text; + return `'${text.replace(/'/g, "'\\''")}'`; +} + +function formatCommand(command, args) { + return [command, ...args].map(shellQuote).join(' '); +} + +function printHeading(title) { + console.log(`\n== ${title} ==`); +} + +function capture(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: options.cwd || process.cwd(), + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }); + + if (result.error) { + return { + command: formatCommand(command, args), + cwd: options.cwd || process.cwd(), + code: 1, + signal: null, + stdout: result.stdout || '', + stderr: `${result.stderr || ''}${result.error.message || result.error}\n`, + }; + } + + return { + command: formatCommand(command, args), + cwd: options.cwd || process.cwd(), + code: result.status ?? (result.signal ? 1 : 0), + signal: result.signal, + stdout: result.stdout || '', + stderr: result.stderr || '', + }; +} + +function runCheck(label, command, args, options = {}) { + console.log(`\n$ ${formatCommand(command, args)}`); + const result = capture(command, args, options); + if (result.stdout.trim()) process.stdout.write(result.stdout); + if (result.stderr.trim()) process.stderr.write(result.stderr); + if (!result.stdout.trim() && !result.stderr.trim()) console.log('(no output)'); + console.log(`[exit ${result.code}] ${label}`); + return { label, ...result }; +} + +function lines(text) { + return String(text || '') + .split('\n') + .map((line) => line.trim()) + .filter(Boolean); +} + +function uniqueSorted(values) { + return [...new Set(values.filter(Boolean))].sort((a, b) => a.localeCompare(b)); +} + +function getChangedFiles(cwd, pathspecs = []) { + const tracked = capture('git', ['diff', '--name-only', 'HEAD', '--', ...pathspecs], { cwd }); + const untracked = capture('git', ['ls-files', '--others', '--exclude-standard', '--', ...pathspecs], { cwd }); + + return uniqueSorted([ + ...(tracked.code === 0 ? lines(tracked.stdout) : []), + ...(untracked.code === 0 ? lines(untracked.stdout) : []), + ]); +} + +function defaultRunsDir(cwd, args) { + return resolve(cwd, args['runs-dir'] || '.codex/logs/agent-runs'); +} + +function newestRunDir(cwd, args, version, name) { + const runsDir = resolve(defaultRunsDir(cwd, args), version); + if (!existsSync(runsDir)) return null; + + const wantedSuffix = name ? `-pi-worker-${slugify(name)}` : null; + const dirs = readdirSync(runsDir) + .map((name) => resolve(runsDir, name)) + .filter((path) => { + if (!existsSync(path) || !statSync(path).isDirectory()) return false; + if (wantedSuffix && !basename(path).endsWith(wantedSuffix)) return false; + return existsSync(join(path, 'summary.json')); + }) + .map((path) => ({ path, mtimeMs: statSync(path).mtimeMs })) + .sort((a, b) => b.mtimeMs - a.mtimeMs); + + return dirs[0]?.path || null; +} + +function resolveRunDir(cwd, args, version, name) { + if (args['run-dir']) { + const runDir = resolve(cwd, args['run-dir']); + if (!existsSync(runDir)) throw new Error(`Run directory does not exist: ${runDir}`); + return runDir; + } + + const runDir = newestRunDir(cwd, args, version, name); + if (!runDir) { + throw new Error(`Could not find an agent run for ${version}${name ? ` / ${name}` : ''}`); + } + return runDir; +} + +function summaryVersionFromRunDir(cwd, args) { + if (!args['run-dir']) return null; + const summaryPath = resolve(cwd, args['run-dir'], 'summary.json'); + if (!existsSync(summaryPath)) return null; + const summary = readJson(summaryPath); + return summary.version ? normalizeVersion(summary.version) : null; +} + +function runWorker(cwd, args, version, name) { + const workerScript = resolve(cwd, 'development/_reference/ai/scripts/run-pi-worker.mjs'); + const workerArgs = [ + workerScript, + '--version', + version, + ]; + + if (name) workerArgs.push('--name', name); + if (args.task) workerArgs.push('--task', args.task); + if (args['prompt-file']) workerArgs.push('--prompt-file', args['prompt-file']); + if (args['runs-dir']) workerArgs.push('--runs-dir', args['runs-dir']); + if (args.model) workerArgs.push('--model', args.model); + if (args.thinking) workerArgs.push('--thinking', args.thinking); + if (args.pi) workerArgs.push('--pi', args.pi); + if (args['session-id']) workerArgs.push('--session-id', args['session-id']); + if (args['event-idle-timeout-ms']) { + workerArgs.push('--event-idle-timeout-ms', args['event-idle-timeout-ms']); + } + if (args['dry-run']) workerArgs.push('--dry-run'); + + printHeading('Pi Worker'); + console.log(`$ ${formatCommand(process.execPath, workerArgs)}`); + const result = spawnSync(process.execPath, workerArgs, { + cwd, + env: process.env, + stdio: 'inherit', + }); + + if (result.error) { + console.error(result.error.message || result.error); + } + + return { + code: result.error ? 1 : (result.status ?? (result.signal ? 1 : 0)), + signal: result.signal, + }; +} + +function forbiddenPathspecs(version) { + return [ + '.pi', + '.agents', + '.codex', + 'AGENTS.md', + 'development/_reference/ai', + ]; +} + +function packageScripts(cwd) { + try { + return readJson(resolve(cwd, 'package.json')).scripts || {}; + } catch { + return {}; + } +} + +function hasScript(cwd, name) { + return Object.prototype.hasOwnProperty.call(packageScripts(cwd), name); +} + +function selectAutoChecks(cwd, changedFiles, mode) { + if (mode === 'none') return []; + + const checks = []; + const appChanged = changedFiles.some((file) => ( + file.startsWith('src/') || + file.startsWith('static/') || + [ + 'package.json', + 'package-lock.json', + 'svelte.config.js', + 'tsconfig.json', + 'vite.config.ts', + 'vite.config.js', + ].includes(file) + )); + + if (appChanged) { + if (hasScript(cwd, 'check')) checks.push({ label: 'Svelte/type check', command: 'npm', args: ['run', 'check'], cwd }); + if (hasScript(cwd, 'build')) checks.push({ label: 'Production build', command: 'npm', args: ['run', 'build'], cwd }); + } + + return checks; +} + +function hasUiBehaviorRisk(changedFiles) { + return changedFiles.some((file) => { + if (!file.startsWith('src/') && !file.startsWith('static/')) return false; + return ['.svelte', '.ts', '.js', '.html', '.css', '.svg'].some((suffix) => file.endsWith(suffix)); + }); +} + +function writeGateArtifact(runDir, payload) { + const path = join(runDir, 'codex-gate.json'); + writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`); + return path; +} + +function runGate({ cwd, args, version, name, preForbiddenFiles, workerResult }) { + const runChecks = args['run-checks'] || 'auto'; + if (!['none', 'auto', 'full'].includes(runChecks)) { + throw new Error(`Unsupported --run-checks ${runChecks}. Expected none, auto, or full.`); + } + + const runDir = resolveRunDir(cwd, args, version, name); + const summaryPath = join(runDir, 'summary.json'); + if (!existsSync(summaryPath)) throw new Error(`Missing summary.json in ${runDir}`); + + const summary = readJson(summaryPath); + const checks = []; + const failures = []; + + printHeading('Worker Summary'); + console.log(`Run directory: ${runDir}`); + console.log(`Final status: ${summary.finalStatus || 'null'}`); + console.log(`Orchestrator status: ${summary.orchestratorStatus || 'null'}`); + console.log(`Last message: ${summary.lastAssistantTextPath || join(runDir, 'last-message.txt')}`); + if (workerResult?.code) { + console.log(`Worker process exit: ${workerResult.code}${workerResult.signal ? ` (${workerResult.signal})` : ''}`); + } + + printHeading('Codex Gate'); + checks.push(runCheck('git status', 'git', ['status', '--short'], { cwd })); + checks.push(runCheck('git diff whitespace check', 'git', ['diff', '--check'], { cwd })); + + const forbidden = forbiddenPathspecs(version); + const forbiddenStatusCheck = runCheck( + 'forbidden orchestration path status', + 'git', + ['status', '--short', '--', ...forbidden], + { cwd }, + ); + checks.push(forbiddenStatusCheck); + + const currentForbiddenFiles = getChangedFiles(cwd, forbidden); + const preForbidden = new Set(preForbiddenFiles || []); + const newForbiddenFiles = currentForbiddenFiles.filter((file) => !preForbidden.has(file)); + + if (summary.orchestratorStatus !== DONE_STATUS) { + failures.push(`worker status is ${summary.orchestratorStatus || 'missing'}, expected ${DONE_STATUS}`); + } + for (const check of checks) { + if (check.code !== 0) failures.push(`${check.label} exited ${check.code}`); + } + if (newForbiddenFiles.length > 0) { + failures.push(`forbidden paths changed during/after worker: ${newForbiddenFiles.join(', ')}`); + } + + const changedFiles = getChangedFiles(cwd); + const autoChecks = []; + if (failures.length === 0) { + const selectedChecks = selectAutoChecks(cwd, changedFiles, runChecks); + if (selectedChecks.length > 0) printHeading('Auto Checks'); + for (const check of selectedChecks) { + const result = runCheck(check.label, check.command, check.args, { cwd: check.cwd }); + autoChecks.push(result); + if (result.code !== 0) failures.push(`${check.label} exited ${result.code}`); + } + } else { + console.log('\nSkipping auto checks because the basic gate did not pass.'); + } + + const uiSmokeRequired = hasUiBehaviorRisk(changedFiles); + const status = failures.length === 0 ? 'PASSED_PENDING_REVIEW' : 'FAILED'; + const gatePath = writeGateArtifact(runDir, { + status, + generatedAt: new Date().toISOString(), + runDir, + version, + name, + runChecks, + workerResult: workerResult || null, + workerSummary: { + finalStatus: summary.finalStatus || null, + orchestratorStatus: summary.orchestratorStatus || null, + lastAssistantTextPath: summary.lastAssistantTextPath || null, + gitStatusAfter: summary.gitStatusAfter || null, + }, + changedFiles, + forbiddenFiles: currentForbiddenFiles, + newForbiddenFiles, + checks, + autoChecks, + uiSmokeRequired, + failures, + }); + + printHeading('Gate Result'); + console.log(`Gate artifact: ${gatePath}`); + console.log(`Changed files: ${changedFiles.length ? changedFiles.join(', ') : '(none)'}`); + if (uiSmokeRequired) { + console.log('UI smoke: required if this changed visible behavior; use the local stand and a real browser before commit.'); + } + + if (failures.length > 0) { + console.log('Verdict: FAILED'); + for (const failure of failures) console.log(`- ${failure}`); + return 1; + } + + console.log('Verdict: PASSED_PENDING_REVIEW'); + console.log('Next: Codex reviews the diff, then stages only expected files and commits if approved.'); + return 0; +} + +function main() { + const cwd = process.cwd(); + const args = parseArgs(process.argv.slice(2)); + + if (args.help) { + console.log(usage()); + return; + } + + if (!existsSync(resolve(cwd, 'AGENTS.md')) || !existsSync(resolve(cwd, 'development/_reference/ai/scripts/run-pi-worker.mjs'))) { + throw new Error('Run this command from the project repository root.'); + } + + const gateOnly = Boolean(args['gate-only'] || args['skip-worker']); + const runDirVersion = gateOnly ? summaryVersionFromRunDir(cwd, args) : null; + const version = normalizeVersion(args.version || runDirVersion || rootVersion(cwd)); + if (args.version && runDirVersion && normalizeVersion(args.version) !== runDirVersion) { + throw new Error(`--version ${normalizeVersion(args.version)} does not match run summary version ${runDirVersion}`); + } + const name = args.name || (gateOnly ? '' : slugify(args.task || args['prompt-file'] || 'worker')); + const hasTask = Boolean(args.task || args['prompt-file']); + + if (!gateOnly && !hasTask) throw new Error(`Missing --task or --prompt-file\n\n${usage()}`); + + const runsDir = resolve(defaultRunsDir(cwd, args), version); + mkdirSync(runsDir, { recursive: true }); + + const preForbiddenFiles = gateOnly ? [] : getChangedFiles(cwd, forbiddenPathspecs(version)); + const workerResult = gateOnly ? null : runWorker(cwd, args, version, name); + + if (args['dry-run']) { + console.log('\nDry run complete. Pi was not launched and the Codex gate was skipped.'); + process.exit(workerResult.code || 0); + } + + const exitCode = runGate({ cwd, args, version, name, preForbiddenFiles, workerResult }); + process.exit(exitCode); +} + +try { + main(); +} catch (error) { + console.error(error.message || error); + process.exit(1); +} diff --git a/development/_reference/ai/scripts/run-pi-worker.mjs b/development/_reference/ai/scripts/run-pi-worker.mjs new file mode 100755 index 0000000..cf55e1b --- /dev/null +++ b/development/_reference/ai/scripts/run-pi-worker.mjs @@ -0,0 +1,579 @@ +#!/usr/bin/env node + +import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { join, resolve } from 'node:path'; +import { execFileSync, spawn } from 'node:child_process'; + +function parseArgs(argv) { + const out = {}; + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + if (!arg.startsWith('--')) { + out._ = out._ || []; + out._.push(arg); + continue; + } + const key = arg.slice(2); + if (key === 'dry-run') { + out[key] = true; + continue; + } + const value = argv[i + 1]; + if (!value || value.startsWith('--')) { + throw new Error(`Missing value for --${key}`); + } + out[key] = value; + i += 1; + } + return out; +} + +function usage() { + return `Usage: + node development/_reference/ai/scripts/run-pi-worker.mjs \\ + --version v0.1.0 \\ + --name "Add calm reset flow" \\ + --task "Implement only the concrete worker task described by Codex." + +Options: + --version Required development version, for example v0.6.47. + --name Optional run label. Defaults to a slug from --task. + --task Worker assignment text. Required unless --prompt-file is used. + --prompt-file Optional file with a longer Codex-authored assignment. + --session-id Optional Pi session id. Defaults to a stable version/name id. + --model Optional Pi model. Defaults to zai/glm-5.2. + --thinking Optional Pi thinking level. Defaults to xhigh, which Z.ai maps to reasoning_effort=max. + --pi Optional pi binary path. Defaults to PI_BIN or pi. + --pi-mode Optional Pi mode: json or text. Defaults to json. + --runs-dir Optional base directory for run artifacts. Defaults to .codex/logs/agent-runs. + --event-idle-timeout-ms + Optional watchdog timeout without worker output. Defaults to 600000 (10 minutes). Use 0 to disable. + --dry-run Write prompt/meta, print command, but do not launch Pi. +`; +} + +function requireArg(args, name) { + if (!args[name]) throw new Error(`Missing required --${name}\n\n${usage()}`); + return args[name]; +} + +function slugify(value) { + return String(value || 'worker') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 80) || 'worker'; +} + +function timestamp() { + return new Date().toISOString().replace(/[:.]/g, '-'); +} + +function readOptionalFile(path) { + if (!path) return ''; + return readFileSync(resolve(path), 'utf8').trim(); +} + +function parseNonNegativeInt(value, fallback) { + if (value === undefined) return fallback; + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed) || parsed < 0) { + throw new Error(`Expected a non-negative integer, got: ${value}`); + } + return parsed; +} + +function getGitStatus(cwd) { + try { + return execFileSync('git', ['status', '--short'], { + cwd, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }).trim(); + } catch (error) { + return `GIT_STATUS_ERROR: ${error.message || error}`; + } +} + +function textFromContent(value) { + if (!value) return ''; + if (typeof value === 'string') return value; + if (Array.isArray(value)) return value.map(textFromContent).filter(Boolean).join(''); + if (typeof value !== 'object') return ''; + + if (typeof value.text === 'string') return value.text; + if (typeof value.delta === 'string') return value.delta; + if (typeof value.content === 'string') return value.content; + if (Array.isArray(value.content)) return textFromContent(value.content); + if (value.type === 'text' && typeof value.value === 'string') return value.value; + + return ''; +} + +function textFromMessage(message) { + if (!message || message.role !== 'assistant') return ''; + return textFromContent(message.content).trim(); +} + +function extractTextDelta(event) { + const update = event?.assistantMessageEvent; + if (!update || typeof update !== 'object') return ''; + if (typeof update.delta === 'string') return update.delta; + if (typeof update.text === 'string') return update.text; + return ''; +} + +function findLastAssistantTextFromMessages(messages) { + if (!Array.isArray(messages)) return ''; + for (let i = messages.length - 1; i >= 0; i -= 1) { + const text = textFromMessage(messages[i]); + if (text) return text; + } + return ''; +} + +function findFinalStatus(text) { + const matches = [...String(text || '').matchAll(/(?:^|\n)\s*WORKER_STATUS:\s*(WORKER_DONE|BLOCKED|NEEDS_DISCUSSION)\b/g)]; + return matches.at(-1)?.[1] || null; +} + +function summarizeOrchestratorStatus({ finalStatus, exitCode, signal, killedByIdle, gitStatusAfter }) { + if (killedByIdle) { + return gitStatusAfter ? 'WORKER_STALLED_WITH_DIFF' : 'WORKER_STALLED_NO_DIFF'; + } + if (signal) return `PROCESS_SIGNAL_${signal}`; + if (exitCode !== null && exitCode !== undefined && exitCode !== 0) return 'PROCESS_FAILED'; + if (finalStatus === 'WORKER_DONE') return 'WORKER_DONE_PENDING_CODEX_GATE'; + if (finalStatus === 'BLOCKED') return 'WORKER_BLOCKED'; + if (finalStatus === 'NEEDS_DISCUSSION') return 'WORKER_NEEDS_DISCUSSION'; + if (exitCode === null || exitCode === undefined) return 'RUNNING_AWAITING_STATUS'; + return 'NO_STRUCTURED_STATUS'; +} + +function renderEventForConsole(event) { + if (!event || typeof event !== 'object') return null; + if (event.type === 'message_update') return extractTextDelta(event) || null; + if (event.type === 'tool_execution_start') return `\n[pi] tool start: ${event.toolName || 'unknown'}\n`; + if (event.type === 'tool_execution_end') { + const status = event.isError ? 'error' : 'ok'; + return `\n[pi] tool end: ${event.toolName || 'unknown'} (${status})\n`; + } + if ( + event.type === 'agent_start' || + event.type === 'agent_end' || + event.type === 'turn_start' || + event.type === 'turn_end' || + event.type === 'compaction_start' || + event.type === 'compaction_end' || + event.type === 'auto_retry_start' || + event.type === 'auto_retry_end' || + event.type === 'extension_error' + ) { + return `\n[pi] ${event.type}\n`; + } + return null; +} + +function assertRequiredFiles(cwd, version) { + const required = [ + 'AGENTS.md', + 'development/_reference/ai/autonomous-agent-harness.md', + ]; + + const missing = required.filter((file) => !existsSync(resolve(cwd, file))); + if (missing.length > 0) { + throw new Error(`Missing required orchestration files:\n${missing.map((file) => `- ${file}`).join('\n')}`); + } +} + +function existingContextFiles(cwd, version) { + return [ + 'README.md', + `development/${version}/prd.md`, + `development/${version}/plan.md`, + `development/${version}/execution-log.md`, + 'development/_reference/ai/autonomous-agent-harness.md', + ].filter((file) => existsSync(resolve(cwd, file))); +} + +function buildPrompt({ contextFiles, name, taskText, version }) { + const contextLine = contextFiles.length > 0 + ? `Read these context files when relevant: ${contextFiles.join(', ')}.` + : 'No version planning files were found; use the direct Codex assignment as the scoped contract.'; + + return `You are the Pi worker launched by Codex-Orchestrator for this project. + +Codex remains the orchestrator, verifier, reviewer, and committer. You are only the bounded implementation worker. + +Task label: +${name} + +Worker assignment: +${taskText} + +Repository contract: +- Read AGENTS.md before editing. +- ${contextLine} +- The direct Codex assignment is the binding scope. Do not broaden it into adjacent cleanup, dependent tasks, or a full release pass. +- If the assignment is too large to finish safely, implement a coherent safe subset and report the recommended next worker task. +- Do not edit planning or reference docs unless the assignment explicitly asks. +- Do not create or update execution logs unless the assignment explicitly asks. Durable progress is captured by Codex through reviewed git checkpoint commits. +- Do not commit, push, merge, reset, clean, or rewrite git history. +- Do not deploy, publish, or touch external services unless the assignment explicitly says so. +- Do not run destructive cleanup or wipe commands. +- Do not edit .pi/**, .agents/**, AGENTS.md, or development/_reference/ai/**. +- If unrelated uncommitted changes exist, work around them. Stop with NEEDS_DISCUSSION if they block the task. +- If credentials, external access, clean local stand setup, or human business choice is required, stop with BLOCKED instead of asking a question. +- The ask_question tool is disabled. Do not request interactive confirmation. + +Implementation rules: +- Prefer existing repo patterns and narrow diffs. +- This is a SvelteKit + TypeScript app. Keep UI behavior calm, readable, and consistent with the existing design. +- Keep game data, game state, and UI components separated according to the current structure. +- Keep user-facing copy consistent with the app language and tone already present in the touched files. +- Prefer existing npm scripts from package.json. Do not invent scripts. +- For UI behavior changes, describe the local browser smoke path Codex should verify. + +Worker output contract: +- Run only relevant local checks that exist in package.json / repo docs. +- If UI behavior changed and local browser smoke is required, use only a local dev server. +- Finish with one of: WORKER_DONE, BLOCKED, or NEEDS_DISCUSSION. +- End your final response with a standalone final status line in this exact shape: + WORKER_STATUS: WORKER_DONE + or WORKER_STATUS: BLOCKED + or WORKER_STATUS: NEEDS_DISCUSSION +- List changed files and observed command exit codes. +- If there is meaningful follow-up work, propose the next concrete worker task. +- Do not claim review approval or human acceptance. Codex will verify, review, and commit if appropriate. +`; +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + const cwd = process.cwd(); + const version = requireArg(args, 'version'); + const model = args.model || 'zai/glm-5.2'; + const thinking = args.thinking || 'xhigh'; + const piBin = args.pi || process.env.PI_BIN || 'pi'; + const piMode = args['pi-mode'] || 'json'; + const eventIdleTimeoutMs = parseNonNegativeInt(args['event-idle-timeout-ms'], 600000); + const taskText = [args.task || '', readOptionalFile(args['prompt-file'])] + .filter(Boolean) + .join('\n\nAdditional Codex assignment details:\n') + .trim(); + + if (!taskText) throw new Error(`Missing --task or --prompt-file\n\n${usage()}`); + if (!['json', 'text'].includes(piMode)) { + throw new Error(`Unsupported --pi-mode ${piMode}. Expected json or text.`); + } + + assertRequiredFiles(cwd, version); + const name = args.name || slugify(taskText); + const contextFiles = existingContextFiles(cwd, version); + const runsBaseDir = resolve(cwd, args['runs-dir'] || '.codex/logs/agent-runs'); + + const runSlug = slugify(`${name}`); + const runDir = resolve(runsBaseDir, version, `${timestamp()}-pi-worker-${runSlug}`); + mkdirSync(runDir, { recursive: true }); + + const prompt = buildPrompt({ contextFiles, name, taskText, version }); + const promptPath = join(runDir, 'prompt.txt'); + const stdoutPath = join(runDir, 'stdout.txt'); + const stderrPath = join(runDir, 'stderr.txt'); + const metaPath = join(runDir, 'meta.json'); + const eventsPath = join(runDir, 'events.jsonl'); + const summaryPath = join(runDir, 'summary.json'); + const lastMessagePath = join(runDir, 'last-message.txt'); + + const sessionId = args['session-id'] || `calm-${version.replace(/[^a-zA-Z0-9]+/g, '-')}-${runSlug}`; + const piArgs = [ + ...(piMode === 'json' ? ['--mode', 'json'] : ['-p']), + '--model', + model, + '--thinking', + thinking, + '--approve', + '--exclude-tools', + 'ask_question', + '--session-id', + sessionId, + prompt, + ]; + + writeFileSync(promptPath, prompt); + const startedAt = new Date().toISOString(); + const baseMeta = { + tool: 'pi', + piBin, + args: piArgs.slice(0, -1).concat(['']), + cwd, + version, + name, + model, + thinking, + piMode, + eventIdleTimeoutMs, + sessionId, + runDir, + promptPath, + stdoutPath, + stderrPath, + eventsPath, + summaryPath, + lastMessagePath, + startedAt, + dryRun: Boolean(args['dry-run']), + }; + + writeFileSync(metaPath, `${JSON.stringify(baseMeta, null, 2)}\n`); + writeFileSync(summaryPath, `${JSON.stringify({ + ...baseMeta, + status: 'NOT_STARTED', + finalStatus: null, + orchestratorStatus: 'NOT_STARTED', + eventCounts: {}, + eventCount: 0, + parseErrors: [], + gitStatusAfter: null, + }, null, 2)}\n`); + + console.log(`Run directory: ${runDir}`); + console.log(`Prompt: ${promptPath}`); + console.log(`Events: ${eventsPath}`); + console.log(`Summary: ${summaryPath}`); + console.log(`Command: ${piBin} ${piArgs.slice(0, -1).join(' ')} `); + + if (args['dry-run']) { + console.log('Dry run: Pi was not launched.'); + return; + } + + const stdout = createWriteStream(stdoutPath); + const stderr = createWriteStream(stderrPath); + const events = createWriteStream(eventsPath); + let stdoutRemainder = ''; + let stdoutTail = ''; + let lastAssistantText = ''; + let lastEventAt = null; + let lastEventType = null; + let parentSignal = null; + let killedByIdle = false; + let idleTimer = null; + const parseErrors = []; + const eventCounts = {}; + let eventCount = 0; + + const child = spawn(piBin, piArgs, { + cwd, + env: process.env, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + function writeRunningSummary(extra = {}) { + const gitStatusAfter = extra.gitStatusAfter ?? null; + const finalStatus = findFinalStatus(lastAssistantText); + const orchestratorStatus = summarizeOrchestratorStatus({ + finalStatus, + exitCode: extra.exitCode ?? null, + signal: extra.signal ?? null, + killedByIdle, + gitStatusAfter, + }); + + writeFileSync(summaryPath, `${JSON.stringify({ + ...baseMeta, + status: extra.status || 'RUNNING', + finalStatus, + orchestratorStatus, + eventCounts, + eventCount, + parseErrors, + lastEventAt, + lastEventType, + parentSignal, + killedByIdle, + gitStatusAfter, + stdoutTail, + lastAssistantTextPath: lastMessagePath, + ...extra, + }, null, 2)}\n`); + } + + function resetIdleTimer() { + if (eventIdleTimeoutMs === 0) return; + clearTimeout(idleTimer); + idleTimer = setTimeout(() => { + killedByIdle = true; + console.error(`\nPi worker produced no output for ${eventIdleTimeoutMs}ms; sending SIGTERM.`); + child.kill('SIGTERM'); + setTimeout(() => { + if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL'); + }, 10000).unref(); + }, eventIdleTimeoutMs); + idleTimer.unref(); + } + + function handleEvent(event) { + eventCount += 1; + eventCounts[event.type || 'unknown'] = (eventCounts[event.type || 'unknown'] || 0) + 1; + lastEventAt = new Date().toISOString(); + lastEventType = event.type || 'unknown'; + events.write(`${JSON.stringify(event)}\n`); + + if (event.type === 'message_end') { + const text = textFromMessage(event.message); + if (text) lastAssistantText = text; + } else if (event.type === 'agent_end') { + const text = findLastAssistantTextFromMessages(event.messages); + if (text) lastAssistantText = text; + } + + if (lastAssistantText) writeFileSync(lastMessagePath, `${lastAssistantText}\n`); + + const rendered = renderEventForConsole(event); + if (rendered) process.stdout.write(rendered); + } + + function handleStdoutChunk(chunk) { + const text = chunk.toString('utf8'); + stdoutTail = `${stdoutTail}${text}`.slice(-12000); + stdout.write(chunk); + resetIdleTimer(); + + if (piMode !== 'json') { + process.stdout.write(chunk); + return; + } + + stdoutRemainder += text; + const lines = stdoutRemainder.split('\n'); + stdoutRemainder = lines.pop() || ''; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + try { + handleEvent(JSON.parse(trimmed)); + } catch (error) { + parseErrors.push({ + at: new Date().toISOString(), + error: error.message || String(error), + line: trimmed.slice(0, 500), + }); + process.stdout.write(`${line}\n`); + } + } + + writeRunningSummary(); + } + + function handleParentSignal(signal) { + parentSignal = signal; + console.error(`\nReceived ${signal}; forwarding to Pi worker.`); + child.kill(signal); + } + + process.once('SIGINT', handleParentSignal); + process.once('SIGTERM', handleParentSignal); + + child.on('error', (error) => { + parseErrors.push({ + at: new Date().toISOString(), + error: error.message || String(error), + line: 'spawn', + }); + }); + + child.stdout.on('data', (chunk) => { + handleStdoutChunk(chunk); + }); + + child.stderr.on('data', (chunk) => { + resetIdleTimer(); + process.stderr.write(chunk); + stderr.write(chunk); + }); + + resetIdleTimer(); + writeRunningSummary(); + + const { exitCode, signal } = await new Promise((resolveExit) => { + child.on('close', (code, closeSignal) => resolveExit({ exitCode: code, signal: closeSignal })); + }); + clearTimeout(idleTimer); + + if (piMode === 'json' && stdoutRemainder.trim()) { + try { + handleEvent(JSON.parse(stdoutRemainder.trim())); + } catch (error) { + parseErrors.push({ + at: new Date().toISOString(), + error: error.message || String(error), + line: stdoutRemainder.trim().slice(0, 500), + }); + } + } + + stdout.end(); + stderr.end(); + events.end(); + + const finishedAt = new Date().toISOString(); + const gitStatusAfter = getGitStatus(cwd); + const finalStatus = piMode === 'json' ? findFinalStatus(lastAssistantText) : findFinalStatus(stdoutTail); + const orchestratorStatus = summarizeOrchestratorStatus({ + finalStatus, + exitCode, + signal, + killedByIdle, + gitStatusAfter, + }); + + writeFileSync( + metaPath, + `${JSON.stringify({ ...baseMeta, finishedAt, exitCode, signal }, null, 2)}\n`, + ); + writeFileSync( + summaryPath, + `${JSON.stringify({ + ...baseMeta, + status: 'FINISHED', + finalStatus, + orchestratorStatus, + eventCounts, + eventCount, + parseErrors, + lastEventAt, + lastEventType, + parentSignal, + killedByIdle, + gitStatusAfter, + stdoutTail, + lastAssistantTextPath: lastMessagePath, + finishedAt, + exitCode, + signal, + }, null, 2)}\n`, + ); + + console.log(`\nPi worker finished with ${orchestratorStatus}. See ${runDir}`); + + if (parentSignal === 'SIGINT') process.exit(130); + if (parentSignal === 'SIGTERM') process.exit(143); + if (killedByIdle) process.exit(124); + if (exitCode !== 0 || signal) { + console.error(`Pi worker exited with code ${exitCode ?? 'null'} signal ${signal ?? 'null'}. See ${runDir}`); + process.exit(exitCode || 1); + } + if (!finalStatus) { + console.error(`Pi worker exited without WORKER_STATUS marker. See ${summaryPath}`); + process.exit(3); + } + + console.log(`Pi worker completed. Structured summary: ${summaryPath}`); +} + +main().catch((error) => { + console.error(error.message || error); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..93838db --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2241 @@ +{ + "name": "my-calm-game", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "my-calm-game", + "version": "0.1.0", + "dependencies": { + "@types/canvas-confetti": "^1.9.0", + "canvas-confetti": "^1.9.4" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.3.1", + "@sveltejs/kit": "^2.8.1", + "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@tailwindcss/vite": "^4.0.0", + "svelte": "^5.1.9", + "svelte-check": "^4.0.5", + "tailwindcss": "^4.0.0", + "typescript": "^5.6.3", + "vite": "^5.4.10" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz", + "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.3.1.tgz", + "integrity": "sha512-5Sc7WAxYdL6q9j/+D0jJKjGREGlfIevDyHSQ2eNETHcB1TKlQWHcAo8AS8H1QdjNvSXpvOwNjykDUHPEAyGgdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-meta-resolve": "^4.1.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.68.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.68.0.tgz", + "integrity": "sha512-PdKiWsqinAoubVsSiRgVFkg3MHzGhQPnwQ8VxnGQKpZYijpapZ3UHHBje0GeByt2TvfjHPw+kxV+dNK2RIZg9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.9", + "@types/cookie": "^0.6.0", + "acorn": "^8.16.0", + "cookie": "^0.6.0", + "devalue": "^5.8.1", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3 || ^6.0.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/load-config": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.0.tgz", + "integrity": "sha512-1LgZ/qUqSoq+QorD83lk2hka79Px0wXNW2q5V1nZlxGhQgw1jrsIbVz5YiCeucVLo4XvFLjXukUaQjIiqowkcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.4.tgz", + "integrity": "sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", + "debug": "^4.3.7", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.12", + "vitefu": "^1.0.3" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0-next.96 || ^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz", + "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0", + "svelte": "^5.0.0-next.96 || ^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "tailwindcss": "4.3.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@types/canvas-confetti": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.9.0.tgz", + "integrity": "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==", + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/canvas-confetti": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.4.tgz", + "integrity": "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==", + "license": "ISC", + "funding": { + "type": "donate", + "url": "https://www.paypal.me/kirilvatev" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.12.tgz", + "integrity": "sha512-On0QbLyaiAkVC4eXtgnXK9Kh2opit+3rcUSOc45DqJ2s/X2eXAHsGOKRSJ6IDagQEW5vPyivANfXUiqgXC67Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.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" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.1.tgz", + "integrity": "sha512-vM9SUhjsUYs6UeJUmygc5Ofm5eQGe85riob5ju6XCgFGJI5PLV4nrDAQpQjd+LkFBpAkADn5BQQpZ9EUNkyLuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.56.4", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.4.tgz", + "integrity": "sha512-/d0QHehmRuJW8gVz395MTkPcPozxzdjBMBE8oEYGz8O3b9KTMzzQ9ZHJQLuFKOHOPQbU6kx/X4iid/EBBzH7iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.12", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.1.tgz", + "integrity": "sha512-FGUOmAqxXdN/H9Zm8slrqO7SLtFisXRB7rfOsHNJ3MLTD2po/+Stg8XyErkpumPHbuUiYTcqrEIzxpVWKTLqtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "^0.2.0", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", + "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..aabd01e --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "my-calm-game", + "version": "0.1.0", + "description": "Calm family quiz night — a cozy 'Svoya Igra'-style game for teams with a host", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "agent:loop": "node development/_reference/ai/scripts/run-agent-loop.mjs" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.3.1", + "@sveltejs/kit": "^2.8.1", + "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@tailwindcss/vite": "^4.0.0", + "svelte": "^5.1.9", + "svelte-check": "^4.0.5", + "tailwindcss": "^4.0.0", + "typescript": "^5.6.3", + "vite": "^5.4.10" + }, + "dependencies": { + "@types/canvas-confetti": "^1.9.0", + "canvas-confetti": "^1.9.4" + } +} diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..e3f374d --- /dev/null +++ b/src/app.css @@ -0,0 +1,209 @@ +@import 'tailwindcss'; + +/* ========================================================================= + Уютная ночная тема — тёплый градиент, мягкое свечение, спокойные цвета. + Эти переменные используются во всём приложении, чтобы тему было легко + менять в одном месте. + ========================================================================= */ +@theme { + --color-night-950: #0b0a1f; + --color-night-900: #14112e; + --color-night-800: #1d1840; + --color-night-700: #2a2356; + --color-night-600: #3a2f70; + + --color-ember-50: #fff7ed; + --color-ember-200: #fed7aa; + --color-ember-300: #fdba74; + --color-ember-400: #fb923c; + --color-ember-500: #f97316; + + --color-gold-300: #fcd34d; + --color-gold-400: #fbbf24; + + --color-mint-300: #6ee7b7; + --color-mint-400: #34d399; + + --color-plum-300: #d8b4fe; + --color-plum-400: #c084fc; +} + +:root { + --font-display: 'Quicksand', ui-rounded, 'Segoe UI', system-ui, sans-serif; +} + +html, +body { + min-height: 100%; +} + +body { + font-family: var(--font-display); + color: theme(--color-night-50, #f8f7ff); + background: + radial-gradient(ellipse at top, #2a2356 0%, transparent 55%), + radial-gradient(ellipse at bottom, #3a1f5d 0%, transparent 50%), + linear-gradient(160deg, #0b0a1f 0%, #14112e 50%, #1d1840 100%); + background-attachment: fixed; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +/* Мягкие «звёзды» на фоне для атмосферы */ +body::before { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + background-image: + radial-gradient(1px 1px at 20% 30%, rgba(255, 255, 255, 0.5), transparent), + radial-gradient(1px 1px at 70% 60%, rgba(255, 255, 255, 0.35), transparent), + radial-gradient(1px 1px at 40% 80%, rgba(255, 255, 255, 0.3), transparent), + radial-gradient(1px 1px at 85% 20%, rgba(255, 255, 255, 0.4), transparent), + radial-gradient(1px 1px at 55% 45%, rgba(255, 255, 255, 0.25), transparent); + background-size: 100% 100%; + opacity: 0.6; + z-index: 0; +} + +/* Убираем раздражающую синюю подсветку на мобильных */ +button { + -webkit-tap-highlight-color: transparent; +} + +/* ===================================================================== + Курсор pointer для всех интерактивных элементов — централизованно. + По умолчанию браузеры дают кнопкам курсор `default`, что ощущается + «неживым». Включаем pointer глобально, а отключённым элементам — + `not-allowed`, чтобы было понятно, что клик невозможен. + ===================================================================== */ +button:not(:disabled), +[role='button']:not([aria-disabled='true']), +[role='button']:not(:disabled), +a[href], +summary, +label[for], +select, +[tabindex]:not([tabindex='-1']) { + cursor: pointer; +} + +button:disabled, +[role='button'][aria-disabled='true'], +[aria-disabled='true'] { + cursor: not-allowed; +} + +/* Скроллбар в теме */ +*::-webkit-scrollbar { + width: 10px; + height: 10px; +} +*::-webkit-scrollbar-thumb { + background: rgba(192, 132, 252, 0.3); + border-radius: 999px; +} +*::-webkit-scrollbar-track { + background: transparent; +} + +/* ===================== Анимации ===================== */ + +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes pop-in { + 0% { + opacity: 0; + transform: scale(0.92) translateY(12px); + } + 60% { + opacity: 1; + transform: scale(1.02) translateY(-2px); + } + 100% { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +@keyframes soft-rise { + from { + opacity: 0; + transform: translateY(18px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes reveal-answer { + 0% { + opacity: 0; + transform: translateY(10px) scale(0.98); + filter: blur(6px); + } + 100% { + opacity: 1; + transform: translateY(0) scale(1); + filter: blur(0); + } +} + +@keyframes score-bump { + 0% { + transform: scale(1); + } + 40% { + transform: scale(1.18); + } + 100% { + transform: scale(1); + } +} + +@keyframes glow-pulse { + 0%, + 100% { + box-shadow: 0 0 18px rgba(251, 191, 36, 0.25); + } + 50% { + box-shadow: 0 0 34px rgba(251, 191, 36, 0.5); + } +} + +.animate-fade-in { + animation: fade-in 0.45s ease both; +} +.animate-pop-in { + animation: pop-in 0.4s cubic-bezier(0.2, 0.9, 0.3, 1.3) both; +} +.animate-soft-rise { + animation: soft-rise 0.5s ease both; +} +.animate-reveal-answer { + animation: reveal-answer 0.55s ease both; +} +.animate-score-bump { + animation: score-bump 0.5s ease; +} +.animate-glow-pulse { + animation: glow-pulse 2.8s ease-in-out infinite; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + } +} diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..d89cd00 --- /dev/null +++ b/src/app.html @@ -0,0 +1,14 @@ + + + + + + + + Уютная игра · Семейная викторина + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/lib/components/ConfirmDialog.svelte b/src/lib/components/ConfirmDialog.svelte new file mode 100644 index 0000000..e74b31b --- /dev/null +++ b/src/lib/components/ConfirmDialog.svelte @@ -0,0 +1,67 @@ + + + + +{#if open} + +
+
+

{title}

+

{message}

+ +
+ + +
+
+
+{/if} diff --git a/src/lib/components/GameBoard.svelte b/src/lib/components/GameBoard.svelte new file mode 100644 index 0000000..887b6cb --- /dev/null +++ b/src/lib/components/GameBoard.svelte @@ -0,0 +1,50 @@ + + +
+ {#each set.categories as category, ci (category.id)} +
+ +
+ {rowIcons[ci % rowIcons.length]} +

+ {category.title} +

+
+ + + {#each category.questions as question (question.id)} + {@const ref = `${category.id}:${question.id}`} +
+ onOpenQuestion(ref)} + /> +
+ {/each} +
+ {/each} +
diff --git a/src/lib/components/GameResult.svelte b/src/lib/components/GameResult.svelte new file mode 100644 index 0000000..ce2726a --- /dev/null +++ b/src/lib/components/GameResult.svelte @@ -0,0 +1,77 @@ + + +
+ + +

+ Вечер завершён +

+ + +
+ + Общий счёт команды + +
+ {score.toLocaleString('ru-RU')} +
+ +
+
+ + {playedCount} вопросов сыграно +
+
+
+ + +

+ {finalPhrase} +

+ + +
+ + + +
+ +

Спокойной ночи и до новых игр 🌙

+
diff --git a/src/lib/components/HostControls.svelte b/src/lib/components/HostControls.svelte new file mode 100644 index 0000000..3faf726 --- /dev/null +++ b/src/lib/components/HostControls.svelte @@ -0,0 +1,46 @@ + + +
+ + + + + +
diff --git a/src/lib/components/QuestionCard.svelte b/src/lib/components/QuestionCard.svelte new file mode 100644 index 0000000..61adab7 --- /dev/null +++ b/src/lib/components/QuestionCard.svelte @@ -0,0 +1,44 @@ + + +{#if played} + + +{:else} + +{/if} diff --git a/src/lib/components/QuestionModal.svelte b/src/lib/components/QuestionModal.svelte new file mode 100644 index 0000000..7574a1a --- /dev/null +++ b/src/lib/components/QuestionModal.svelte @@ -0,0 +1,164 @@ + + + + + + diff --git a/src/lib/components/QuestionSetSelector.svelte b/src/lib/components/QuestionSetSelector.svelte new file mode 100644 index 0000000..8a549c0 --- /dev/null +++ b/src/lib/components/QuestionSetSelector.svelte @@ -0,0 +1,77 @@ + + +
+
+
+ 🌙 +
+

+ Уютная викторина +

+

+ Спокойная игра для всей семьи перед сном. Выберите набор вопросов — и устраивайтесь поудобнее на диване. +

+
+ +
+ {#each sets as set, i (set.id)} + + {/each} +
+ +
+ Ведущий управляет игрой · команда отвечает вместе · никто не торопится +
+
diff --git a/src/lib/components/ScorePanel.svelte b/src/lib/components/ScorePanel.svelte new file mode 100644 index 0000000..d737be4 --- /dev/null +++ b/src/lib/components/ScorePanel.svelte @@ -0,0 +1,37 @@ + + +
+ +
+ + Счёт команды + + + {score.toLocaleString('ru-RU')} + +
+
diff --git a/src/lib/confetti.ts b/src/lib/confetti.ts new file mode 100644 index 0000000..6a966da --- /dev/null +++ b/src/lib/confetti.ts @@ -0,0 +1,51 @@ +import confetti from 'canvas-confetti'; + +/** + * Мягкое «спокойное» конфетти за правильный ответ. + * + * Использует каноничную библиотеку `canvas-confetti` (без зависимостей, + * рендер в overlay- поверх всего). Цвета — в тёплой теме приложения. + * + * Намеренно НЕ агрессивно: меньше частиц, мягкий разлёт, без бесконечных + * залпов. Уютное поздравление, а не новогодний салют. + * + * Важно: используем `useWorker: false` (рисуем на обычном DOM-canvas в главном + * потоке). Режим Web Worker по умолчанию использует OffscreenCanvas вне DOM, + * что в некоторых окружениях (headless/автоматизация, отдельные origin-условия) + * рендерит вхолостую. Для редких залпов викторины главный поток более чем + * достаточен и гарантированно работает везде. + */ +export function celebrate(): void { + const colors = ['#fcd34d', '#fbbf24', '#fb923c', '#c084fc', '#6ee7b7']; + const base = { colors, useWorker: false, disableForReducedMotion: true }; + + // Основной «пуфф» из центра-сверху. + confetti({ + ...base, + particleCount: 70, + spread: 70, + startVelocity: 38, + gravity: 0.9, + scalar: 1.05, + ticks: 200, + origin: { y: 0.35 } + }); + + // Два лёгких боковых дополнения с задержкой — мягкое эхо. + setTimeout(() => { + confetti({ + ...base, + particleCount: 28, + angle: 60, + spread: 55, + origin: { x: 0, y: 0.5 } + }); + confetti({ + ...base, + particleCount: 28, + angle: 120, + spread: 55, + origin: { x: 1, y: 0.5 } + }); + }, 180); + } diff --git a/src/lib/question-sets/animals.ts b/src/lib/question-sets/animals.ts new file mode 100644 index 0000000..0d9fdd6 --- /dev/null +++ b/src/lib/question-sets/animals.ts @@ -0,0 +1,148 @@ +import type { QuestionSet } from '$lib/types'; + +/** + * Тестовый набор-заглушка: «Животные». + * + * Это пример структуры данных и НЕ содержит реального контента — + * только простые вопросы-заглушки. Реальные наборы добавляются отдельно + * по той же схеме. В каждой категории ровно 5 вопросов с очками + * 100 / 200 / 300 / 500 / 1000. + */ +export const animalsSet: QuestionSet = { + id: 'animals', + title: 'Животные', + description: 'Знакомые звери и птицы — тёплый разогрев для всей семьи.', + ageRange: '6+ лет', + categories: [ + { + id: 'who', + title: 'Кто это?', + questions: [ + { + id: 'q1', + points: 100, + question: 'Кто говорит «мяу»?', + answer: 'Кошка', + type: 'strict' + }, + { + id: 'q2', + points: 200, + question: 'У какого домашнего животного есть грива?', + answer: 'Лев (или лошадь — грива есть и у жеребца)', + optionalHint: 'Его часто называют царём зверей.', + type: 'strict' + }, + { + id: 'q3', + points: 300, + question: 'Какое животное носит дом на спине?', + answer: 'Улитка (или черепаха)', + type: 'strict' + }, + { + id: 'q4', + points: 500, + question: 'Какое самое крупное наземное животное?', + answer: 'Африканский слон', + optionalHint: 'У него большие уши и хобот.', + type: 'strict' + }, + { + id: 'q5', + points: 1000, + question: 'Какое морское животное — млекопитающее, а не рыба?', + answer: 'Кит или дельфин', + optionalHint: 'Оно дышит воздухом и кормит детёнышей молоком.', + type: 'strict' + } + ] + }, + { + id: 'where', + title: 'Где живут?', + questions: [ + { + id: 'q1', + points: 100, + question: 'Где живёт аквариумная рыбка?', + answer: 'В аквариуме (в воде)', + type: 'strict' + }, + { + id: 'q2', + points: 200, + question: 'Как называется домик для пчёл?', + answer: 'Улей (или пасека)', + type: 'strict' + }, + { + id: 'q3', + points: 300, + question: 'В каком холодном месте живут белые медведи?', + answer: 'В Арктике (на Северном полюсе / во льдах)', + type: 'strict' + }, + { + id: 'q4', + points: 500, + question: 'Где можно встретить стаю диких обезьян?', + answer: 'В тропическом лесу / джунглях', + optionalHint: 'Там тепло, влажно и много деревьев.', + type: 'strict' + }, + { + id: 'q5', + points: 1000, + question: 'В какой природной зоне живут верблюды?', + answer: 'В пустыне', + optionalHint: 'Их называют «кораблями» этих мест.', + type: 'strict' + } + ] + }, + { + id: 'invent', + title: 'Придумай сам', + questions: [ + { + id: 'q1', + points: 100, + question: 'Придумай ласковое прозвище для котёнка.', + answer: 'Любое тёплое имя (например: Пушок, Мурлык)', + type: 'creative' + }, + { + id: 'q2', + points: 200, + question: 'Каким голосом, по-твоему, разговаривает собака?', + answer: 'Любой творческий ответ', + optionalHint: 'Это фантазийный вопрос — поощряй воображение.', + type: 'creative' + }, + { + id: 'q3', + points: 300, + question: 'Придумай, чем хобот слона может быть полезен.', + answer: 'Любая идея (пить, обниматься, душ, рисовать)', + type: 'creative' + }, + { + id: 'q4', + points: 500, + question: 'Опиши воображаемого зверя, который живёт на диване.', + answer: 'Любой фантазийный ответ', + optionalHint: 'Чем нелепее и добрее — тем лучше.', + type: 'creative' + }, + { + id: 'q5', + points: 1000, + question: 'Придумай короткую сказку про дружбу кота и пчелы.', + answer: 'Любой связный рассказ', + type: 'creative' + } + ] + } + ] +}; diff --git a/src/lib/question-sets/cozy.ts b/src/lib/question-sets/cozy.ts new file mode 100644 index 0000000..95e5626 --- /dev/null +++ b/src/lib/question-sets/cozy.ts @@ -0,0 +1,146 @@ +import type { QuestionSet } from '$lib/types'; + +/** + * Тестовый набор-заглушка: «Уютные вечера». + * + * Содержит простые вопросы-заглушки без реального контента — структура + * данных и форматирование. Каждая категория содержит ровно 5 вопросов + * с очками 100 / 200 / 300 / 500 / 1000. + */ +export const cozySet: QuestionSet = { + id: 'cozy-evenings', + title: 'Уютные вечера', + description: 'Спокойные вопросы про дом, сон и вечерние ритуалы.', + ageRange: 'вся семья', + categories: [ + { + id: 'bedtime', + title: 'Перед сном', + questions: [ + { + id: 'q1', + points: 100, + question: 'Что мы надеваем перед тем, как лечь спать?', + answer: 'Пижаму', + type: 'strict' + }, + { + id: 'q2', + points: 200, + question: 'Как называется мягкая подушка-одеяло, в которую закутываются?', + answer: 'Плед', + type: 'strict' + }, + { + id: 'q3', + points: 300, + question: 'Что светит в окно ночью на небе?', + answer: 'Луна (и звёзды)', + type: 'strict' + }, + { + id: 'q4', + points: 500, + question: 'Как называется горячий вечерний напиток из молока?', + answer: 'Какао (или горячий шоколад / тёплое молоко)', + optionalHint: 'Его часто пьют перед сном.', + type: 'strict' + }, + { + id: 'q5', + points: 1000, + question: 'Что нужно сделать с кроватью, чтобы в ней было уютно спать?', + answer: 'Заправить / застелить / взбить подушку', + optionalHint: 'Это вечерний ритуал порядка.', + type: 'strict' + } + ] + }, + { + id: 'home', + title: 'Дом и семья', + questions: [ + { + id: 'q1', + points: 100, + question: 'Кто будит всех по утрам в семье самым первым? (по-твоему)', + answer: 'Любой правдоподобный ответ (мама, папа, кот, будильник)', + type: 'creative' + }, + { + id: 'q2', + points: 200, + question: 'Где семья чаще всего ужинает вместе?', + answer: 'На кухне / за столом', + type: 'strict' + }, + { + id: 'q3', + points: 300, + question: 'Что висит на стене и показывает время в доме?', + answer: 'Часы', + type: 'strict' + }, + { + id: 'q4', + points: 500, + question: 'Как называется лампа на прикроватной тумбочке?', + answer: 'Настольная лампа / ночник', + optionalHint: 'Она даёт мягкий свет вечером.', + type: 'strict' + }, + { + id: 'q5', + points: 1000, + question: 'Какой домашний вечерний ритуал помогает уснуть спокойнее?', + answer: 'Чтение сказки / тёплый напиток / тёплый душ / тишину', + optionalHint: 'Главное — чтобы было тихо и тепло.', + type: 'creative' + } + ] + }, + { + id: 'dreams', + title: 'Фантазии перед сном', + questions: [ + { + id: 'q1', + points: 100, + question: 'Если бы ты мог летать во сне, куда бы ты полетел?', + answer: 'Любой творческий ответ', + type: 'creative' + }, + { + id: 'q2', + points: 200, + question: 'Какой цвет ты бы выбрал для звёзд?', + answer: 'Любой цвет с объяснением', + optionalHint: 'Может быть даже необычный.', + type: 'creative' + }, + { + id: 'q3', + points: 300, + question: 'Придумай имя для доброго ночного облачка.', + answer: 'Любое мягкое имя', + type: 'creative' + }, + { + id: 'q4', + points: 500, + question: 'Опиши самый уютный сон, который ты можешь представить.', + answer: 'Любой тёплый рассказ', + optionalHint: 'Мягкие цвета, тёплое место, добрые герои.', + type: 'creative' + }, + { + id: 'q5', + points: 1000, + question: 'Сочини доброе пожелание перед сном для всей семьи.', + answer: 'Любое искреннее пожелание', + type: 'creative' + } + ] + } + ] +}; diff --git a/src/lib/question-sets/index.ts b/src/lib/question-sets/index.ts new file mode 100644 index 0000000..6339d6c --- /dev/null +++ b/src/lib/question-sets/index.ts @@ -0,0 +1,57 @@ +import type { Category, Question, QuestionRef, QuestionSet } from '$lib/types'; +import { animalsSet } from './animals'; +import { cozySet } from './cozy'; + +/** + * Реестр всех доступных наборов вопросов. + * + * ┌───────────────────────────────────────────────────────────────────────┐ + * │ КАК ДОБАВИТЬ НОВЫЙ НАБОР: │ + * │ 1. Создай файл `src/lib/question-sets/.ts` │ + * │ 2. Экспортируй из него объект типа `QuestionSet`. │ + * │ (id, title, description, ageRange, categories[][]) │ + * │ 3. Импортируй его сюда и добавь в массив `questionSets` ниже. │ + * │ Всё — на экране выбора набор появится автоматически. │ + * └───────────────────────────────────────────────────────────────────────┘ + */ +export const questionSets: QuestionSet[] = [animalsSet, cozySet]; + +/* ----------------------------------------------------------------------- */ +/* Хелперы поиска по данным (без состояния). */ +/* ----------------------------------------------------------------------- */ + +export function getQuestionSetById(id: string): QuestionSet | undefined { + return questionSets.find((set) => set.id === id); +} + +/** Сколько всего вопросов в наборе (для прогресс-бара). */ +export function getTotalQuestions(set: QuestionSet): number { + return set.categories.reduce((sum, cat) => sum + cat.questions.length, 0); +} + +/** Ключ для множества сыгранных вопросов: `categoryId:questionId`. */ +export function makeQuestionRef(categoryId: string, questionId: string): QuestionRef { + return `${categoryId}:${questionId}`; +} + +/** Разбивает ключ обратно на id категории и id вопроса. */ +export function parseQuestionRef(ref: QuestionRef): { categoryId: string; questionId: string } { + const [categoryId, questionId] = ref.split(':'); + return { categoryId, questionId }; +} + +/** + * Находит вопрос по ключу `categoryId:questionId` внутри набора. + * Возвращает категорию, вопрос или undefined, если не найдено. + */ +export function findQuestion( + set: QuestionSet, + ref: QuestionRef +): { category: Category; question: Question } | undefined { + const { categoryId, questionId } = parseQuestionRef(ref); + const category = set.categories.find((c) => c.id === categoryId); + if (!category) return undefined; + const question = category.questions.find((q) => q.id === questionId); + if (!question) return undefined; + return { category, question }; +} diff --git a/src/lib/stores/game.ts b/src/lib/stores/game.ts new file mode 100644 index 0000000..4b7b6ea --- /dev/null +++ b/src/lib/stores/game.ts @@ -0,0 +1,186 @@ +import { writable } from 'svelte/store'; +import { browser } from '$app/environment'; +import type { GameState, QuestionRef } from '$lib/types'; +import { getQuestionSetById, getTotalQuestions } from '$lib/question-sets'; + +/** + * Игровое состояние полностью отделено от компонентов. + * + * Магия Svelte store: один `writable`, за которым следят все экраны. + * Действия ведущего (выбор набора, открытие вопроса, засчёт, новая игра) + * реализованы как функции, мутирующие этот store. При каждом изменении + * состояние автоматически сохраняется в localStorage, а при перезагрузке + * страницы — восстанавливается. + */ + +const STORAGE_KEY = 'my-calm-game:state:v1'; + +/** Начальное (пустое) состояние — экран выбора набора. */ +function initialState(): GameState { + return { + questionSetId: null, + score: 0, + played: [], + current: null + }; +} + +/** Безопасно читаем состояние из localStorage, игнорируя мусор/ошибки. */ +function loadState(): GameState { + if (!browser) return initialState(); + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return initialState(); + const parsed = JSON.parse(raw) as Partial; + // Защита от повреждённых/чужих данных: проверяем базовую структуру. + if ( + parsed && + (parsed.questionSetId === null || typeof parsed.questionSetId === 'string') && + typeof parsed.score === 'number' && + Array.isArray(parsed.played) && + (parsed.current === null || typeof parsed.current === 'string') + ) { + return { + questionSetId: parsed.questionSetId ?? null, + score: parsed.score ?? 0, + played: parsed.played as QuestionRef[], + current: parsed.current ?? null + }; + } + } catch { + // Повреждённый JSON — просто начинаем заново. + } + return initialState(); +} + +/** Сохраняем состояние в localStorage (только в браузере). */ +function saveState(state: GameState): void { + if (!browser) return; + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + } catch { + // localStorage переполнен или недоступен (приватный режим) — + // игра продолжит работать в памяти, просто без сохранения. + } +} + +function createGameStore() { + const { subscribe, update, set } = writable(loadState()); + + // Автосохранение при каждом изменении. + if (browser) { + subscribe((state) => saveState(state)); + } + + return { + subscribe, + + /** Выбрать набор и перейти к табло. */ + selectQuestionSet(id: string) { + update((s) => ({ ...s, questionSetId: id, score: 0, played: [], current: null })); + }, + + /** Открыть вопрос по ключу `categoryId:questionId`. */ + openQuestion(ref: QuestionRef) { + update((s) => ({ ...s, current: ref })); + }, + + /** Закрыть текущий вопрос без решения (вернуться к табло). */ + closeQuestion() { + update((s) => ({ ...s, current: null })); + }, + + /** + * Засчитать ответ: добавить очки и пометить вопрос сыгранным. + * Очки берутся из данных вопроса, а не хардкодятся. + */ + awardQuestion(ref: QuestionRef, points: number) { + update((s) => ({ + ...s, + score: s.score + points, + played: s.played.includes(ref) ? s.played : [...s.played, ref], + current: null + })); + }, + + /** + * Не засчитывать: вопрос всё равно считается сыгранным, но очки не идут. + * Штрафов и минусов нет. + */ + rejectQuestion(ref: QuestionRef) { + update((s) => ({ + ...s, + played: s.played.includes(ref) ? s.played : [...s.played, ref], + current: null + })); + }, + + /** + * Новая игра: сбросить прогресс по ТЕКУЩЕМУ набору (счёт, сыгранные, + * текущий вопрос). Набор остаётся выбранным. Сохранение очищается + * соответствующим образом (через автосохранение). + */ + newGame() { + update((s) => ({ ...s, score: 0, played: [], current: null })); + }, + + /** + * Сменить набор: вернуться к экрану выбора набора. + * Полный сброс партии. + */ + changeSet() { + set(initialState()); + }, + + /** Принудительно заменить состояние (для тестов/отладки). */ + reset() { + set(initialState()); + } + }; +} + +export const game = createGameStore(); + +/* ----------------------------------------------------------------------- */ +/* Производные значения как отдельные store (computed-like). */ +/* ----------------------------------------------------------------------- */ + +import { derived } from 'svelte/store'; + +/** Текущий выбранный набор (объект) или null. */ +export const currentSet = derived(game, ($g) => + $g.questionSetId ? getQuestionSetById($g.questionSetId) ?? null : null +); + +/** Сколько всего вопросов в текущем наборе. */ +export const totalQuestions = derived(currentSet, ($set) => + $set ? getTotalQuestions($set) : 0 +); + +/** Сколько вопросов уже сыграно. */ +export const playedCount = derived(game, ($g) => $g.played.length); + +/** + * Сыграли ли все вопросы набора → пора показывать финал. + * Финал показывается только если набор выбран и сыграны ВСЕ вопросы. + */ +export const isFinished = derived( + [currentSet, totalQuestions, playedCount], + ([$set, $total, $played]) => $set !== null && $total > 0 && $played >= $total +); + +/** + * Глобальное «app screen» — высчитывается из состояния. + * - 'select' — экран выбора набора + * - 'board' — основное табло + * - 'question' — открыт вопрос + * - 'result' — финальный экран + * + * Восстанавливается корректно после перезагрузки. + */ +export const screen = derived([game, currentSet, isFinished], ([$g, $set, $finished]) => { + if (!$set) return 'select' as const; + if ($finished) return 'result' as const; + if ($g.current) return 'question' as const; + return 'board' as const; +}); diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..e648941 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,105 @@ +/** + * Доменные типы игры. + * + * Данные вопросов (`QuestionSet`, `Category`, `Question`) полностью отделены + * от состояния игры и от UI. Реальные наборы добавляются как файлы в + * `src/lib/question-sets/` и регистрируются в `src/lib/question-sets/index.ts`. + */ + +/** + * Тип вопроса. + * - `strict` — есть однозначный правильный ответ (ведущий решает строго). + * - `creative` — творческий вопрос, допускающий разные interpretations. + * UI для таких вопросов использует более мягкие формулировки действий. + */ +export type QuestionType = 'strict' | 'creative'; + +/** + * Стоимость вопроса. Ограничена разрешёнными номиналами «Своей игры». + * Чем выше стоимость — тем сложнее вопрос (сложность заложена в тексте). + */ +export const POINT_VALUES = [100, 200, 300, 500, 1000] as const; +export type PointValue = (typeof POINT_VALUES)[number]; + +/** + * Структура вопроса (исходные данные, без состояния). + * + * `played` намеренно НЕ хранится здесь — это игровое состояние, оно живёт + * отдельно в `GameState` и сохраняется в localStorage. + */ +export interface Question { + /** Стабильный идентификатор, уникальный в пределах набора. */ + id: string; + /** Стоимость вопроса, берётся из данных — НЕ хардкодится в UI. */ + points: PointValue; + /** Текст вопроса. */ + question: string; + /** Правильный ответ. */ + answer: string; + /** Необязательная подсказка, ведущий может её показать по желанию. */ + optionalHint?: string; + /** Тип вопроса влияет на формулировки действий ведущего. */ + type: QuestionType; +} + +/** + * Категория набора — содержит ровно 5 вопросов с очками 100/200/300/500/1000. + * + * `id` нужен для стабильной адресации вопросов (`categoryId:questionId`), + * так как id вопросов уникальны лишь в пределах категории. + */ +export interface Category { + id: string; + title: string; + questions: Question[]; +} + +/** + * Возрастная рекомендация набора. + */ +export type AgeRange = string; + +/** + * Структура набора вопросов (исходные данные). + * + * Чтобы добавить новый набор: + * 1. Создайте файл в `src/lib/question-sets/.ts`. + * 2. Экспортируйте из него объект, удовлетворяющий этому интерфейсу. + * 3. Добавьте его в массив `questionSets` в `src/lib/question-sets/index.ts`. + */ +export interface QuestionSet { + id: string; + title: string; + description: string; + /** Возрастная рекомендация, напр. "6–9 лет" или "вся семья". */ + ageRange: AgeRange; + categories: Category[]; +} + +/* ----------------------------------------------------------------------- */ +/* Игровое состояние (живёт отдельно от данных вопросов) */ +/* ----------------------------------------------------------------------- */ + +/** + * Идентификатор сыгранного/открытого вопроса в виде `:`. + * Используется как ключ в множестве сыгранных вопросов. + */ +export type QuestionRef = string; + +/** + * Полное состояние одной партии. + * Сериализуется в localStorage и восстанавливается после перезагрузки. + */ +export interface GameState { + /** id выбранного набора (или null, если на экране выбора). */ + questionSetId: string | null; + /** Текущий счёт команды (только засчитанные ответы). */ + score: number; + /** Множество сыгранных вопросов в виде `categoryId:questionId`. */ + played: QuestionRef[]; + /** + * Текущий открытый вопрос (`categoryId:questionId`) или null, + * если табло открыто и вопрос не выбран. + */ + current: QuestionRef | null; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..e9ba703 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,9 @@ + + +
+ {@render children()} +
diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 0000000..a17eb38 --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1,5 @@ +// Отключаем SSR: игра полностью клиентская (работа с localStorage). +// Это упрощает работу с браузерным состоянием и avoids hydration mismatches +// при восстановлении сохранённой партии. +export const ssr = false; +export const prerender = false; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..0f42e81 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,195 @@ + + + +{#if $screen === 'select'} + +{:else if $screen === 'result'} + game.newGame()} + onChangeSet={() => game.changeSet()} + /> +{:else if $screen === 'board' || $screen === 'question'} + {@const set = $currentSet} + {#if set} + +
+ +
+
+ 🌙 +
+
+ 📂 Набор вопросов +
+

+ {set.title} +

+
+
+ +
+ + +
+
+ 🎯 Прогресс · {$playedCount} из {$totalQuestions} + {progressPct}% +
+
+
+
+
+ + +
+ +
+ + +
+ +
+
+ {/if} +{/if} + + +{#if $screen === 'question' && currentQuestionData} + +{/if} + + + diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..7f132dc --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..fcf06bd --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,12 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter() + } +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a8f10c8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..bf699a8 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()] +});