diff --git a/apps/client/.env.example b/apps/client/.env.example new file mode 100644 index 0000000..dbba66d --- /dev/null +++ b/apps/client/.env.example @@ -0,0 +1,3 @@ +# URL of the RoastDev server (no trailing slash) +# Copy this file to .env.local for local development +VITE_SERVER_URL=http://localhost:3001 diff --git a/apps/client/.gitignore b/apps/client/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/apps/client/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/client/eslint.config.js b/apps/client/eslint.config.js new file mode 100644 index 0000000..cc2dcd4 --- /dev/null +++ b/apps/client/eslint.config.js @@ -0,0 +1,34 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import prettier from 'eslint-config-prettier'; +import { defineConfig } from 'eslint/config'; + +export default defineConfig([ + js.configs.recommended, + prettier, + { + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + globals: { + ...globals.browser, + }, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +]); diff --git a/apps/client/index.html b/apps/client/index.html new file mode 100644 index 0000000..0d154db --- /dev/null +++ b/apps/client/index.html @@ -0,0 +1,13 @@ + + + + + + + RoastDev + + +
+ + + diff --git a/apps/client/package.json b/apps/client/package.json new file mode 100644 index 0000000..10df900 --- /dev/null +++ b/apps/client/package.json @@ -0,0 +1,32 @@ +{ + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint src", + "format:check": "prettier --check .", + "test": "vitest run" + }, + "devDependencies": { + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@vitejs/plugin-react": "^6.0.1", + "@vitest/coverage-v8": "4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "jsdom": "^29.0.2", + "vite": "^8.0.4", + "vitest": "4" + }, + "dependencies": { + "canvas-confetti": "^1.9.4", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-icons": "^5.6.0", + "socket.io-client": "^4.8.3" + } +} diff --git a/apps/client/public/favicon.svg b/apps/client/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/apps/client/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/client/public/icons.svg b/apps/client/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/apps/client/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/client/src/App.jsx b/apps/client/src/App.jsx new file mode 100644 index 0000000..7538895 --- /dev/null +++ b/apps/client/src/App.jsx @@ -0,0 +1,45 @@ +import { useState } from 'react'; +import Home from './pages/Home'; +import Host from './pages/Host'; +import Participant from './pages/Participant'; + +/** + * App — state-based router. + * + * Three pages, no URL sharing needed, so React Router adds overhead without + * benefit. A `page` string + `sessionData` object is the full routing layer. + * + * sessionData shape: { code: string } + * Set by Home before navigating so Host/Participant receive the session code + * as a prop without any extra fetch. + */ +export default function App() { + const [page, setPage] = useState('home'); // 'home' | 'host' | 'participant' + const [sessionData, setSessionData] = useState(null); + + function goHome() { + setSessionData(null); + setPage('home'); + } + + if (page === 'host') { + return ; + } + + if (page === 'participant') { + return ; + } + + return ( + { + setSessionData(data); + setPage('participant'); + }} + onHost={(data) => { + setSessionData(data); + setPage('host'); + }} + /> + ); +} diff --git a/apps/client/src/components/BadgeLive.jsx b/apps/client/src/components/BadgeLive.jsx new file mode 100644 index 0000000..dfb16d7 --- /dev/null +++ b/apps/client/src/components/BadgeLive.jsx @@ -0,0 +1,8 @@ +export default function BadgeLive() { + return ( +
+
+ live +
+ ); +} diff --git a/apps/client/src/components/ConfirmModal.jsx b/apps/client/src/components/ConfirmModal.jsx new file mode 100644 index 0000000..5e92d9b --- /dev/null +++ b/apps/client/src/components/ConfirmModal.jsx @@ -0,0 +1,59 @@ +import { useEffect } from 'react'; + +/** + * Generic confirmation modal. + * + * Props: + * title — heading text + * message — body text explaining the consequence + * confirmLabel — label for the destructive button (default "Confirm") + * onConfirm — called when the user accepts + * onCancel — called when the user dismisses + * + * Accessibility notes: + * • role="dialog" + aria-modal="true" tells screen readers this is a modal + * • aria-labelledby points to the heading so the dialog has a name + * • Escape key closes without confirming — expected keyboard behaviour + * • Clicking the backdrop also cancels + */ +export default function ConfirmModal({ + title, + message, + confirmLabel = 'Confirm', + onConfirm, + onCancel, +}) { + // Close on Escape + useEffect(() => { + function onKeyDown(e) { + if (e.key === 'Escape') onCancel(); + } + document.addEventListener('keydown', onKeyDown); + return () => document.removeEventListener('keydown', onKeyDown); + }, [onCancel]); + + return ( +
+
e.stopPropagation()} + > + +

{message}

+
+ + +
+
+
+ ); +} diff --git a/apps/client/src/components/Footer.jsx b/apps/client/src/components/Footer.jsx new file mode 100644 index 0000000..31a5e11 --- /dev/null +++ b/apps/client/src/components/Footer.jsx @@ -0,0 +1,32 @@ +import { SiGithub } from 'react-icons/si'; +import { LuGlobe } from 'react-icons/lu'; + +export default function Footer() { + return ( + + ); +} diff --git a/apps/client/src/components/Layout.jsx b/apps/client/src/components/Layout.jsx new file mode 100644 index 0000000..f8a13e0 --- /dev/null +++ b/apps/client/src/components/Layout.jsx @@ -0,0 +1,26 @@ +import NavBar from './NavBar'; +import Footer from './Footer'; + +/** + * Shared page shell: NavBar on top, Footer at the bottom, page content + * in between. min-height: 100dvh on the wrapper ensures the footer is + * always pushed to the bottom even on short pages. + * + * Props: + * code — session code shown in the nav (optional) + * showLive — whether to show the live badge (optional) + * navAction — optional { label, onClick, className } forwarded to NavBar. + * Makes the logo a button and adds a labelled action button. + * children — the page content rendered below the nav + */ +export default function Layout({ code, showLive, navAction, children }) { + return ( +
+ + {children} +
+
+ ); +} diff --git a/apps/client/src/components/NavBar.jsx b/apps/client/src/components/NavBar.jsx new file mode 100644 index 0000000..e168843 --- /dev/null +++ b/apps/client/src/components/NavBar.jsx @@ -0,0 +1,77 @@ +import { useState } from 'react'; +import { LuCopy, LuCheck } from 'react-icons/lu'; +import BadgeLive from './BadgeLive'; + +/** + * Copy-to-clipboard button for the session code. + * Shows a LuCheck for 2 s after a successful copy, then reverts. + * Uses the Clipboard API (available in all modern browsers over HTTPS/localhost). + */ +function CopyCode({ code }) { + const [copied, setCopied] = useState(false); + + async function handleCopy() { + await navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + return ( + + ); +} + +/** + * Top navigation bar shared across all pages. + * Props: + * code — session code; when provided renders a copy button + * showLive — whether to display the live badge + * navAction — optional { label, onClick, className } — makes the logo a + * button and adds a labelled action button in the nav right + */ +export default function NavBar({ code, showLive, navAction }) { + return ( + + ); +} diff --git a/apps/client/src/components/ResultBar.jsx b/apps/client/src/components/ResultBar.jsx new file mode 100644 index 0000000..ae3bf45 --- /dev/null +++ b/apps/client/src/components/ResultBar.jsx @@ -0,0 +1,30 @@ +/** + * One result row: label, percentage text, and animated fill bar. + * + * Props: + * label — answer text + * pct — number 0–100 (computed by the parent from raw vote counts) + * color — CSS variable string for the fill, e.g. 'var(--rd-brand)' + * + * The `width` inline style is the only dynamic value that touches JSX style + * in the whole app — it's a number, not a color, so it cannot live in CSS. + * Every color stays in CSS variables. + */ +export default function ResultBar({ label, pct, color }) { + return ( +
+
+ {label} + + {pct}% + +
+
+
+
+
+ ); +} diff --git a/apps/client/src/hooks/useSession.js b/apps/client/src/hooks/useSession.js new file mode 100644 index 0000000..a3b97db --- /dev/null +++ b/apps/client/src/hooks/useSession.js @@ -0,0 +1,52 @@ +import { useEffect, useState } from 'react'; +import socket from '../socket'; + +/** + * Custom hook — manages socket subscriptions for a session room. + * + * Both Host and Participant need the same three socket events: + * session_joined → exposes the question object + * vote_update → keeps results live + * session_closed → calls onClose() to navigate home + * + * Extracting this into a hook means neither page owns the subscription + * logic: they just call useSession() and receive { question, results }. + * + * Custom hooks are the React pattern for reusing stateful logic across + * components. Unlike a utility function, a hook can call useState and + * useEffect — it participates in the React lifecycle. + * + * Named handler variables are required so socket.off() can remove the + * exact same function reference that was registered with socket.on(). + * Inline arrows would create new references each render, making + * socket.off() a no-op and stacking duplicate handlers over time. + */ +export function useSession(code, onClose) { + const [question, setQuestion] = useState(null); + const [results, setResults] = useState([]); + + useEffect(() => { + function onSessionJoined({ question: q }) { + setQuestion(q); + } + function onVoteUpdate({ results: r }) { + setResults(r); + } + function onSessionClosed() { + onClose(); + } + + socket.on('session_joined', onSessionJoined); + socket.on('vote_update', onVoteUpdate); + socket.on('session_closed', onSessionClosed); + socket.emit('join_session', code); + + return () => { + socket.off('session_joined', onSessionJoined); + socket.off('vote_update', onVoteUpdate); + socket.off('session_closed', onSessionClosed); + }; + }, [code, onClose]); + + return { question, results }; +} diff --git a/apps/client/src/main.jsx b/apps/client/src/main.jsx new file mode 100644 index 0000000..b649438 --- /dev/null +++ b/apps/client/src/main.jsx @@ -0,0 +1,18 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './styles/tokens.css'; +import './styles/components.css'; +import App from './App'; + +/** + * Entry point — mounts the React tree into
in index.html. + * + * StrictMode renders components twice in development to surface side effects + * that depend on render order. It has no effect in production builds. + * It's the reason you may see useEffect run twice in dev — intentional. + */ +createRoot(document.getElementById('root')).render( + + + +); diff --git a/apps/client/src/pages/Home.jsx b/apps/client/src/pages/Home.jsx new file mode 100644 index 0000000..05fac5b --- /dev/null +++ b/apps/client/src/pages/Home.jsx @@ -0,0 +1,144 @@ +import { useState } from 'react'; +import { LuLogIn, LuRadio } from 'react-icons/lu'; +import Layout from '../components/Layout'; +import socket from '../socket'; + +const SERVER_URL = import.meta.env.VITE_SERVER_URL ?? 'http://localhost:3001'; + +export default function Home({ onJoin, onHost }) { + const [code, setCode] = useState(''); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + async function handleJoin(e) { + e.preventDefault(); + const trimmed = code.trim().toUpperCase(); + if (!trimmed) return; + setError(null); + setLoading(true); + try { + const res = await fetch(`${SERVER_URL}/sessions/${trimmed}`); + if (!res.ok) { + const body = await res.json().catch(() => ({})); + setError(body.message ?? 'Session not found.'); + return; + } + const session = await res.json(); + if (session.status === 'closed') { + setError('This session is already closed.'); + return; + } + socket.connect(); + onJoin({ code: trimmed }); + } catch { + setError('Could not reach the server. Is it running?'); + } finally { + setLoading(false); + } + } + + async function handleHost() { + setError(null); + setLoading(true); + try { + const res = await fetch(`${SERVER_URL}/sessions`, { method: 'POST' }); + if (!res.ok) throw new Error('Failed to create session'); + const session = await res.json(); + socket.connect(); + onHost({ code: session.code }); + } catch { + setError('Could not create a session. Is the server running?'); + } finally { + setLoading(false); + } + } + + return ( + +
+
+
+ Roast + Dev +
+
+ where devs get roasted — one hot take at a time +
+
+ +
+
+
+
Session code
+ setCode(e.target.value.toUpperCase())} + maxLength={8} + autoComplete="off" + spellCheck={false} + /> + {/* Always rendered — min-height reserves the space so the + buttons don't jump when the message appears or disappears. */} +

+ {error ?? ''} +

+ +
+ +
+
+
or
+
+
+ + +
+ +

+ No account needed. Just a code. +

+
+
+ + ); +} diff --git a/apps/client/src/pages/Host.jsx b/apps/client/src/pages/Host.jsx new file mode 100644 index 0000000..97bf734 --- /dev/null +++ b/apps/client/src/pages/Host.jsx @@ -0,0 +1,127 @@ +import { useState } from 'react'; +import { LuPower, LuShare2 } from 'react-icons/lu'; +import Layout from '../components/Layout'; +import ConfirmModal from '../components/ConfirmModal'; +import ResultBar from '../components/ResultBar'; +import { useSession } from '../hooks/useSession'; +import { BAR_COLORS, calcPct } from '../utils'; + +const SERVER_URL = import.meta.env.VITE_SERVER_URL ?? 'http://localhost:3001'; + +export default function Host({ code, onClose }) { + const { question, results } = useSession(code, onClose); + const [showConfirm, setShowConfirm] = useState(false); + + async function handleClose() { + await fetch(`${SERVER_URL}/sessions/${code}/close`, { method: 'PATCH' }); + } + + const navAction = { + label: 'Close session', + onClick: () => setShowConfirm(true), + className: 'btn-danger-outline btn-sm', + }; + + return ( + <> + +
+ {!question ? ( +

+ Loading question… +

+ ) : ( +
+ {/* Left column — current question + results */} +
+
+
Current question
+
+ {question.text} +
+ + {question.answers.map((answer, i) => ( + + ))} +
+ +
+ +
+
+ + {/* Right column — hint card */} +
+
+ + Share with participants +
+

+ Share the code{' '} + + {code} + {' '} + with your audience. Results update live as participants vote. + Close the session when you're ready to move on. +

+
+
+ )} +
+
+ + {showConfirm && ( + setShowConfirm(false)} + /> + )} + + ); +} diff --git a/apps/client/src/pages/Participant.jsx b/apps/client/src/pages/Participant.jsx new file mode 100644 index 0000000..08eef87 --- /dev/null +++ b/apps/client/src/pages/Participant.jsx @@ -0,0 +1,184 @@ +import { useState } from 'react'; +import confetti from 'canvas-confetti'; +import { LuClock, LuSend, LuRefreshCw } from 'react-icons/lu'; +import Layout from '../components/Layout'; +import ResultBar from '../components/ResultBar'; +import { useSession } from '../hooks/useSession'; +import { BAR_COLORS, calcPct } from '../utils'; +import socket from '../socket'; + +export default function Participant({ code, onClose }) { + const { question, results } = useSession(code, onClose); + const [selectedId, setSelectedId] = useState(null); + const [phase, setPhase] = useState('voting'); + + function handleSubmit() { + socket.emit('submit_vote', { code, answerId: selectedId }); + confetti({ + particleCount: 80, + spread: 60, + origin: { y: 0.7 }, + colors: ['#E17000', '#F88101', '#F8A800'], + }); + setPhase('waiting'); + } + + function handleChange() { + setPhase('voting'); + } + + const selectedAnswer = question?.answers.find((a) => a.id === selectedId); + + if (!question) { + return ( + +
+

+ Joining session… +

+
+
+ ); + } + + return ( + +
+ {phase === 'voting' ? ( + /* ── Voting phase ── */ +
+
+
+
Question
+
+ {question.text} +
+
+ +
+ {question.answers.map((answer) => ( +
setSelectedId(answer.id)} + > +
+
+
+ {answer.text} +
+ ))} +
+ + +
+ +
+
How it works
+

+ Select your answer and submit. Results appear live once + you've voted — and you can change your mind until the host + closes the question. +

+
+
+ ) : ( + /* ── Waiting phase ── */ +
+
+
+
+
+ +
+ Vote submitted +
+
+ Waiting for others… +
+ + {selectedAnswer && ( +
+
+ + {selectedAnswer.text} + +
+ )} + +
+
+
+
+
+ + +
+
+ +
+
Live results
+ {question.answers.map((answer, i) => ( + + ))} +

+ Updates live as others vote +

+
+
+ )} +
+ + ); +} diff --git a/apps/client/src/socket.js b/apps/client/src/socket.js new file mode 100644 index 0000000..5df2c76 --- /dev/null +++ b/apps/client/src/socket.js @@ -0,0 +1,17 @@ +import { io } from 'socket.io-client'; + +/** + * Singleton Socket.io client. + * + * autoConnect: false — the socket does NOT connect on import. + * We call socket.connect() explicitly after the user enters a valid code, + * so we never open a WebSocket against a session that doesn't exist yet. + * + * One module-level instance is shared across all components: if io() were + * called inside a component, every re-render would create a new connection. + */ +const socket = io(import.meta.env.VITE_SERVER_URL ?? 'http://localhost:3001', { + autoConnect: false, +}); + +export default socket; diff --git a/apps/client/src/styles/components.css b/apps/client/src/styles/components.css new file mode 100644 index 0000000..97f6a81 --- /dev/null +++ b/apps/client/src/styles/components.css @@ -0,0 +1,550 @@ +/* ─── Animations ─────────────────────────────────────────────── */ +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.3; + } +} + +/* ─── Logo ───────────────────────────────────────────────────── */ +.logo { + font-size: 18px; + font-weight: 500; + letter-spacing: -0.3px; +} +.logo-w { + color: var(--rd-text); +} +.logo-o { + color: var(--rd-brand2); +} + +/* Reset button styles when the logo acts as a nav link */ +.logo-btn { + background: none; + border: none; + padding: 0; + cursor: pointer; + font-family: inherit; +} +.logo-btn:focus-visible { + outline: 2px solid var(--rd-brand); + outline-offset: 4px; + border-radius: 4px; +} + +/* ─── Navbar ─────────────────────────────────────────────────── */ +.navbar { + background: var(--rd-surf); + border-bottom: 1px solid var(--rd-border); + padding: 0 var(--sp-xl); + height: 56px; + display: flex; + align-items: center; + justify-content: space-between; +} +.navbar-right { + display: flex; + align-items: center; + gap: 12px; +} +.navbar-code { + font-size: 13px; + font-weight: 500; + color: var(--rd-text); + font-family: var(--font-mono); + letter-spacing: 0.1em; +} + +/* Modifier that adds an icon before the label in any button */ +.btn-icon { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; +} + +/* Copy-code button in the navbar */ +.copy-btn { + display: inline-flex; + align-items: center; + gap: 6px; + background: none; + border: none; + padding: 4px 6px; + border-radius: var(--r-sm); + cursor: pointer; + color: var(--rd-muted); + font-family: inherit; + transition: color 0.15s; +} +.copy-btn:hover { + color: var(--rd-text); +} + +/* ─── Badge Live ─────────────────────────────────────────────── */ +.badge-live { + display: inline-flex; + align-items: center; + gap: 5px; + background: #1c0a00; + border: 1px solid #e1700044; + border-radius: var(--r-full); + padding: 4px 10px; + font-size: 11px; + font-weight: 500; + color: var(--rd-brand2); +} +.live-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--rd-brand); + animation: pulse 2s infinite; + flex-shrink: 0; +} + +/* ─── Buttons ────────────────────────────────────────────────── */ +.btn-primary { + background: var(--rd-brand); + color: var(--rd-bg); + border: none; + border-radius: var(--r-md); + padding: 12px 28px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + font-family: inherit; + transition: background 0.15s; +} +.btn-primary:hover { + background: var(--rd-brand2); +} +.btn-primary:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.btn-ghost { + background: transparent; + color: var(--rd-text); + border: 1px solid var(--rd-border); + border-radius: var(--r-md); + padding: 12px 28px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s; +} +.btn-ghost:hover { + border-color: var(--rd-muted); +} + +.btn-sm { + padding: 8px 16px; + font-size: 12px; + border-radius: var(--r-sm); +} + +.btn-danger-outline { + background: transparent; + color: var(--rd-brand); + border: 1px solid #e1700044; + border-radius: var(--r-sm); + padding: 8px 16px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s; +} +.btn-danger-outline:hover { + border-color: var(--rd-brand); +} + +.btn-change { + background: transparent; + color: var(--rd-brand); + border: 1px solid #e1700044; + border-radius: var(--r-sm); + padding: 9px 18px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s; +} +.btn-change:hover { + border-color: var(--rd-brand); +} + +/* ─── Cards ──────────────────────────────────────────────────── */ +.card { + background: var(--rd-surf); + border: 1px solid var(--rd-border); + border-radius: var(--r-lg); + padding: var(--sp-lg); +} + +.card-sm { + background: var(--rd-surf2); + border: 1px solid var(--rd-border); + border-radius: var(--r-md); + padding: var(--sp-md); +} + +/* ─── Inputs ─────────────────────────────────────────────────── */ +.input-label { + font-size: 11px; + color: var(--rd-muted); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: var(--sp-sm); +} + +.code-input { + background: var(--rd-bg); + border: 1.5px solid var(--rd-brand); + border-radius: var(--r-md); + padding: 14px var(--sp-md); + font-size: 22px; + font-weight: 500; + color: var(--rd-text); + letter-spacing: 0.3em; + width: 100%; + font-family: var(--font-mono); + text-align: center; + outline: none; +} +.code-input::placeholder { + color: #2a2a2a; + letter-spacing: 0.2em; + font-size: 18px; +} + +/* ─── Divider ────────────────────────────────────────────────── */ +.or-divider { + display: flex; + align-items: center; + gap: 12px; + margin: var(--sp-md) 0; +} +.or-line { + flex: 1; + height: 1px; + background: var(--rd-border); +} +.or-text { + font-size: 12px; + color: var(--rd-muted); +} + +/* ─── Answer Options ─────────────────────────────────────────── */ +.answers-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +.answer-opt { + background: var(--rd-surf2); + border: 1px solid var(--rd-border); + border-radius: var(--r-md); + padding: 14px var(--sp-md); + display: flex; + align-items: center; + gap: 12px; + cursor: pointer; + transition: border-color 0.12s; + user-select: none; +} +.answer-opt:hover { + border-color: var(--rd-muted); +} +.answer-opt:active { + transform: scale(0.97); +} +.answer-opt.selected { + border-color: var(--rd-brand); + background: var(--rd-sel); +} + +.answer-opt-text { + font-size: 14px; + color: var(--rd-muted); +} +.answer-opt.selected .answer-opt-text { + color: var(--rd-text); +} + +.radio { + width: 16px; + height: 16px; + border-radius: 50%; + border: 1.5px solid var(--rd-border); + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.12s; +} +.answer-opt.selected .radio { + border-color: var(--rd-brand); + background: var(--rd-brand); +} +.radio-inner { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--rd-bg); + display: none; +} +.answer-opt.selected .radio-inner { + display: block; +} + +/* ─── Result Bars ────────────────────────────────────────────── */ +.result-row { + margin-bottom: 14px; +} +.result-row:last-child { + margin-bottom: 0; +} +.result-meta { + display: flex; + justify-content: space-between; + margin-bottom: 6px; +} +.result-label { + font-size: 13px; + color: var(--rd-muted); +} +.result-pct { + font-size: 13px; + font-weight: 500; +} +.result-track { + background: var(--rd-bg); + border-radius: 4px; + height: 8px; + overflow: hidden; + border: 1px solid var(--rd-border); +} +.result-fill { + height: 8px; + border-radius: 4px; + /* Animated by width change driven by the pct prop */ + transition: width 0.4s ease; +} + +/* ─── Section label ──────────────────────────────────────────── */ +.section-label { + font-size: 10px; + color: var(--rd-accent); + text-transform: uppercase; + letter-spacing: 0.1em; + font-weight: 500; + margin-bottom: var(--sp-sm); +} + +/* ─── Action row ─────────────────────────────────────────────── */ +.action-row { + display: flex; + gap: 10px; +} + +/* ─── Waiting card ───────────────────────────────────────────── */ +.waiting-card { + background: var(--rd-surf); + border: 1px solid var(--rd-border); + border-radius: var(--r-lg); + padding: 32px var(--sp-lg); + text-align: center; +} +.waiting-icon { + width: 48px; + height: 48px; + border-radius: 50%; + background: #1c0e00; + border: 1.5px solid #e1700044; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto var(--sp-md); +} +.waiting-dot-row { + display: flex; + gap: 6px; + justify-content: center; + margin: var(--sp-md) 0 var(--sp-lg); +} +.wdot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--rd-border); +} +.wdot:nth-child(1) { + background: var(--rd-brand); + animation: pulse 1.5s infinite; +} +.wdot:nth-child(2) { + background: var(--rd-brand); + animation: pulse 1.5s 0.2s infinite; +} +.wdot:nth-child(3) { + background: var(--rd-brand); + animation: pulse 1.5s 0.4s infinite; +} +.voted-pill { + display: inline-flex; + align-items: center; + gap: var(--sp-sm); + background: var(--rd-sel); + border: 1px solid var(--rd-brand); + border-radius: var(--r-sm); + padding: var(--sp-sm) 14px; + margin: 12px 0 20px; +} +.voted-pill-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--rd-brand); + flex-shrink: 0; +} + +/* ─── Confirm modal ──────────────────────────────────────────── */ +.modal-backdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + padding: var(--sp-md); +} +.modal-box { + background: var(--rd-surf); + border: 1px solid var(--rd-border); + border-radius: var(--r-lg); + padding: var(--sp-lg); + width: 100%; + max-width: 400px; +} +.modal-title { + font-size: 16px; + font-weight: 500; + color: var(--rd-text); + margin-bottom: var(--sp-sm); +} +.modal-message { + font-size: 13px; + color: var(--rd-muted); + line-height: 1.6; + margin-bottom: var(--sp-lg); +} +.modal-actions { + display: flex; + justify-content: flex-end; + gap: var(--sp-sm); +} + +/* ─── Footer ─────────────────────────────────────────────────── */ +.footer { + display: flex; + align-items: center; + justify-content: center; + gap: 20px; + padding: var(--sp-lg) var(--sp-xl); + margin-top: auto; + border-top: 1px solid var(--rd-border); + font-size: 12px; + color: var(--rd-muted); +} +.footer-link { + color: var(--rd-muted); + text-decoration: none; + transition: color 0.15s; +} +.footer-link:hover { + color: var(--rd-text); +} +.footer-icon-link { + display: inline-flex; + align-items: center; + gap: 6px; +} +.footer-sep { + width: 1px; + height: 12px; + background: var(--rd-border); + display: inline-block; +} + +/* ─── Home page ──────────────────────────────────────────────── */ +.home-wrap { + padding: var(--sp-lg) var(--sp-md); + max-width: 480px; + margin: 0 auto; +} +.home-hero { + text-align: center; + padding: 48px 0 32px; +} +.hero-title { + font-size: 52px; + font-weight: 500; + letter-spacing: -1px; + line-height: 1; +} +.home-card { + max-width: 440px; + margin: 0 auto; +} + +/* ─── Page content wrapper ───────────────────────────────────── */ +.page-content { + padding: var(--sp-2xl) var(--sp-xl); +} + +/* ─── Host layout ────────────────────────────────────────────── */ +.host-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--sp-lg); +} + +/* ─── Participant layout ─────────────────────────────────────── */ +.participant-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--sp-lg); + align-items: start; +} + +/* ─── Responsive (mobile-first breakpoint at 700px) ──────────── */ +@media (max-width: 700px) { + .navbar { + padding: 0 var(--sp-md); + } + .page-content { + padding: var(--sp-lg) var(--sp-md); + } + .home-hero { + padding: 32px 0 24px; + } + .hero-title { + font-size: 36px; + } + .host-grid, + .participant-grid, + .answers-grid { + grid-template-columns: 1fr; + } +} diff --git a/apps/client/src/styles/tokens.css b/apps/client/src/styles/tokens.css new file mode 100644 index 0000000..8a82d95 --- /dev/null +++ b/apps/client/src/styles/tokens.css @@ -0,0 +1,54 @@ +/* Clown Fish palette — extracted from the RoastDev reference design */ +:root { + /* Brand */ + --rd-brand: #e17000; + --rd-brand2: #f88101; + --rd-accent: #f8a800; + + /* Surfaces */ + --rd-bg: #0c0c0c; + --rd-surf: #161616; + --rd-surf2: #1e1e1e; + + /* Borders & text */ + --rd-border: #2a2a2a; + --rd-text: #fcfcfc; + --rd-muted: #888; + + /* Selected answer background */ + --rd-sel: #1c0e00; + + /* Spacing */ + --sp-xs: 4px; + --sp-sm: 8px; + --sp-md: 16px; + --sp-lg: 24px; + --sp-xl: 32px; + --sp-2xl: 40px; + + /* Border radius */ + --r-sm: 8px; + --r-md: 10px; + --r-lg: 14px; + --r-full: 9999px; + + /* Typography */ + --font-base: + -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif; + --font-mono: 'Courier New', monospace; +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: var(--font-base); + background: var(--rd-bg); + color: var(--rd-text); + min-height: 100vh; +} diff --git a/apps/client/src/tests/Home.test.jsx b/apps/client/src/tests/Home.test.jsx new file mode 100644 index 0000000..a77a4bd --- /dev/null +++ b/apps/client/src/tests/Home.test.jsx @@ -0,0 +1,118 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import Home from '../pages/Home.jsx'; + +// Prevent a real WebSocket from opening — socket is only called on submit +vi.mock('../socket.js', () => ({ + default: { on: vi.fn(), off: vi.fn(), emit: vi.fn(), connect: vi.fn() }, +})); + +const mockSession = { code: 'XK92PL', status: 'open' }; + +describe('Home', () => { + beforeEach(() => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue({ + ok: true, + json: async () => mockSession, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('renders the code input and both action buttons', () => { + render(); + + expect(screen.getByPlaceholderText('_ _ _ _ _ _')).toBeTruthy(); + expect(screen.getByRole('button', { name: /join roast/i })).toBeTruthy(); + expect( + screen.getByRole('button', { name: /host a session/i }) + ).toBeTruthy(); + }); + + it('coerces typed input to uppercase', () => { + render(); + + const input = screen.getByPlaceholderText('_ _ _ _ _ _'); + fireEvent.change(input, { target: { value: 'abc123' } }); + + expect(input.value).toBe('ABC123'); + }); + + it('error slot is always rendered (no layout jump)', () => { + render(); + + // The

must exist in the DOM even with no error so layout is stable + const errorSlot = screen.getByText('', { selector: 'p' }); + expect(errorSlot).toBeTruthy(); + expect(errorSlot.style.visibility).toBe('hidden'); + }); + + it('shows an error when the session is closed', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue({ + ok: true, + json: async () => ({ code: 'XK92PL', status: 'closed' }), + }); + + render(); + + fireEvent.change(screen.getByPlaceholderText('_ _ _ _ _ _'), { + target: { value: 'XK92PL' }, + }); + fireEvent.click(screen.getByRole('button', { name: /join roast/i })); + + await waitFor(() => { + const errorSlot = screen.getByText(/closed/i); + expect(errorSlot.style.visibility).toBe('visible'); + }); + }); + + it('shows an error when the session is not found (404)', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue({ + ok: false, + json: async () => ({ message: 'Session not found' }), + }); + + render(); + + fireEvent.change(screen.getByPlaceholderText('_ _ _ _ _ _'), { + target: { value: 'ZZZZZZ' }, + }); + fireEvent.click(screen.getByRole('button', { name: /join roast/i })); + + await waitFor(() => { + expect(screen.getByText(/session not found/i)).toBeTruthy(); + }); + }); + + it('calls onJoin with the code when join succeeds', async () => { + const onJoin = vi.fn(); + render(); + + fireEvent.change(screen.getByPlaceholderText('_ _ _ _ _ _'), { + target: { value: 'XK92PL' }, + }); + fireEvent.click(screen.getByRole('button', { name: /join roast/i })); + + await waitFor(() => { + expect(onJoin).toHaveBeenCalledWith({ code: 'XK92PL' }); + }); + }); + + it('calls onHost when host a session succeeds', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue({ + ok: true, + json: async () => ({ code: 'NEWCOD', status: 'open' }), + }); + + const onHost = vi.fn(); + render(); + + fireEvent.click(screen.getByRole('button', { name: /host a session/i })); + + await waitFor(() => { + expect(onHost).toHaveBeenCalledWith({ code: 'NEWCOD' }); + }); + }); +}); diff --git a/apps/client/src/tests/Host.test.jsx b/apps/client/src/tests/Host.test.jsx new file mode 100644 index 0000000..2bc1774 --- /dev/null +++ b/apps/client/src/tests/Host.test.jsx @@ -0,0 +1,101 @@ +import { render, screen, fireEvent, act } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import socket from '../socket.js'; +import Host from '../pages/Host.jsx'; +import { getSocketHandler, mockQuestion } from './helpers.js'; + +// vi.mock must live in this file — Vitest hoists it at parse time. +vi.mock('../socket.js', () => ({ + default: { on: vi.fn(), off: vi.fn(), emit: vi.fn(), connect: vi.fn() }, +})); + +vi.spyOn(globalThis, 'fetch').mockResolvedValue({ + ok: true, + json: async () => ({}), +}); + +describe('Host', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('shows a loading state before session_joined fires', () => { + render(); + expect(screen.getByText('Loading question…')).toBeTruthy(); + }); + + it('displays the question text after session_joined', () => { + render(); + + act(() => getSocketHandler('session_joined')({ question: mockQuestion })); + + expect(screen.getByText(mockQuestion.text)).toBeTruthy(); + }); + + it('renders a result bar for each answer', () => { + render(); + + act(() => getSocketHandler('session_joined')({ question: mockQuestion })); + + mockQuestion.answers.forEach(({ text }) => { + expect(screen.getByText(text)).toBeTruthy(); + }); + }); + + it('updates percentages when vote_update fires', () => { + render(); + + act(() => getSocketHandler('session_joined')({ question: mockQuestion })); + act(() => + getSocketHandler('vote_update')({ + results: [ + { answerId: 'a1', count: 3 }, + { answerId: 'a2', count: 1 }, + ], + }) + ); + + expect(screen.getByText('75%')).toBeTruthy(); + expect(screen.getByText('25%')).toBeTruthy(); + }); + + it('opens the confirm modal when Close session is clicked', () => { + render(); + + act(() => getSocketHandler('session_joined')({ question: mockQuestion })); + + fireEvent.click( + screen.getAllByRole('button', { name: /close session/i })[0] + ); + + expect(screen.getByRole('dialog')).toBeTruthy(); + expect(screen.getByText('Close this session?')).toBeTruthy(); + }); + + it('dismisses the modal when Cancel is clicked', () => { + render(); + + act(() => getSocketHandler('session_joined')({ question: mockQuestion })); + + fireEvent.click( + screen.getAllByRole('button', { name: /close session/i })[0] + ); + fireEvent.click(screen.getByRole('button', { name: /cancel/i })); + + expect(screen.queryByRole('dialog')).toBeNull(); + }); + + it('calls onClose when session_closed fires', () => { + const onClose = vi.fn(); + render(); + + act(() => getSocketHandler('session_closed')()); + + expect(onClose).toHaveBeenCalledOnce(); + }); + + it('emits join_session with the code on mount', () => { + render(); + expect(socket.emit).toHaveBeenCalledWith('join_session', 'XK92PL'); + }); +}); diff --git a/apps/client/src/tests/Participant.test.jsx b/apps/client/src/tests/Participant.test.jsx new file mode 100644 index 0000000..087f0b5 --- /dev/null +++ b/apps/client/src/tests/Participant.test.jsx @@ -0,0 +1,159 @@ +import { render, screen, fireEvent, act } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import socket from '../socket.js'; +import Participant from '../pages/Participant.jsx'; +import { getSocketHandler, mockQuestion } from './helpers.js'; + +// vi.mock must live in this file — Vitest hoists it at parse time. +vi.mock('../socket.js', () => ({ + default: { on: vi.fn(), off: vi.fn(), emit: vi.fn(), connect: vi.fn() }, +})); + +// canvas-confetti calls the Canvas API which is not available in jsdom +vi.mock('canvas-confetti', () => ({ default: vi.fn() })); + +/** Renders Participant and fires session_joined so the question is set. */ +function renderWithQuestion(props = {}) { + const utils = render( + + ); + act(() => getSocketHandler('session_joined')({ question: mockQuestion })); + return utils; +} + +describe('Participant', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('shows a loading state before session_joined fires', () => { + render(); + expect(screen.getByText('Joining session…')).toBeTruthy(); + }); + + it('renders all answer options in the voting phase', () => { + renderWithQuestion(); + + mockQuestion.answers.forEach(({ text }) => { + expect(screen.getByText(text)).toBeTruthy(); + }); + }); + + it('submit button is disabled before any answer is selected', () => { + renderWithQuestion(); + + const submitBtn = screen.getByRole('button', { name: /submit vote/i }); + expect(submitBtn.disabled).toBe(true); + }); + + it('selecting an answer adds the selected class and enables submit', () => { + renderWithQuestion(); + + const option = screen + .getByText(mockQuestion.answers[0].text) + .closest('.answer-opt'); + fireEvent.click(option); + + expect(option.classList.contains('selected')).toBe(true); + expect(screen.getByRole('button', { name: /submit vote/i }).disabled).toBe( + false + ); + }); + + it('submitting a vote emits submit_vote and transitions to waiting phase', () => { + renderWithQuestion(); + + fireEvent.click( + screen.getByText(mockQuestion.answers[1].text).closest('.answer-opt') + ); + fireEvent.click(screen.getByRole('button', { name: /submit vote/i })); + + expect(socket.emit).toHaveBeenCalledWith('submit_vote', { + code: 'XK92PL', + answerId: 'a2', + }); + expect(screen.getByText('Vote submitted')).toBeTruthy(); + expect(screen.getByText('Waiting for others…')).toBeTruthy(); + }); + + it('waiting phase shows the voted answer in the pill', () => { + renderWithQuestion(); + + fireEvent.click( + screen.getByText(mockQuestion.answers[0].text).closest('.answer-opt') + ); + fireEvent.click(screen.getByRole('button', { name: /submit vote/i })); + + // Text appears in both the voted-pill and the result bar label + expect( + screen.getAllByText(mockQuestion.answers[0].text).length + ).toBeGreaterThan(0); + }); + + it('waiting phase shows live results updated by vote_update', () => { + renderWithQuestion(); + + fireEvent.click( + screen.getByText(mockQuestion.answers[0].text).closest('.answer-opt') + ); + fireEvent.click(screen.getByRole('button', { name: /submit vote/i })); + + act(() => + getSocketHandler('vote_update')({ + results: [ + { answerId: 'a1', count: 2 }, + { answerId: 'a2', count: 2 }, + ], + }) + ); + + // 2/4 = 50% for each + const fifties = screen.getAllByText('50%'); + expect(fifties.length).toBe(2); + }); + + it('Change my answer returns to the voting phase', () => { + renderWithQuestion(); + + fireEvent.click( + screen.getByText(mockQuestion.answers[0].text).closest('.answer-opt') + ); + fireEvent.click(screen.getByRole('button', { name: /submit vote/i })); + fireEvent.click(screen.getByRole('button', { name: /change my answer/i })); + + // Back to voting phase — submit button should be visible again + expect(screen.getByRole('button', { name: /submit vote/i })).toBeTruthy(); + }); + + it('previously selected answer stays highlighted after changing mind', () => { + renderWithQuestion(); + + fireEvent.click( + screen.getByText(mockQuestion.answers[0].text).closest('.answer-opt') + ); + fireEvent.click(screen.getByRole('button', { name: /submit vote/i })); + fireEvent.click(screen.getByRole('button', { name: /change my answer/i })); + + const option = screen + .getByText(mockQuestion.answers[0].text) + .closest('.answer-opt'); + expect(option.classList.contains('selected')).toBe(true); + }); + + it('calls onClose when session_closed event fires', () => { + const onClose = vi.fn(); + render(); + + act(() => getSocketHandler('session_closed')()); + + expect(onClose).toHaveBeenCalledOnce(); + }); + + it('cleans up socket listeners on unmount', () => { + const { unmount } = render(); + unmount(); + + // off() should have been called for each of the 3 registered events + expect(socket.off).toHaveBeenCalledTimes(3); + }); +}); diff --git a/apps/client/src/tests/helpers.js b/apps/client/src/tests/helpers.js new file mode 100644 index 0000000..1e8d59a --- /dev/null +++ b/apps/client/src/tests/helpers.js @@ -0,0 +1,36 @@ +/** + * Shared test helpers. + * + * NOTE — vi.mock() calls cannot be extracted here. + * Vitest hoists vi.mock() to the top of each file at parse time, so the + * factory must be declared in the test file itself. Import this module + * after your vi.mock() declarations. + */ +import socket from '../socket.js'; + +/** + * Returns the handler function registered for a socket event. + * + * useSession calls socket.on(event, fn) inside useEffect. After a render, + * this helper finds the exact fn so tests can trigger events manually: + * + * act(() => getSocketHandler('session_joined')({ question: mockQuestion })); + */ +export function getSocketHandler(event) { + return socket.on.mock.calls.find(([e]) => e === event)?.[1]; +} + +/** + * Shared question fixture — used by Host and Participant tests. + * Matches the shape emitted by the server in session_joined. + */ +export const mockQuestion = { + id: 'q1', + text: 'Deploy en prod le vendredi 17h ?', + answers: [ + { id: 'a1', text: 'Jamais de la vie' }, + { id: 'a2', text: "Yolo c'est vendredi" }, + { id: 'a3', text: 'Uniquement hotfix' }, + { id: 'a4', text: 'Je suis le seul dev' }, + ], +}; diff --git a/apps/client/src/utils.js b/apps/client/src/utils.js new file mode 100644 index 0000000..1ef7559 --- /dev/null +++ b/apps/client/src/utils.js @@ -0,0 +1,24 @@ +/** + * Bar fill colors indexed by answer position. + * Leading answer gets brand orange, second gets gold, rest fall to grey tones. + */ +export const BAR_COLORS = [ + 'var(--rd-brand)', + 'var(--rd-accent)', + 'var(--rd-muted)', + '#444', +]; + +/** + * Compute the vote percentage for one answer from raw server results. + * + * The server emits { answerId, count }[] — no percentages. + * The client derives them so the display layer stays decoupled from + * whatever aggregation the server chooses to send. + */ +export function calcPct(results, answerId) { + const total = results.reduce((sum, r) => sum + r.count, 0); + if (total === 0) return 0; + const found = results.find((r) => r.answerId === answerId); + return Math.round(((found?.count ?? 0) / total) * 100); +} diff --git a/apps/client/vite.config.js b/apps/client/vite.config.js new file mode 100644 index 0000000..f02a064 --- /dev/null +++ b/apps/client/vite.config.js @@ -0,0 +1,11 @@ +/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + globals: true, + }, +}); diff --git a/apps/server/package.json b/apps/server/package.json index 7bdabfa..fa02d9b 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -12,6 +12,7 @@ "format:check": "prettier --check ." }, "dependencies": { + "cors": "^2.8.6", "dotenv": "^16.4.5", "express": "^4.19.2", "mongoose": "^8.4.1", diff --git a/apps/server/src/app.js b/apps/server/src/app.js index 99ef021..90f89f9 100644 --- a/apps/server/src/app.js +++ b/apps/server/src/app.js @@ -1,6 +1,7 @@ import express from 'express'; import { createServer } from 'http'; import { Server } from 'socket.io'; +import cors from 'cors'; import { createSessionsRouter } from './routes/sessions.js'; import { initSocket } from './socket/index.js'; @@ -10,6 +11,11 @@ const io = new Server(server, { cors: { origin: '*' }, }); +// cors() adds Access-Control-Allow-Origin headers to every Express response. +// Without this, browser fetch() calls from localhost:5173 are blocked by the +// same-origin policy even though the Socket.io WS upgrade is already allowed. +// The Socket.io cors option only covers the WebSocket handshake, not REST. +app.use(cors()); app.use(express.json()); app.use('/sessions', createSessionsRouter(io)); diff --git a/package.json b/package.json index c6d5fe0..538f278 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { + "dev": "pnpm -r --parallel dev", "lint": "pnpm -r lint", "format": "prettier --write .", "format:check": "prettier --check .", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f476d64..37a18ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,57 @@ importers: specifier: ^3.8.2 version: 3.8.2 + apps/client: + dependencies: + canvas-confetti: + specifier: ^1.9.4 + version: 1.9.4 + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + react-icons: + specifier: ^5.6.0 + version: 5.6.0(react@19.2.5) + socket.io-client: + specifier: ^4.8.3 + version: 4.8.3 + devDependencies: + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.8(@types/node@25.5.2)) + '@vitest/coverage-v8': + specifier: '4' + version: 4.1.4(vitest@4.1.4) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@10.2.0) + eslint-plugin-react-refresh: + specifier: ^0.5.2 + version: 0.5.2(eslint@10.2.0) + jsdom: + specifier: ^29.0.2 + version: 29.0.2(@noble/hashes@1.8.0) + vite: + specifier: ^8.0.4 + version: 8.0.8(@types/node@25.5.2) + vitest: + specifier: '4' + version: 4.1.4(@types/node@25.5.2)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2(@noble/hashes@1.8.0))(vite@8.0.8(@types/node@25.5.2)) + apps/server: dependencies: + cors: + specifier: ^2.8.6 + version: 2.8.6 dotenv: specifier: ^16.4.5 version: 16.6.1 @@ -50,10 +99,145 @@ importers: version: 7.2.2 vitest: specifier: ^1.6.0 - version: 1.6.1(@types/node@25.5.2) + version: 1.6.1(@types/node@25.5.2)(jsdom@29.0.2(@noble/hashes@1.8.0))(lightningcss@1.32.0) packages: + '@asamuzakjp/css-color@5.1.10': + resolution: {integrity: sha512-02OhhkKtgNRuicQ/nF3TRnGsxL9wp0r3Y7VlKWyOHHGmGyvXv03y+PnymU8FKFJMTjIr1Bk8U2g1HWSLrpAHww==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.0.9': + resolution: {integrity: sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.1.1': + resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.0.2': + resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.2': + resolution: {integrity: sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -231,6 +415,15 @@ packages: resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@exodus/bytes@1.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -251,19 +444,142 @@ packages: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mongodb-js/saslprep@1.4.6': resolution: {integrity: sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==} + '@napi-rs/wasm-runtime@1.1.3': + resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} + '@oxc-project/types@0.124.0': + resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + '@rolldown/binding-android-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.15': + resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} + + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + '@rollup/rollup-android-arm-eabi@4.60.1': resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} cpu: [arm] @@ -408,9 +724,49 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} @@ -432,21 +788,72 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@vitest/coverage-v8@4.1.4': + resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==} + peerDependencies: + '@vitest/browser': 4.1.4 + vitest: 4.1.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@1.6.1': resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} + '@vitest/expect@4.1.4': + resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} + + '@vitest/mocker@4.1.4': + resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.4': + resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} + '@vitest/runner@1.6.1': resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} + '@vitest/runner@4.1.4': + resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} + '@vitest/snapshot@1.6.1': resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} + '@vitest/snapshot@4.1.4': + resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} + '@vitest/spy@1.6.1': resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} + '@vitest/spy@4.1.4': + resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} + '@vitest/utils@1.6.1': resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + '@vitest/utils@4.1.4': + resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -468,6 +875,10 @@ packages: ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} @@ -476,6 +887,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -485,6 +899,13 @@ packages: assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -496,6 +917,14 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} + baseline-browser-mapping@2.10.18: + resolution: {integrity: sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==} + engines: {node: '>=6.0.0'} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -512,6 +941,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bson@6.10.4: resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==} engines: {node: '>=16.20.1'} @@ -532,10 +966,20 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + caniuse-lite@1.0.30001787: + resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} + + canvas-confetti@1.9.4: + resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==} + chai@4.5.0: resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} engines: {node: '>=4'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} @@ -561,6 +1005,9 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} @@ -583,6 +1030,14 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -600,6 +1055,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-eql@4.1.4: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} @@ -615,10 +1073,18 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} @@ -626,6 +1092,9 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} @@ -637,6 +1106,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.335: + resolution: {integrity: sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -652,6 +1124,10 @@ packages: resolution: {integrity: sha512-U2SN0w3OpjFRVlrc17E6TMDmH58Xl9rai1MblNjAdwWp07Kk+llmzX0hjDpQdrDGzwmvOtgM5yI+meYX6iZ2xA==} engines: {node: '>=10.2.0'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -660,6 +1136,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -673,6 +1152,10 @@ packages: engines: {node: '>=12'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -686,6 +1169,17 @@ packages: peerDependencies: eslint: '>=7.0.0' + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.5.2: + resolution: {integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==} + peerDependencies: + eslint: ^9 || ^10 + eslint-scope@9.1.2: resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -739,6 +1233,10 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + express@4.22.1: resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} @@ -755,6 +1253,15 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -802,6 +1309,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} @@ -837,6 +1348,10 @@ packages: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -849,6 +1364,19 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -895,6 +1423,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -902,9 +1433,41 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + jsdom@29.0.2: + resolution: {integrity: sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -914,6 +1477,11 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + kareem@2.6.3: resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==} engines: {node: '>=12.0.0'} @@ -925,6 +1493,80 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + local-pkg@0.5.1: resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} engines: {node: '>=14'} @@ -936,13 +1578,34 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + lru-cache@11.3.3: + resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -1049,6 +1712,9 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + nodemon@3.1.14: resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==} engines: {node: '>=10'} @@ -1070,6 +1736,9 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -1097,6 +1766,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1132,6 +1804,10 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -1148,6 +1824,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1179,13 +1859,39 @@ packages: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + peerDependencies: + react: ^19.2.5 + + react-icons@5.6.0: + resolution: {integrity: sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==} + peerDependencies: + react: '*' + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rolldown@1.0.0-rc.15: + resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.60.1: resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -1197,6 +1903,17 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -1283,6 +2000,9 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.0.0: + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -1302,17 +2022,43 @@ packages: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + tinyspy@2.2.1: resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} + tldts-core@7.0.28: + resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==} + + tldts@7.0.28: + resolution: {integrity: sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1325,10 +2071,21 @@ packages: resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + tr46@5.1.1: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1350,10 +2107,20 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici@7.24.7: + resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} + engines: {node: '>=20.18.1'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -1401,6 +2168,49 @@ packages: terser: optional: true + vite@8.0.8: + resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitest@1.6.1: resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} engines: {node: ^18.0.0 || >=20.0.0} @@ -1426,14 +2236,71 @@ packages: jsdom: optional: true + vitest@4.1.4: + resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.4 + '@vitest/browser-preview': 4.1.4 + '@vitest/browser-webdriverio': 4.1.4 + '@vitest/coverage-istanbul': 4.1.4 + '@vitest/coverage-v8': 4.1.4 + '@vitest/ui': 4.1.4 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + whatwg-url@14.2.0: resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1463,10 +2330,20 @@ packages: utf-8-validate: optional: true + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xmlhttprequest-ssl@2.1.2: resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} engines: {node: '>=0.4.0'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1475,8 +2352,181 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + snapshots: + '@asamuzakjp/css-color@5.1.10': + dependencies: + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.0.9': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.2(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -1580,6 +2630,10 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 + '@exodus/bytes@1.15.0(@noble/hashes@1.8.0)': + optionalDependencies: + '@noble/hashes': 1.8.0 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -1595,18 +2649,97 @@ snapshots: dependencies: '@sinclair/typebox': 0.27.10 + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@mongodb-js/saslprep@1.4.6': dependencies: sparse-bitfield: 3.0.3 + '@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.1 + optional: true + '@noble/hashes@1.8.0': {} + '@oxc-project/types@0.124.0': {} + '@paralleldrive/cuid2@2.3.1': dependencies: '@noble/hashes': 1.8.0 + '@rolldown/binding-android-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.15': {} + + '@rolldown/pluginutils@1.0.0-rc.7': {} + '@rollup/rollup-android-arm-eabi@4.60.1': optional: true @@ -1686,10 +2819,48 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@standard-schema/spec@1.1.0': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/cors@2.8.19': dependencies: '@types/node': 25.5.2 + '@types/deep-eql@4.0.2': {} + '@types/esrecurse@4.3.1': {} '@types/estree@1.0.8': {} @@ -1710,28 +2881,82 @@ snapshots: dependencies: '@types/node': 25.5.2 + '@vitejs/plugin-react@6.0.1(vite@8.0.8(@types/node@25.5.2))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.8(@types/node@25.5.2) + + '@vitest/coverage-v8@4.1.4(vitest@4.1.4)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.4 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.0.0 + tinyrainbow: 3.1.0 + vitest: 4.1.4(@types/node@25.5.2)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2(@noble/hashes@1.8.0))(vite@8.0.8(@types/node@25.5.2)) + '@vitest/expect@1.6.1': dependencies: '@vitest/spy': 1.6.1 '@vitest/utils': 1.6.1 chai: 4.5.0 + '@vitest/expect@4.1.4': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@25.5.2))': + dependencies: + '@vitest/spy': 4.1.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.8(@types/node@25.5.2) + + '@vitest/pretty-format@4.1.4': + dependencies: + tinyrainbow: 3.1.0 + '@vitest/runner@1.6.1': dependencies: '@vitest/utils': 1.6.1 p-limit: 5.0.0 pathe: 1.1.2 + '@vitest/runner@4.1.4': + dependencies: + '@vitest/utils': 4.1.4 + pathe: 2.0.3 + '@vitest/snapshot@1.6.1': dependencies: magic-string: 0.30.21 pathe: 1.1.2 pretty-format: 29.7.0 + '@vitest/snapshot@4.1.4': + dependencies: + '@vitest/pretty-format': 4.1.4 + '@vitest/utils': 4.1.4 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/spy@1.6.1': dependencies: tinyspy: 2.2.1 + '@vitest/spy@4.1.4': {} + '@vitest/utils@1.6.1': dependencies: diff-sequences: 29.6.3 @@ -1739,6 +2964,12 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 + '@vitest/utils@4.1.4': + dependencies: + '@vitest/pretty-format': 4.1.4 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -1761,6 +2992,8 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + ansi-styles@5.2.0: {} anymatch@3.1.3: @@ -1768,18 +3001,36 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.2 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-flatten@1.1.1: {} asap@2.0.6: {} assertion-error@1.1.0: {} + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + asynckit@0.4.0: {} balanced-match@4.0.4: {} base64id@2.0.0: {} + baseline-browser-mapping@2.10.18: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + binary-extensions@2.3.0: {} body-parser@1.20.4: @@ -1807,6 +3058,14 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.18 + caniuse-lite: 1.0.30001787 + electron-to-chromium: 1.5.335 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + bson@6.10.4: {} bytes@3.1.2: {} @@ -1823,6 +3082,10 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + caniuse-lite@1.0.30001787: {} + + canvas-confetti@1.9.4: {} + chai@4.5.0: dependencies: assertion-error: 1.1.0 @@ -1833,6 +3096,8 @@ snapshots: pathval: 1.1.1 type-detect: 4.1.0 + chai@6.2.2: {} + check-error@1.0.3: dependencies: get-func-name: 2.0.2 @@ -1863,6 +3128,8 @@ snapshots: content-type@1.0.5: {} + convert-source-map@2.0.0: {} + cookie-signature@1.0.7: {} cookie-signature@1.2.2: {} @@ -1882,6 +3149,18 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + data-urls@7.0.0(@noble/hashes@1.8.0): + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1(@noble/hashes@1.8.0) + transitivePeerDependencies: + - '@noble/hashes' + debug@2.6.9: dependencies: ms: 2.0.0 @@ -1892,6 +3171,8 @@ snapshots: optionalDependencies: supports-color: 5.5.0 + decimal.js@10.6.0: {} + deep-eql@4.1.4: dependencies: type-detect: 4.1.0 @@ -1902,8 +3183,12 @@ snapshots: depd@2.0.0: {} + dequal@2.0.3: {} + destroy@1.2.0: {} + detect-libc@2.1.2: {} + dezalgo@1.0.4: dependencies: asap: 2.0.6 @@ -1911,6 +3196,8 @@ snapshots: diff-sequences@29.6.3: {} + dom-accessibility-api@0.5.16: {} + dotenv@16.6.1: {} dunder-proto@1.0.1: @@ -1921,6 +3208,8 @@ snapshots: ee-first@1.1.1: {} + electron-to-chromium@1.5.335: {} + encodeurl@2.0.0: {} engine.io-client@6.6.4: @@ -1954,10 +3243,14 @@ snapshots: - supports-color - utf-8-validate + entities@6.0.1: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -1995,6 +3288,8 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + escalade@3.2.0: {} + escape-html@1.0.3: {} escape-string-regexp@4.0.0: {} @@ -2003,6 +3298,21 @@ snapshots: dependencies: eslint: 10.2.0 + eslint-plugin-react-hooks@7.0.1(eslint@10.2.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + eslint: 10.2.0 + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.2(eslint@10.2.0): + dependencies: + eslint: 10.2.0 + eslint-scope@9.1.2: dependencies: '@types/esrecurse': 4.3.1 @@ -2085,6 +3395,8 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + expect-type@1.3.0: {} + express@4.22.1: dependencies: accepts: 1.3.8 @@ -2129,6 +3441,10 @@ snapshots: fast-safe-stringify@2.1.1: {} + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -2184,6 +3500,8 @@ snapshots: function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + get-func-name@2.0.2: {} get-intrinsic@1.3.0: @@ -2220,6 +3538,8 @@ snapshots: has-flag@3.0.0: {} + has-flag@4.0.0: {} + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -2230,6 +3550,20 @@ snapshots: dependencies: function-bind: 1.1.2 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + html-encoding-sniffer@6.0.0(@noble/hashes@1.8.0): + dependencies: + '@exodus/bytes': 1.15.0(@noble/hashes@1.8.0) + transitivePeerDependencies: + - '@noble/hashes' + + html-escaper@2.0.2: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -2266,18 +3600,67 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-stream@3.0.0: {} isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + jsdom@29.0.2(@noble/hashes@1.8.0): + dependencies: + '@asamuzakjp/css-color': 5.1.10 + '@asamuzakjp/dom-selector': 7.0.9 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.2(css-tree@3.2.1) + '@exodus/bytes': 1.15.0(@noble/hashes@1.8.0) + css-tree: 3.2.1 + data-urls: 7.0.0(@noble/hashes@1.8.0) + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0(@noble/hashes@1.8.0) + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.3.3 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.24.7 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1(@noble/hashes@1.8.0) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + kareem@2.6.3: {} keyv@4.5.4: @@ -2289,6 +3672,55 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + local-pkg@0.5.1: dependencies: mlly: 1.8.2 @@ -2302,12 +3734,32 @@ snapshots: dependencies: get-func-name: 2.0.2 + lru-cache@11.3.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + math-intrinsics@1.1.0: {} + mdn-data@2.27.1: {} + media-typer@0.3.0: {} memory-pager@1.5.0: {} @@ -2389,6 +3841,8 @@ snapshots: negotiator@0.6.3: {} + node-releases@2.0.37: {} + nodemon@3.1.14: dependencies: chokidar: 3.6.0 @@ -2412,6 +3866,8 @@ snapshots: object-inspect@1.13.4: {} + obug@2.1.1: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -2445,6 +3901,10 @@ snapshots: dependencies: p-limit: 3.1.0 + parse5@8.0.0: + dependencies: + entities: 6.0.1 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -2465,6 +3925,8 @@ snapshots: picomatch@2.3.2: {} + picomatch@4.0.4: {} + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -2481,6 +3943,12 @@ snapshots: prettier@3.8.2: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -2513,12 +3981,48 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + react-dom@19.2.5(react@19.2.5): + dependencies: + react: 19.2.5 + scheduler: 0.27.0 + + react-icons@5.6.0(react@19.2.5): + dependencies: + react: 19.2.5 + + react-is@17.0.2: {} + react-is@18.3.1: {} + react@19.2.5: {} + readdirp@3.6.0: dependencies: picomatch: 2.3.2 + require-from-string@2.0.2: {} + + rolldown@1.0.0-rc.15: + dependencies: + '@oxc-project/types': 0.124.0 + '@rolldown/pluginutils': 1.0.0-rc.15 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-x64': 1.0.0-rc.15 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.15 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 + rollup@4.60.1: dependencies: '@types/estree': 1.0.8 @@ -2554,6 +4058,14 @@ snapshots: safer-buffer@2.1.2: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + semver@7.7.4: {} send@0.19.2: @@ -2682,6 +4194,8 @@ snapshots: std-env@3.10.0: {} + std-env@4.0.0: {} + strip-final-newline@3.0.0: {} strip-literal@2.1.1: @@ -2714,12 +4228,33 @@ snapshots: dependencies: has-flag: 3.0.0 + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + symbol-tree@3.2.4: {} + tinybench@2.9.0: {} + tinyexec@1.1.1: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@0.8.4: {} + tinyrainbow@3.1.0: {} + tinyspy@2.2.1: {} + tldts-core@7.0.28: {} + + tldts@7.0.28: + dependencies: + tldts-core: 7.0.28 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -2728,10 +4263,21 @@ snapshots: touch@3.1.1: {} + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.28 + tr46@5.1.1: dependencies: punycode: 2.3.1 + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + tslib@2.8.1: + optional: true + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -2749,8 +4295,16 @@ snapshots: undici-types@7.18.2: {} + undici@7.24.7: {} + unpipe@1.0.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -2759,13 +4313,13 @@ snapshots: vary@1.1.2: {} - vite-node@1.6.1(@types/node@25.5.2): + vite-node@1.6.1(@types/node@25.5.2)(lightningcss@1.32.0): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@5.5.0) pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.4.21(@types/node@25.5.2) + vite: 5.4.21(@types/node@25.5.2)(lightningcss@1.32.0) transitivePeerDependencies: - '@types/node' - less @@ -2777,7 +4331,7 @@ snapshots: - supports-color - terser - vite@5.4.21(@types/node@25.5.2): + vite@5.4.21(@types/node@25.5.2)(lightningcss@1.32.0): dependencies: esbuild: 0.21.5 postcss: 8.5.9 @@ -2785,8 +4339,20 @@ snapshots: optionalDependencies: '@types/node': 25.5.2 fsevents: 2.3.3 + lightningcss: 1.32.0 + + vite@8.0.8(@types/node@25.5.2): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.9 + rolldown: 1.0.0-rc.15 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.5.2 + fsevents: 2.3.3 - vitest@1.6.1(@types/node@25.5.2): + vitest@1.6.1(@types/node@25.5.2)(jsdom@29.0.2(@noble/hashes@1.8.0))(lightningcss@1.32.0): dependencies: '@vitest/expect': 1.6.1 '@vitest/runner': 1.6.1 @@ -2805,11 +4371,12 @@ snapshots: strip-literal: 2.1.1 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.21(@types/node@25.5.2) - vite-node: 1.6.1(@types/node@25.5.2) + vite: 5.4.21(@types/node@25.5.2)(lightningcss@1.32.0) + vite-node: 1.6.1(@types/node@25.5.2)(lightningcss@1.32.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.5.2 + jsdom: 29.0.2(@noble/hashes@1.8.0) transitivePeerDependencies: - less - lightningcss @@ -2820,13 +4387,58 @@ snapshots: - supports-color - terser + vitest@4.1.4(@types/node@25.5.2)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2(@noble/hashes@1.8.0))(vite@8.0.8(@types/node@25.5.2)): + dependencies: + '@vitest/expect': 4.1.4 + '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@25.5.2)) + '@vitest/pretty-format': 4.1.4 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.8(@types/node@25.5.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.5.2 + '@vitest/coverage-v8': 4.1.4(vitest@4.1.4) + jsdom: 29.0.2(@noble/hashes@1.8.0) + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + webidl-conversions@7.0.0: {} + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + whatwg-url@14.2.0: dependencies: tr46: 5.1.1 webidl-conversions: 7.0.0 + whatwg-url@16.0.1(@noble/hashes@1.8.0): + dependencies: + '@exodus/bytes': 1.15.0(@noble/hashes@1.8.0) + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which@2.0.2: dependencies: isexe: 2.0.0 @@ -2842,8 +4454,20 @@ snapshots: ws@8.18.3: {} + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + xmlhttprequest-ssl@2.1.2: {} + yallist@3.1.1: {} + yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} + + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {}