commit fa8e0c58ce7f9f870ff4ab71b827f1c8b126837d Author: renato97 Date: Tue Jul 21 15:31:46 2026 -0300 feat: Puente Mayúscula-Minúscula + TTS SpeechSynthesis + E2E fixes diff --git a/.atl/.skill-registry.cache.json b/.atl/.skill-registry.cache.json new file mode 100644 index 0000000..abda2d6 --- /dev/null +++ b/.atl/.skill-registry.cache.json @@ -0,0 +1,3 @@ +{ + "fingerprint": "ee41cd16aee142978eedd00dcf1854a07a534d9e" +} diff --git a/.atl/skill-registry.md b/.atl/skill-registry.md new file mode 100644 index 0000000..b0d53ee --- /dev/null +++ b/.atl/skill-registry.md @@ -0,0 +1,38 @@ +# Skill Registry — edueasy + + + +Last updated: 2026-07-20 + +## Sources scanned + +- /home/ren/.config/opencode/skills +- /home/ren/.codex/skills + +## Contract + +**Delegator use only.** This registry is an index, not a summary. Any agent that launches subagents reads it to select relevant skills, then passes exact `SKILL.md` paths for the subagent to read before work. + +`SKILL.md` remains the source of truth. Do not inject generated summaries or compact rules by default; pass paths so subagents load the full runtime contract and preserve author intent. + +## Skills + +| Skill | Trigger / description | Scope | Path | +| --- | --- | --- | --- | +| `branch-pr` | Create Gentle AI pull requests with issue-first checks. Trigger: creating, opening, or preparing PRs for review. | user | `/home/ren/.config/opencode/skills/branch-pr/SKILL.md` | +| `chained-pr` | Trigger: PRs over 400 lines, stacked PRs, review slices. Split oversized changes into chained PRs that protect review focus. | user | `/home/ren/.config/opencode/skills/chained-pr/SKILL.md` | +| `cognitive-doc-design` | Design docs that reduce cognitive load. Trigger: writing guides, READMEs, RFCs, onboarding, architecture, or review-facing docs. | user | `/home/ren/.config/opencode/skills/cognitive-doc-design/SKILL.md` | +| `comment-writer` | Write warm, direct collaboration comments. Trigger: PR feedback, issue replies, reviews, Slack messages, or GitHub comments. | user | `/home/ren/.config/opencode/skills/comment-writer/SKILL.md` | +| `go-testing` | Trigger: Go tests, go test coverage, Bubbletea teatest, golden files. Apply focused Go testing patterns. | user | `/home/ren/.config/opencode/skills/go-testing/SKILL.md` | +| `issue-creation` | Create Gentle AI issues with issue-first checks. Trigger: creating GitHub issues, bug reports, or feature requests. | user | `/home/ren/.config/opencode/skills/issue-creation/SKILL.md` | +| `judgment-day` | Trigger: judgment day, dual review, adversarial review, juzgar. Run blind dual review, fix confirmed issues, then re-judge. | user | `/home/ren/.config/opencode/skills/judgment-day/SKILL.md` | +| `skill-creator` | Trigger: new skills, agent instructions, documenting AI usage patterns. Create LLM-first skills with valid frontmatter. | user | `/home/ren/.config/opencode/skills/skill-creator/SKILL.md` | +| `skill-improver` | Trigger: improve skills, audit skills, refactor skills, skill quality. Audit and upgrade existing LLM-first skills. | user | `/home/ren/.config/opencode/skills/skill-improver/SKILL.md` | +| `work-unit-commits` | Plan commits as reviewable work units. Trigger: implementation, commit splitting, chained PRs, or keeping tests and docs with code. | user | `/home/ren/.config/opencode/skills/work-unit-commits/SKILL.md` | + +## Loading protocol + +1. Match task context and target files against the `Trigger / description` column. +2. Pass only the matching `Path` values to the subagent under `## Skills to load before work`. +3. Instruct the subagent to read those exact `SKILL.md` files before reading, writing, reviewing, testing, or creating artifacts. +4. If no matching skill exists, proceed without project skill injection and report `skill_resolution: none`. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c34c34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +.next/ +*.tsbuildinfo +next-env.d.ts +.env +.env.local +.env*.local +.env.local +.env*.local +.cache/ +dist/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d988054 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +# Stage 1: Install dependencies +FROM node:22-alpine AS deps +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + +# Stage 2: Build +FROM node:22-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npx prisma generate +RUN npm run build + +# Stage 3: Production +FROM node:22-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..ad88ed2 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,219 @@ +# EduEasy — Plan de Plataforma + +## Arquitectura General + +Plataforma educativa para una nena de 8 años con déficit de atención, baja abstractividad, que no sabe leer ni contar (bases malas en 1er grado). Dos caras en un mismo server: + +| App | Dominio | Dispositivo | Usuario | +|-----|---------|-------------|---------| +| **Aprendizaje** | `mate.cbcren.online` | iPad Air 4 (solo dedo) | La nena | +| **Dashboard** | `simulacro.cbcren.online` | Poco F6 Pro | La mamá | + +**Stack**: Next.js 15 + React 19 + TypeScript estricto + Tailwind CSS + shadcn/ui + Better Auth + Prisma + PostgreSQL + ts-fsrs +**Deploy**: All-in-one Docker (un proceso Next.js + Postgres) en red `caddy` del server existente (194.163.191.200) + +## Decisiones de Diseño Cerradas + +| Decisión | Respuesta | +|----------|-----------| +| **Mascota** | 🐱 **Gato** — guía las sesiones con instrucciones en audio, celebra logros, da feedback en errorless learning. Sin nombre fijo aún. | +| **Paleta** | **Calmada** — fondo `#FFF8F0` crema/beige, primario `#8CB8A0` verde salvia, secundario `#6B8FA3` azul medio, acento `#D4A574` terracota, texto `#2D3436` (no negro puro). Bajas saturaciones en todos. | +| **Audio** | **TTS por ahora** (Web Speech API, voz es-ES femenina). Se reemplaza por grabaciones reales es-AR en Fase 4+ cuando el contenido esté validado. | + +--- + +## Pedagogía (Métodos Europeos) + +| Área | Método | Aplicación | +|------|--------|------------| +| **Lectura** | Borel-Maisonny (FR) | Cada fonema tiene un gesto físico + sonido. La nena hace el gesto en el aire + traza con el dedo en la pantalla + escucha el audio. | +| **Lectura** | Jolly Phonics (UK) adaptado | Orden de aprendizaje: no A-B-C, sino por frecuencia de uso. Vocales primero (A, E, I, O, U) + consonantes sonoras continuas (S, M, L, N). | +| **Lectura** | Método silábico | Español = 5 vocales puras, sílabas CV, CVC, CCV. Silabeo sistemático desde el día 1. | +| **Numeración** | CPA (Concrete-Pictórico-Abstracto) | **Concreto**: objetos (tapitas virtuales para contar). **Pictórico**: dibujos de objetos. **Abstracto**: el número símbolo. | +| **Numeración** | Regletas Cuisenaire (Bélgica) | Cantidad como barra de color. 1=blanco, 2=rojo, 3=verde claro... La nena ve y toca la cantidad sin "contar" abstractamente. | +| **Internalización** | Galperin (URSS) | **1. Mano**: manipular objetos. **2. Voz externa**: decir en voz alta. **3. Voz interna**: susurrar. **4. Mental**: hacerlo en la cabeza. | +| **Errores** | Errorless learning | **Nunca** se muestra "incorrecto". Si titubea, el sistema da la pista más obvia (modelado). Si persiste el error, fadea la ayuda. | +| **Refuerzo** | Fijo y predecible | Sin slots, sin FOMO, sin rachas ansiosas. Estrellita predecible después de cada ronda completa. | +| **Sesiones** | Chunking estructurado | 10-15 min máximo. Temporizador visual tipo Time Timer. Micro-pausa motora cada 5 min (saltar, aplaudir). | + +--- + +## Stack Técnico + +``` +Frontend: Next.js 15 (App Router) + React 19 + TypeScript + Tailwind CSS +UI Lib: shadcn/ui (componentes base) +Auth: Better Auth (login mamá + QR pairing → JWT device-bound para nena) +DB: PostgreSQL + Prisma ORM +Spaced Rep: ts-fsrs (Free Spaced Repetition Scheduler) +Canvas: perfect-freehand (trazo con dedo) + @use-gesture/react +Audio: Web Speech API (TTS es-ES/es-MX, confirmado: TTS por ahora → reemplazar con grabaciones reales es-AR en Fase 4+) +Animación: framer-motion (transiciones lentas 300-500ms, sin sparkles) +Charts: Recharts (dashboard mamá) +Deploy: Docker + red caddy existente +``` + +### Repos reutilizados +- `ts-fsrs` (npm) — algoritmo FSRS de repetición espaciada +- `perfect-freehand` (npm) — trazado fluido con dedo (Excalidraw) +- Better Auth (npm) — auth + device pairing +- shadcn/ui (npm) — componentes base de UI +- Math Learning Center apps — referencia UX de manipulativos virtuales (number-rack, ten-frames) + +--- + +## Modelo de Datos + +```prisma +model Family { id, name, created_at } +model Parent { id, family_id, email, name, auth_provider } +model Child { id, family_id, name, avatar, birthdate, profile_notes } +model Device { id, child_id, device_fingerprint, paired_at, last_seen, role } +model Session { id, child_id, started_at, ended_at, duration_sec, skills_practiced } +model SkillAttempt { id, session_id, skill_code, prompt_level, correct, response_ms } +model FsrsCard { id, child_id, skill_code, due_at, stability, difficulty, reps, lapses } +model Milestone { id, child_id, skill_code, reached_at } +``` + +--- + +## Estructura del Proyecto + +``` +/home/ren/edueasy/ +├── app/ +│ ├── globals.css +│ ├── layout.tsx # Raíz (fonts, metadata) +│ ├── page.tsx # Redirige según host +│ ├── child/ # mate.cbcren.online +│ │ ├── layout.tsx # Layout infantil (gato, audio siempre presente) +│ │ ├── page.tsx # Menú principal de la nena +│ │ ├── sesion/ +│ │ └── logros/ +│ ├── parent/ # simulacro.cbcren.online +│ │ ├── layout.tsx # Layout dashboard + sidebar +│ │ ├── page.tsx # Dashboard principal +│ │ ├── auth/login/ +│ │ ├── auth/pairing/ +│ │ └── configuracion/ +│ └── api/ +│ ├── auth/[...all]/ # Better Auth handler +│ ├── pairing/ # QR pairing endpoint +│ └── sessions/sync/ # Sync offline → server +├── components/ +│ ├── ui/ # shadcn/ui +│ ├── child/ # Componentes de la nena +│ │ ├── gato-mascota +│ │ ├── trazo-letra (canvas tracing) +│ │ ├── vocal-card +│ │ ├── cuisenaire-rod +│ │ ├── temporizador-visual +│ │ └── boton-grande (60-80px) +│ └── parent/ # Componentes del dashboard +├── lib/ +│ ├── db.ts (Prisma client) +│ ├── auth.ts (Better Auth server) +│ ├── auth-client.ts +│ ├── speech.ts (Web Speech API) +│ ├── fsrs.ts (ts-fsrs wrapper) +│ └── pedagogia/ +│ ├── borel-maisonny.ts (fonemas + gestos) +│ ├── cuisenaire.ts (regletas) +│ ├── errorless.ts (prompt fading) +│ └── cpa-progression.ts (CPA estados) +├── prisma/schema.prisma +├── middleware.ts (host-based routing) +├── Dockerfile +├── docker-compose.yml +├── package.json +├── tsconfig.json +├── next.config.ts +├── tailwind.config.ts +├── postcss.config.js +├── components.json (shadcn/ui) +└── .env.local +``` + +--- + +## UX Constraints (iPad Air 4 + Dedo) + +- **Touch targets**: 60–80px mínimo (baselines adultos 44px no alcanzan para niños) +- **Drag > Tap** para interacciones principales +- **Una sola acción focal** por pantalla +- **100% iconografía + audio** (la nena NO LEE) +- **Sin "X roja"** nunca — sonido neutro + mascota dice "probemos juntas" +- **Fondo claro baja saturación** (crema/beige) + acentos verde salvia + azul medio +- **Botón "Tocá para empezar"** en cada sesión (unlock AudioContext en iOS Safari) +- **Persistencia al backend siempre** (Safari evicta IndexedDB a 7 días) + +### iOS Safari Técnico +- Audio: .m4a primario + .mp3 fallback. **NUNCA .ogg**. +- AudioContext: requiere user gesture para desbloquear. +- `100dvh` en vez de `100vh` (barra de Safari). +- `touch-action: manipulation` para eliminar delay 300ms. +- inputs con `font-size: 16px` mínimo (evita zoom automático). + +--- + +## Roadmap + +### Fase 0 — Fundaciones (1-2 sem) +- [x] Repositorio + package.json + configs +- [x] Docker + docker-compose +- [x] Prisma schema + migración +- [x] Better Auth + QR pairing +- [x] Design system base (gato mascota, paleta, component primitives) +- [x] Caddy config update + +### Fase 1 — Lectura MVP +- [ ] Módulo vocales: A, E, I, O, U +- [ ] Audio TTS para cada fonema + palabra ejemplo +- [ ] Gesto Borel-Maisonny (animación + instrucción) +- [ ] Trazo con dedo (canvas + perfect-freehand) +- [ ] Sesión guiada 10 min + temporizador visual +- [ ] Errorless learning + fading automático +- [ ] Logros por vocal completada + +### Fase 2 — Numeración MVP +- [ ] Módulo cantidades 1-5 (expandir a 10) +- [ ] CPA: objetos → imagen → símbolo +- [ ] Regletas Cuisenaire virtuales (arrastrar, comparar) +- [ ] Conteo one-to-one con arrastre +- [ ] Galperin: mano → voz → cabeza + +### Fase 3 — Adaptativo + Dashboard +- [ ] ts-fsrs: repaso espaciado por skill +- [ ] Dashboard mamá: streak, tiempo, mastery %, último resumen +- [ ] Push notifications al Poco F6 Pro +- [ ] QR pairing funcional +- [ ] Export de progreso + +### Fase 4+ (futuro) +- [ ] Consonantes y sílabas completas +- [ ] Números 11-100, suma/resta +- [ ] Grabaciones de audio real (es-AR) +- [ ] IA adaptativa (Ollama/Open WebUI) + +--- + +## Diseño Visual + +**Mascota**: 🐱 Gato (sin nombre definido — que la nena lo nombre). +- Guía las sesiones, da instrucciones en audio, celebra logros. +- Animaciones suaves (no bruscas), tamaño grande en pantalla. + +**Paleta**: +- Fondo: `#FFF8F0` (crema suave) +- Primario: `#8CB8A0` (verde salvia) +- Secundario: `#6B8FA3` (azul medio) +- Acento: `#D4A574` (terracota suave) +- Texto: `#2D3436` (gris oscuro, no negro puro) +- Correcto: `#7FB685` (verde suave, no brillante) + +**Tipografía**: +- Títulos: Fredoka One (redondeada, amigable, legible) +- Interface: Inter (sistema, para dashboard de la mamá) +- La nena no ve texto — solo escucha. Las letras aparecen grandes, claras, bien trazadas. + +--- diff --git a/app/api/auth/[...all]/route.ts b/app/api/auth/[...all]/route.ts new file mode 100644 index 0000000..5cd4846 --- /dev/null +++ b/app/api/auth/[...all]/route.ts @@ -0,0 +1,4 @@ +import { auth } from "@/lib/auth" +import { toNextJsHandler } from "better-auth/next-js" + +export const { POST, GET } = toNextJsHandler(auth) diff --git a/app/api/children/route.ts b/app/api/children/route.ts new file mode 100644 index 0000000..e303597 --- /dev/null +++ b/app/api/children/route.ts @@ -0,0 +1,57 @@ +import { NextRequest, NextResponse } from "next/server" +import { prisma } from "@/lib/db" +import { auth } from "@/lib/auth" + +export async function GET(request: NextRequest) { + const session = await auth.api.getSession({ headers: request.headers }) + if (!session?.user?.id) { + return NextResponse.json({ error: "No autorizado" }, { status: 401 }) + } + + const parent = await prisma.parent.findUnique({ + where: { id: session.user.id }, + include: { + family: { + include: { children: true }, + }, + }, + }) + + if (!parent || !parent.family) { + return NextResponse.json({ error: "Parent or family not found" }, { status: 404 }) + } + + return NextResponse.json(parent.family.children) +} + +export async function POST(request: Request) { + const session = await auth.api.getSession({ headers: request.headers }) + if (!session?.user?.id) { + return NextResponse.json({ error: "No autorizado" }, { status: 401 }) + } + + const { name, birthdate, profileNotes } = await request.json() + if (!name) { + return NextResponse.json({ error: "name required" }, { status: 400 }) + } + + const parent = await prisma.parent.findUnique({ + where: { id: session.user.id }, + select: { familyId: true }, + }) + + if (!parent || !parent.familyId) { + return NextResponse.json({ error: "Parent has no family" }, { status: 400 }) + } + + const child = await prisma.child.create({ + data: { + name, + birthdate: birthdate ? new Date(birthdate) : undefined, + profileNotes, + familyId: parent.familyId, + }, + }) + + return NextResponse.json(child, { status: 201 }) +} diff --git a/app/api/curriculum/grade/route.ts b/app/api/curriculum/grade/route.ts new file mode 100644 index 0000000..ccee70a --- /dev/null +++ b/app/api/curriculum/grade/route.ts @@ -0,0 +1,39 @@ +import { NextRequest, NextResponse } from "next/server" +import { prisma } from "@/lib/db" + +export async function POST(request: NextRequest) { + try { + const { childId, exerciseId, skillCode, correct, promptLevel, responseMs, errorType, stage } = await request.json() + + if (!childId || !exerciseId || !skillCode) { + return NextResponse.json({ error: "Missing required fields" }, { status: 400 }) + } + + const sessionLog = await prisma.sessionLog.findFirst({ + where: { childId }, + orderBy: { startedAt: "desc" }, + }) + + if (!sessionLog) { + return NextResponse.json({ error: "No active session" }, { status: 400 }) + } + + await prisma.skillAttempt.create({ + data: { + sessionLogId: sessionLog.id, + skillCode, + promptLevel: promptLevel ?? 0, + correct, + responseMs: responseMs ?? null, + errorType: errorType ?? null, + exerciseId, + stage: stage ?? null, + }, + }) + + return NextResponse.json({ success: true }) + } catch (error) { + console.error("Curriculum grade error:", error) + return NextResponse.json({ error: "Internal error" }, { status: 500 }) + } +} diff --git a/app/api/curriculum/next/route.ts b/app/api/curriculum/next/route.ts new file mode 100644 index 0000000..9672526 --- /dev/null +++ b/app/api/curriculum/next/route.ts @@ -0,0 +1,13 @@ +import { NextRequest, NextResponse } from "next/server" +import { getNextExercise } from "@/lib/curriculum/engine" + +export async function GET(request: NextRequest) { + const childId = request.nextUrl.searchParams.get("childId") + const topic = (request.nextUrl.searchParams.get("topic") || "lectura") as "lectura" | "numeros" + if (!childId) { + return NextResponse.json({ error: "childId required" }, { status: 400 }) + } + + const exercise = await getNextExercise(childId, topic) + return NextResponse.json(exercise || { id: null, done: true }) +} diff --git a/app/api/curriculum/progress/route.ts b/app/api/curriculum/progress/route.ts new file mode 100644 index 0000000..6da0953 --- /dev/null +++ b/app/api/curriculum/progress/route.ts @@ -0,0 +1,77 @@ +import { NextRequest, NextResponse } from "next/server" +import { prisma } from "@/lib/db" +import { initCurriculum, getLecturaStages, getNumerosStages } from "@/curriculum/index" +import { getCurrentStage, getNextExercise } from "@/lib/curriculum/engine" + +export async function GET(request: NextRequest) { + const childId = request.nextUrl.searchParams.get("childId") + const topic = (request.nextUrl.searchParams.get("topic") || "lectura") as "lectura" | "numeros" + if (!childId) { + return NextResponse.json({ error: "childId required" }, { status: 400 }) + } + + await initCurriculum() + + const stages = topic === "lectura" ? getLecturaStages() : getNumerosStages() + const stageKeys = Object.keys(stages).map(Number).sort((a, b) => a - b) + + const progress = prisma.curriculumProgress.findMany({ + where: { childId, topic }, + orderBy: { stage: "asc" }, + }) + + const allAttempts = prisma.skillAttempt.findMany({ + where: { + exerciseId: { not: null }, + stage: { not: null }, + sessionLog: { childId }, + }, + select: { exerciseId: true, correct: true }, + }) + + const [progressRows, attempts] = await Promise.all([progress, allAttempts]) + + const mastered = new Set( + attempts.filter((a) => a.correct).map((a) => a.exerciseId), + ) + + const stagesInfo = stageKeys.map((sk) => { + const s = stages[sk] + const total = s?.exercises?.length || 0 + const completed = s?.exercises?.filter((e) => mastered.has(e.id)).length || 0 + const prog = progressRows.find((p) => p.stage === sk) + return { + stage: sk, + name: s?.name || `Etapa ${sk}`, + total, + completed, + unlocked: !!prog, + completedAt: prog?.completedAt || null, + percent: total > 0 ? Math.round((completed / total) * 100) : 0, + } + }) + + const errorTypes = await prisma.skillAttempt.groupBy({ + by: ["errorType"], + where: { + errorType: { not: null }, + sessionLog: { childId }, + }, + _count: { errorType: true }, + }) + + const errorPatterns = errorTypes.map((e) => ({ + errorType: e.errorType, + count: e._count.errorType, + })) + + const activeProgress = progressRows.find((p) => !p.completedAt) + const currentStage = activeProgress?.stage || 1 + + return NextResponse.json({ + currentStage, + stages: stagesInfo, + errorPatterns, + hasActiveSession: true, + }) +} diff --git a/app/api/dashboard/summary/route.ts b/app/api/dashboard/summary/route.ts new file mode 100644 index 0000000..43161b1 --- /dev/null +++ b/app/api/dashboard/summary/route.ts @@ -0,0 +1,159 @@ +import { NextRequest, NextResponse } from "next/server" +import { prisma } from "@/lib/db" + +export async function GET(request: NextRequest) { + const childId = request.nextUrl.searchParams.get("childId") + if (!childId) { + return NextResponse.json({ error: "childId required" }, { status: 400 }) + } + + const today = new Date() + today.setHours(0, 0, 0, 0) + + const [totalSessions, todaySessions, milestones, fsrsCards, lastSession, weeklySessions, milestoneRecords, curriculumProgress, errorPatterns, allAttempts] = + await Promise.all([ + prisma.sessionLog.count({ where: { childId } }), + prisma.sessionLog.count({ + where: { childId, startedAt: { gte: today } }, + }), + prisma.milestone.count({ where: { childId } }), + prisma.fsrsCard.findMany({ where: { childId } }), + prisma.sessionLog.findFirst({ + where: { childId }, + orderBy: { startedAt: "desc" }, + include: { skillAttempts: true }, + }), + prisma.sessionLog.findMany({ + where: { childId, startedAt: { gte: new Date(Date.now() - 7 * 86400000) } }, + orderBy: { startedAt: "asc" }, + select: { startedAt: true, durationSec: true }, + }), + prisma.milestone.findMany({ + where: { childId }, + orderBy: { reachedAt: "desc" }, + select: { skillCode: true, reachedAt: true }, + }), + prisma.curriculumProgress.findMany({ + where: { childId }, + orderBy: { stage: "asc" }, + }), + prisma.skillAttempt.findMany({ + where: { + errorType: { not: null }, + sessionLog: { childId }, + }, + select: { errorType: true }, + }), + prisma.skillAttempt.findMany({ + where: { + correct: false, + exerciseId: { not: null }, + sessionLog: { childId }, + }, + select: { exerciseId: true, errorType: true }, + }), + ]) + + const todayMinutes = todaySessions > 0 + ? await prisma.sessionLog.aggregate({ + where: { childId, startedAt: { gte: today } }, + _sum: { durationSec: true }, + }).then((r) => Math.round((r._sum.durationSec ?? 0) / 60)) + : 0 + + const totalSkills = fsrsCards.length + const masteredSkills = fsrsCards.filter((c) => c.reps >= 5 && c.stability > 30).length + const masteryPercent = totalSkills > 0 ? Math.round((masteredSkills / totalSkills) * 100) : 0 + + const weeklyData = weeklySessions.map((s) => ({ + date: s.startedAt.toISOString().slice(0, 10), + minutes: Math.round((s.durationSec ?? 0) / 60), + })) + + const skillBreakdown = fsrsCards.map((c) => ({ + skillCode: c.skillCode, + stability: c.stability, + difficulty: c.difficulty, + reps: c.reps, + due: c.dueAt.toISOString(), + })) + + const streak = await calculateStreak(childId) + + const totalErrors = allAttempts.length + const errorTypeCount = errorPatterns.reduce>((acc, e) => { + const key = e.errorType || "sin-clasificar" + acc[key] = (acc[key] || 0) + 1 + return acc + }, {}) + + const stageProgress = curriculumProgress.map((cp) => ({ + topic: cp.topic, + stage: cp.stage, + completedAt: cp.completedAt?.toISOString() || null, + })) + + return NextResponse.json({ + totalSessions, + todaySessions, + todayMinutes, + masteryPercent, + masteredSkills, + totalSkills, + milestones, + streak, + weeklyData, + skillBreakdown, + milestoneData: milestoneRecords, + stageProgress, + errorTypeCount, + totalErrors, + lastSession: lastSession + ? { + startedAt: lastSession.startedAt.toISOString(), + durationSec: lastSession.durationSec, + skillsPracticed: lastSession.skillsPracticed, + correctCount: lastSession.skillAttempts.filter((a) => a.correct).length, + totalAttempts: lastSession.skillAttempts.length, + } + : null, + }) +} + +async function calculateStreak(childId: string): Promise { + const logs = await prisma.sessionLog.findMany({ + where: { childId }, + orderBy: { startedAt: "desc" }, + select: { startedAt: true }, + }) + + if (logs.length === 0) return 0 + + const days = [ + ...new Set(logs.map((l) => l.startedAt.toISOString().slice(0, 10))), + ].sort() + .reverse() + + let streak = 1 + const today = new Date().toISOString().slice(0, 10) + + if (days[0] !== today && days[0] !== getYesterday()) return 0 + + for (let i = 1; i < days.length; i++) { + const prev = new Date(days[i - 1]) + const curr = new Date(days[i]) + const diff = (prev.getTime() - curr.getTime()) / 86400000 + if (Math.round(diff) === 1) { + streak++ + } else { + break + } + } + return streak +} + +function getYesterday(): string { + const d = new Date() + d.setDate(d.getDate() - 1) + return d.toISOString().slice(0, 10) +} diff --git a/app/api/fsrs/grade/route.ts b/app/api/fsrs/grade/route.ts new file mode 100644 index 0000000..b44c815 --- /dev/null +++ b/app/api/fsrs/grade/route.ts @@ -0,0 +1,57 @@ +import { NextRequest, NextResponse } from "next/server" +import { prisma } from "@/lib/db" +import { buildFsrsCard, gradeCard, skillCodeFromGrade } from "@/lib/fsrs" + +export async function POST(request: NextRequest) { + try { + const { childId, skillCode, correct, responseMs } = await request.json() + if (!childId || !skillCode) { + return NextResponse.json({ error: "childId and skillCode required" }, { status: 400 }) + } + + const existingCard = await prisma.fsrsCard.findFirst({ + where: { childId, skillCode }, + }) + + const grade = skillCodeFromGrade(correct, responseMs ?? 5000) + const baseCard = buildFsrsCard({ + stability: existingCard?.stability, + difficulty: existingCard?.difficulty, + reps: existingCard?.reps, + lapses: existingCard?.lapses, + due: existingCard?.dueAt, + }) + + const { card: updatedCard } = gradeCard(baseCard, grade) + + if (existingCard) { + await prisma.fsrsCard.update({ + where: { id: existingCard.id }, + data: { + dueAt: updatedCard.due, + stability: updatedCard.stability, + difficulty: updatedCard.difficulty, + reps: updatedCard.reps, + lapses: updatedCard.lapses, + }, + }) + } else { + await prisma.fsrsCard.create({ + data: { + childId, + skillCode, + dueAt: updatedCard.due, + stability: updatedCard.stability, + difficulty: updatedCard.difficulty, + reps: updatedCard.reps, + lapses: updatedCard.lapses, + }, + }) + } + + return NextResponse.json({ success: true }) + } catch (error) { + console.error("FSRS grade error:", error) + return NextResponse.json({ error: "Internal error" }, { status: 500 }) + } +} diff --git a/app/api/fsrs/next/route.ts b/app/api/fsrs/next/route.ts new file mode 100644 index 0000000..d502596 --- /dev/null +++ b/app/api/fsrs/next/route.ts @@ -0,0 +1,35 @@ +import { NextRequest, NextResponse } from "next/server" +import { prisma } from "@/lib/db" +import { buildFsrsCard, getRetrievability } from "@/lib/fsrs" + +export async function GET(request: NextRequest) { + const childId = request.nextUrl.searchParams.get("childId") + if (!childId) { + return NextResponse.json({ error: "childId required" }, { status: 400 }) + } + + const dueCard = await prisma.fsrsCard.findFirst({ + where: { + childId, + dueAt: { lte: new Date() }, + }, + orderBy: { dueAt: "asc" }, + }) + + if (dueCard) { + const card = buildFsrsCard({ + stability: dueCard.stability, + difficulty: dueCard.difficulty, + reps: dueCard.reps, + lapses: dueCard.lapses, + due: dueCard.dueAt, + }) + return NextResponse.json({ + ...dueCard, + cardId: dueCard.id, + retrievability: getRetrievability(card), + }) + } + + return NextResponse.json({ skillCode: null }) +} diff --git a/app/api/milestones/route.ts b/app/api/milestones/route.ts new file mode 100644 index 0000000..d9bc44d --- /dev/null +++ b/app/api/milestones/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from "next/server" +import { prisma } from "@/lib/db" + +export async function GET(request: NextRequest) { + const childId = request.nextUrl.searchParams.get("childId") + if (!childId) { + return NextResponse.json({ error: "childId required" }, { status: 400 }) + } + const milestones = await prisma.milestone.findMany({ + where: { childId }, + orderBy: { reachedAt: "desc" }, + }) + return NextResponse.json(milestones) +} + +export async function POST(request: Request) { + try { + const { childId, skillCode } = await request.json() + if (!childId || !skillCode) { + return NextResponse.json({ error: "childId and skillCode required" }, { status: 400 }) + } + const existing = await prisma.milestone.findUnique({ + where: { childId_skillCode: { childId, skillCode } }, + }) + if (existing) { + return NextResponse.json(existing) + } + const milestone = await prisma.milestone.create({ + data: { childId, skillCode }, + }) + return NextResponse.json(milestone, { status: 201 }) + } catch (error) { + console.error("Milestone error:", error) + return NextResponse.json({ error: "Internal error" }, { status: 500 }) + } +} diff --git a/app/api/pairing/route.ts b/app/api/pairing/route.ts new file mode 100644 index 0000000..14f161e --- /dev/null +++ b/app/api/pairing/route.ts @@ -0,0 +1,98 @@ +import { NextResponse } from "next/server" +import { prisma } from "@/lib/db" +import { randomUUID } from "crypto" + +export async function POST(request: Request) { + try { + const { deviceFingerprint, pairingCode, childId, action } = await request.json() + + if (action === "generate") { + if (!childId) { + return NextResponse.json({ error: "childId required" }, { status: 400 }) + } + const code = randomUUID().slice(0, 8).toUpperCase() + await prisma.pendingPairing.create({ + data: { + code, + childId, + expiresAt: new Date(Date.now() + 10 * 60 * 1000), + }, + }) + return NextResponse.json({ pairingCode: code }) + } + + if (pairingCode) { + const pending = await prisma.pendingPairing.findUnique({ + where: { code: pairingCode }, + }) + if (!pending || pending.expiresAt < new Date()) { + return NextResponse.json({ error: "Código inválido o expirado" }, { status: 404 }) + } + + const targetChildId = childId || pending.childId + if (!targetChildId) { + return NextResponse.json({ error: "Sin niño asociado" }, { status: 400 }) + } + + if (childId && !pending.childId) { + await prisma.pendingPairing.update({ + where: { id: pending.id }, + data: { childId }, + }) + } + + const fp = deviceFingerprint || pending.deviceFingerprint + if (!fp) { + return NextResponse.json({ error: "Se requiere deviceFingerprint" }, { status: 400 }) + } + + const device = await prisma.device.create({ + data: { + childId: targetChildId, + deviceFingerprint: fp, + role: "child", + }, + include: { child: true }, + }) + + await prisma.pendingPairing.delete({ where: { id: pending.id } }) + + return NextResponse.json({ + success: true, + childId: device.childId, + childName: device.child.name, + }) + } + + if (deviceFingerprint) { + const existingDevice = await prisma.device.findFirst({ + where: { deviceFingerprint }, + }) + if (existingDevice) { + return NextResponse.json({ + paired: true, + childId: existingDevice.childId, + role: existingDevice.role, + }) + } + + const code = randomUUID().slice(0, 8).toUpperCase() + await prisma.pendingPairing.create({ + data: { + code, + deviceFingerprint, + expiresAt: new Date(Date.now() + 10 * 60 * 1000), + }, + }) + return NextResponse.json({ + paired: false, + pairingCode: code, + }) + } + + return NextResponse.json({ error: "Invalid request" }, { status: 400 }) + } catch (error) { + console.error("Pairing error:", error) + return NextResponse.json({ error: "Internal error" }, { status: 500 }) + } +} diff --git a/app/api/sessions/sync/route.ts b/app/api/sessions/sync/route.ts new file mode 100644 index 0000000..38cc23d --- /dev/null +++ b/app/api/sessions/sync/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from "next/server" +import { prisma } from "@/lib/db" + +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { childId, session, attempts } = body + + if (!childId || !session) { + return NextResponse.json({ error: "Missing required fields" }, { status: 400 }) + } + + const sessionLog = await prisma.sessionLog.create({ + data: { + childId, + startedAt: new Date(session.startedAt), + endedAt: session.endedAt ? new Date(session.endedAt) : undefined, + durationSec: session.durationSec, + skillsPracticed: session.skillsPracticed, + skillAttempts: { + create: (attempts || []).map((a: any) => ({ + skillCode: a.skillCode, + promptLevel: a.promptLevel ?? 0, + correct: a.correct, + responseMs: a.responseMs, + })), + }, + }, + }) + + return NextResponse.json({ success: true, sessionId: sessionLog.id }) + } catch (error) { + console.error("Sync error:", error) + return NextResponse.json({ error: "Internal error" }, { status: 500 }) + } +} diff --git a/app/api/tts/route.ts b/app/api/tts/route.ts new file mode 100644 index 0000000..a115528 --- /dev/null +++ b/app/api/tts/route.ts @@ -0,0 +1,49 @@ +import { NextRequest, NextResponse } from "next/server" +import { execSync } from "child_process" +import { existsSync, mkdirSync } from "fs" +import { createHash } from "crypto" +import { readFile, mkdir } from "fs/promises" +import { join } from "path" + +const CACHE_DIR = join(process.cwd(), "public", "tts-cache") + +async function ensureCacheDir() { + if (!existsSync(CACHE_DIR)) { + await mkdir(CACHE_DIR, { recursive: true }) + } +} + +function getCachePath(text: string): string { + const hash = createHash("md5").update(text).digest("hex") + return join(CACHE_DIR, `${hash}.wav`) +} + +export async function GET(request: NextRequest) { + const text = request.nextUrl.searchParams.get("text") + if (!text || text.length > 200) { + return NextResponse.json({ error: "text param required (max 200 chars)" }, { status: 400 }) + } + + await ensureCacheDir() + const cachePath = getCachePath(text) + + if (!existsSync(cachePath)) { + try { + execSync( + `espeak-ng -v es-mx -s 140 -p 60 "${text.replace(/"/g, '\\"')}" -w "${cachePath}"`, + { timeout: 10000 }, + ) + } catch { + return NextResponse.json({ error: "TTS generation failed" }, { status: 500 }) + } + } + + const audioBuffer = await readFile(cachePath) + return new NextResponse(audioBuffer, { + headers: { + "Content-Type": "audio/wav", + "Content-Length": audioBuffer.length.toString(), + "Cache-Control": "public, max-age=31536000, immutable", + }, + }) +} diff --git a/app/child/layout.tsx b/app/child/layout.tsx new file mode 100644 index 0000000..d273529 --- /dev/null +++ b/app/child/layout.tsx @@ -0,0 +1,26 @@ +"use client" + +import { useEffect, useState } from "react" +import AudioUnlock from "@/components/child/audio-unlock" + +export default function ChildLayout({ + children, +}: { + children: React.ReactNode +}) { + const [audioReady, setAudioReady] = useState(false) + + useEffect(() => { + document.body.style.overscrollBehavior = "none" + }, []) + + if (!audioReady) { + return setAudioReady(true)} /> + } + + return ( +
+ {children} +
+ ) +} diff --git a/app/child/lectura/page.tsx b/app/child/lectura/page.tsx new file mode 100644 index 0000000..7d55bb6 --- /dev/null +++ b/app/child/lectura/page.tsx @@ -0,0 +1,167 @@ +"use client" + +import { useState, useCallback, useEffect, useRef } from "react" +import { motion, AnimatePresence } from "framer-motion" +import { GatoMascota } from "@/components/child/gato-mascota" +import NoChildError from "@/components/child/no-child-error" +import TemporizadorVisual from "@/components/child/temporizador-visual" +import ExerciseDispatcher from "@/components/exercises/exercise-dispatcher" +import { speak, stopSpeaking } from "@/lib/speech" +import type { Exercise } from "@/curriculum/types" + +const CHILD_ID_KEY = "edueasy_child_id" + +type PageState = "loading" | "ready" | "complete" | "error" + +export default function LecturaSesion() { + const [pageState, setPageState] = useState("loading") + const [exercise, setExercise] = useState(null) + const [running, setRunning] = useState(true) + const [score, setScore] = useState(0) + const [totalAttempts, setTotalAttempts] = useState(0) + const childIdRef = useRef("") + const startedAtRef = useRef(new Date().toISOString()) + + const getChildId = useCallback(() => { + const id = localStorage.getItem(CHILD_ID_KEY) || "" + childIdRef.current = id + return id + }, []) + + const fetchNext = useCallback(async () => { + const childId = getChildId() + if (!childId) { + setPageState("error") + return null + } + + try { + const res = await fetch(`/api/curriculum/next?childId=${childId}&topic=lectura`) + const data = await res.json() + if (data.done || !data.id) { + setPageState("complete") + return null + } + setExercise(data) + setPageState("ready") + return data + } catch { + setPageState("error") + return null + } + }, [getChildId]) + + useEffect(() => { + fetchNext() + }, [fetchNext]) + + const gradeAttempt = useCallback(async (ex: Exercise, correct: boolean) => { + const childId = childIdRef.current + if (!childId || !ex) return + + setTotalAttempts((t) => t + 1) + if (correct) setScore((s) => s + 1) + + const errorType = !correct + ? ex.errorType || "discriminacion-auditiva" + : null + + try { + await fetch("/api/curriculum/grade", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + childId, + exerciseId: ex.id, + skillCode: ex.skillCode, + correct, + promptLevel: 0, + responseMs: 3000, + errorType, + stage: ex.stage, + }), + }) + } catch { + // silent + } + }, []) + + const handleComplete = useCallback( + async (correct: boolean) => { + if (!exercise) return + await gradeAttempt(exercise, correct) + stopSpeaking() + if (correct) { + await speak("¡Muy bien!") + await new Promise((r) => setTimeout(r, 800)) + } + fetchNext() + }, + [exercise, gradeAttempt, fetchNext], + ) + + const resetSession = useCallback(() => { + setScore(0) + setTotalAttempts(0) + setRunning(true) + startedAtRef.current = new Date().toISOString() + fetchNext() + }, [fetchNext]) + + if (pageState === "loading") { + return ( +
+ +
+ ) + } + + if (pageState === "error") { + return + } + + if (pageState === "complete") { + return ( +
+ +

+ Aciertos: {score}/{totalAttempts} +

+ +
+ ) + } + + return ( +
+ { + setRunning(false) + setPageState("complete") + }} + running={running} + /> + + + + {exercise && ( + + )} + + +
+ ) +} diff --git a/app/child/logros/page.tsx b/app/child/logros/page.tsx new file mode 100644 index 0000000..9dcbddd --- /dev/null +++ b/app/child/logros/page.tsx @@ -0,0 +1,78 @@ +"use client" + +import { useState, useEffect } from "react" +import { motion } from "framer-motion" +import { GatoMascota } from "@/components/child/gato-mascota" + +const ALL_SKILLS = [ + { id: "vocal-a", label: "Letra A", emoji: "✈️" }, + { id: "vocal-e", label: "Letra E", emoji: "🐘" }, + { id: "vocal-i", label: "Letra I", emoji: "🏔️" }, + { id: "vocal-o", label: "Letra O", emoji: "🐻" }, + { id: "vocal-u", label: "Letra U", emoji: "🍇" }, + { id: "numero-1", label: "Número 1", emoji: "1️⃣" }, + { id: "numero-2", label: "Número 2", emoji: "2️⃣" }, + { id: "numero-3", label: "Número 3", emoji: "3️⃣" }, + { id: "numero-4", label: "Número 4", emoji: "4️⃣" }, + { id: "numero-5", label: "Número 5", emoji: "5️⃣" }, +] + +const CHILD_ID_KEY = "edueasy_child_id" + +export default function LogrosPage() { + const [milestones, setMilestones] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const childId = localStorage.getItem(CHILD_ID_KEY) + if (!childId) { + setLoading(false) + return + } + fetch(`/api/milestones?childId=${childId}`) + .then((r) => r.json()) + .then((data) => { + if (Array.isArray(data)) { + setMilestones(data.map((m: any) => m.skillCode)) + } + }) + .catch(() => {}) + .finally(() => setLoading(false)) + }, []) + + const completedCount = milestones.length + + return ( +
+ + +

+ {completedCount} de {ALL_SKILLS.length} completados +

+ + {loading ? ( +

Cargando...

+ ) : ( +
+ {ALL_SKILLS.map((skill, i) => { + const completed = milestones.includes(skill.id) + return ( + + {skill.emoji} + {skill.label} + + ) + })} +
+ )} +
+ ) +} diff --git a/app/child/numeros/page.tsx b/app/child/numeros/page.tsx new file mode 100644 index 0000000..b7fbed2 --- /dev/null +++ b/app/child/numeros/page.tsx @@ -0,0 +1,167 @@ +"use client" + +import { useState, useCallback, useEffect, useRef } from "react" +import { motion, AnimatePresence } from "framer-motion" +import { GatoMascota } from "@/components/child/gato-mascota" +import NoChildError from "@/components/child/no-child-error" +import TemporizadorVisual from "@/components/child/temporizador-visual" +import ExerciseDispatcher from "@/components/exercises/exercise-dispatcher" +import { speak, stopSpeaking } from "@/lib/speech" + +const CHILD_ID_KEY = "edueasy_child_id" +const META_KEY = "edueasy_child_meta" + +type PageState = "loading" | "ready" | "complete" | "error" + +export default function NumerosSesion() { + const [pageState, setPageState] = useState("loading") + const [exercise, setExercise] = useState(null) + const [running, setRunning] = useState(true) + const [score, setScore] = useState(0) + const [totalAttempts, setTotalAttempts] = useState(0) + const childIdRef = useRef("") + const startedAtRef = useRef(new Date().toISOString()) + + const getChildId = useCallback(() => { + const id = localStorage.getItem(CHILD_ID_KEY) || "" + childIdRef.current = id + return id + }, []) + + const fetchNext = useCallback(async () => { + const childId = getChildId() + if (!childId) { + setPageState("error") + return null + } + + try { + const res = await fetch(`/api/curriculum/next?childId=${childId}&topic=numeros`) + const data = await res.json() + if (data.done || !data.id) { + setPageState("complete") + return null + } + setExercise(data) + setPageState("ready") + return data + } catch { + setPageState("error") + return null + } + }, [getChildId]) + + useEffect(() => { + fetchNext() + }, [fetchNext]) + + const gradeAttempt = useCallback(async (ex: any, correct: boolean) => { + const childId = childIdRef.current + if (!childId || !ex) return + + setTotalAttempts((t) => t + 1) + if (correct) setScore((s) => s + 1) + + const errorType = !correct + ? ex.errorType || "discriminacion-auditiva" + : null + + try { + await fetch("/api/curriculum/grade", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + childId, + exerciseId: ex.id, + skillCode: ex.skillCode, + correct, + promptLevel: 0, + responseMs: 3000, + errorType, + stage: ex.stage, + }), + }) + } catch { + // silent + } + }, []) + + const handleComplete = useCallback( + async (correct: boolean) => { + if (!exercise) return + await gradeAttempt(exercise, correct) + stopSpeaking() + if (correct) { + await speak("¡Muy bien!") + await new Promise((r) => setTimeout(r, 800)) + } + fetchNext() + }, + [exercise, gradeAttempt, fetchNext], + ) + + const resetSession = useCallback(() => { + setScore(0) + setTotalAttempts(0) + setRunning(true) + startedAtRef.current = new Date().toISOString() + fetchNext() + }, [fetchNext]) + + if (pageState === "loading") { + return ( +
+ +
+ ) + } + + if (pageState === "error") { + return + } + + if (pageState === "complete") { + return ( +
+ +

+ Aciertos: {score}/{totalAttempts} +

+ +
+ ) + } + + return ( +
+ { + setRunning(false) + setPageState("complete") + }} + running={running} + /> + + + + {exercise && ( + + )} + + +
+ ) +} diff --git a/app/child/page.tsx b/app/child/page.tsx new file mode 100644 index 0000000..e1ff5de --- /dev/null +++ b/app/child/page.tsx @@ -0,0 +1,34 @@ +"use client" + +import { useRouter } from "next/navigation" +import { GatoMascota } from "@/components/child/gato-mascota" + +const activities = [ + { id: "lectura", label: "Letras", icon: "Aa", color: "bg-primary" }, + { id: "numeros", label: "Números", icon: "123", color: "bg-accent" }, + { id: "logros", label: "Mis logros", icon: "⭐", color: "bg-warning" }, + { id: "pairing", label: "iPad nuevo", icon: "🔗", color: "bg-secondary" }, +] + +export default function ChildHome() { + const router = useRouter() + + return ( +
+ + +
+ {activities.map((a) => ( + + ))} +
+
+ ) +} diff --git a/app/child/pairing/page.tsx b/app/child/pairing/page.tsx new file mode 100644 index 0000000..fe1587c --- /dev/null +++ b/app/child/pairing/page.tsx @@ -0,0 +1,90 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import { GatoMascota } from "@/components/child/gato-mascota" +import { speak } from "@/lib/speech" + +const CHILD_ID_KEY = "edueasy_child_id" + +export default function ChildPairingPage() { + const [code, setCode] = useState("") + const [loading, setLoading] = useState(false) + const [error, setError] = useState("") + const [paired, setPaired] = useState(false) + const router = useRouter() + + const handlePair = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + setError("") + + const deviceFingerprint = `child-${navigator.userAgent.slice(0, 64)}` + const res = await fetch("/api/pairing", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + pairingCode: code.toUpperCase(), + deviceFingerprint, + }), + }) + const data = await res.json() + + if (data.error) { + setError(data.error) + setLoading(false) + return + } + + localStorage.setItem(CHILD_ID_KEY, data.childId) + setPaired(true) + speak("¡Emparejado! Ahora podemos aprender juntos") + } + + if (paired) { + return ( +
+ + +
+ ) + } + + return ( +
+ + +
+ setCode(e.target.value.toUpperCase())} + placeholder="Código" + maxLength={8} + className="w-full rounded-2xl border-2 border-primary bg-white px-6 py-4 text-center text-3xl tracking-[0.5em] uppercase font-display font-bold focus:outline-none focus:ring-4 focus:ring-primary/30" + autoFocus + /> + {error &&

{error}

} + + +
+
+ ) +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..8871305 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,52 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap'); + +@layer base { + :root { + --radius: 1rem; + --background: 38 100% 97%; + --foreground: 200 6% 19%; + --primary: 150 22% 64%; + --primary-foreground: 0 0% 100%; + --secondary: 200 24% 53%; + --secondary-foreground: 0 0% 100%; + --accent: 30 48% 64%; + --accent-foreground: 0 0% 100%; + --muted: 30 20% 88%; + --muted-foreground: 200 4% 40%; + --card: 0 0% 100%; + --border: 30 20% 88%; + --ring: 150 22% 64%; + --success: 130 25% 61%; + --destructive: 0 62% 68%; + --warning: 35 85% 62%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground font-sans; + overscroll-behavior: none; + -webkit-overflow-scrolling: touch; + } +} + +@layer utilities { + .touch-target { + @apply min-h-[60px] min-w-[60px]; + } + .touch-target-lg { + @apply min-h-[80px] min-w-[80px]; + } +} + +/* iOS Safari quirks fixes */ +input, textarea { + font-size: 16px !important; +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..f867b28 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,38 @@ +import type { Metadata, Viewport } from "next" +import { Inter } from "next/font/google" +import "./globals.css" + +const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }) + +export const metadata: Metadata = { + title: "EduEasy", + description: "Plataforma educativa infantil", + appleWebApp: { + capable: true, + statusBarStyle: "default", + title: "EduEasy", + }, +} + +export const viewport: Viewport = { + width: "device-width", + initialScale: 1, + maximumScale: 1, + viewportFit: "cover", +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + + + {children} + + ) +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..8d35acc --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,19 @@ +"use client" + +import { useEffect } from "react" +import { useRouter } from "next/navigation" + +export default function RootPage() { + const router = useRouter() + + useEffect(() => { + const host = window.location.hostname + if (host.includes("simulacro")) { + router.replace("/parent") + } else { + router.replace("/child") + } + }, [router]) + + return null +} diff --git a/app/parent/auth/login/page.tsx b/app/parent/auth/login/page.tsx new file mode 100644 index 0000000..3ddfc4c --- /dev/null +++ b/app/parent/auth/login/page.tsx @@ -0,0 +1,79 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import { authClient } from "@/lib/auth-client" + +export default function LoginPage() { + const [email, setEmail] = useState("") + const [password, setPassword] = useState("") + const [loading, setLoading] = useState(false) + const [error, setError] = useState("") + const router = useRouter() + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + setError("") + + const { error: err } = await authClient.signIn.email({ + email, + password, + }) + + if (err) { + setError(err.message || "Error al iniciar sesión") + setLoading(false) + return + } + + router.push("/parent") + } + + return ( +
+
+

+ EduEasy · Mamá +

+ + {error && ( +

{error}

+ )} + + setEmail(e.target.value)} + className="w-full rounded-xl border border-border bg-white px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-primary" + required + /> + + setPassword(e.target.value)} + className="w-full rounded-xl border border-border bg-white px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-primary" + required + /> + + + +

+ ¿No tenés cuenta?{" "} + + Crear cuenta + +

+
+
+ ) +} diff --git a/app/parent/auth/pairing/page.tsx b/app/parent/auth/pairing/page.tsx new file mode 100644 index 0000000..b2acc2f --- /dev/null +++ b/app/parent/auth/pairing/page.tsx @@ -0,0 +1,144 @@ +"use client" + +import { useState, useEffect } from "react" +import { authClient } from "@/lib/auth-client" +import { useRouter } from "next/navigation" +import { GatoMascota } from "@/components/child/gato-mascota" + +interface ChildSummary { + id: string + name: string | null +} + +export default function PairingPage() { + const [code, setCode] = useState("") + const [loading, setLoading] = useState(false) + const [error, setError] = useState("") + const [paired, setPaired] = useState(false) + const [children, setChildren] = useState([]) + const [selectedChildId, setSelectedChildId] = useState("") + const { data: session } = authClient.useSession() + const router = useRouter() + + useEffect(() => { + fetch("/api/children") + .then((r) => r.json()) + .then((list) => { + if (Array.isArray(list)) { + setChildren(list) + if (list.length > 0) setSelectedChildId(list[0].id) + } + }) + .catch(() => {}) + }, []) + + const handlePairing = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + setError("") + + if (!selectedChildId) { + setError("Seleccioná un niño") + setLoading(false) + return + } + + try { + const res = await fetch("/api/pairing", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + pairingCode: code.toUpperCase(), + childId: selectedChildId, + }), + }) + const data = await res.json() + + if (data.error) { + setError(data.error) + setLoading(false) + return + } + + setPaired(true) + } catch { + setError("Error de conexión") + } + setLoading(false) + } + + if (!session) { + return ( +
+

Iniciá sesión primero para emparejar

+ +
+ ) + } + + return ( +
+

Emparejar iPad

+ + {paired ? ( +
+ + +
+ ) : ( +
+

+ Ingresá el código de 8 caracteres que aparece en el iPad +

+ + {children.length > 0 && ( +
+ {children.map((c) => ( + + ))} +
+ )} + + setCode(e.target.value.toUpperCase())} + placeholder="Código de 8 letras" + maxLength={8} + className="w-full rounded-xl border border-border bg-white px-4 py-3 text-center text-2xl tracking-widest uppercase focus:outline-none focus:ring-2 focus:ring-primary" + autoFocus + /> + {error &&

{error}

} + +
+ )} +
+ ) +} diff --git a/app/parent/auth/register/page.tsx b/app/parent/auth/register/page.tsx new file mode 100644 index 0000000..5ae5d3e --- /dev/null +++ b/app/parent/auth/register/page.tsx @@ -0,0 +1,88 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import { authClient } from "@/lib/auth-client" + +export default function RegisterPage() { + const [email, setEmail] = useState("") + const [password, setPassword] = useState("") + const [name, setName] = useState("") + const [loading, setLoading] = useState(false) + const [error, setError] = useState("") + const router = useRouter() + + const handleRegister = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + setError("") + + const { error: err } = await authClient.signUp.email({ + email, + password, + name: name || "", + }) + + if (err) { + setError(err.message || "Error al registrarse") + setLoading(false) + return + } + + router.push("/parent") + } + + return ( +
+
+

+ Crear cuenta · Mamá +

+ + {error &&

{error}

} + + setName(e.target.value)} + className="w-full rounded-xl border border-border bg-white px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-primary" + /> + + setEmail(e.target.value)} + className="w-full rounded-xl border border-border bg-white px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-primary" + required + /> + + setPassword(e.target.value)} + minLength={8} + className="w-full rounded-xl border border-border bg-white px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-primary" + required + /> + + + +

+ ¿Ya tenés cuenta?{" "} + + Iniciá sesión + +

+
+
+ ) +} diff --git a/app/parent/configuracion/page.tsx b/app/parent/configuracion/page.tsx new file mode 100644 index 0000000..9b827bc --- /dev/null +++ b/app/parent/configuracion/page.tsx @@ -0,0 +1,36 @@ +"use client" + +import { authClient } from "@/lib/auth-client" +import { useRouter } from "next/navigation" + +export default function ConfigPage() { + const { data: session } = authClient.useSession() + const router = useRouter() + + return ( +
+

Configuración

+ +
+
+

Email

+

{session?.user?.email || "—"}

+
+ + + + +
+
+ ) +} diff --git a/app/parent/dashboard/page.tsx b/app/parent/dashboard/page.tsx new file mode 100644 index 0000000..577a08b --- /dev/null +++ b/app/parent/dashboard/page.tsx @@ -0,0 +1,293 @@ +"use client" + +import { useState, useEffect } from "react" +import { useRouter } from "next/navigation" +import { authClient } from "@/lib/auth-client" +import { MetricCard } from "@/components/parent/metric-card" +import { WeeklyActivityChart } from "@/components/parent/weekly-activity-chart" +import { SkillMasteryChart } from "@/components/parent/skill-mastery-chart" +import { MilestonesList } from "@/components/parent/milestones-list" +import ErrorPatternsChart from "@/components/parent/error-patterns-chart" + +interface ChildSummary { + id: string + name: string | null +} + +interface DashboardData { + totalSessions: number + todaySessions: number + todayMinutes: number + masteryPercent: number + masteredSkills: number + totalSkills: number + milestones: number + milestoneData: { skillCode: string; reachedAt: string }[] + streak: number + weeklyData: { date: string; minutes: number }[] + skillBreakdown: { skillCode: string; stability: number; reps: number }[] + stageProgress: { topic: string; stage: number; completedAt: string | null }[] + errorTypeCount: Record + totalErrors: number + lastSession: { + startedAt: string + durationSec: number + skillsPracticed: string + correctCount: number + totalAttempts: number + } | null +} + +export default function ParentDashboard() { + const { data: session, isPending } = authClient.useSession() + const router = useRouter() + const [children, setChildren] = useState([]) + const [selectedChildId, setSelectedChildId] = useState("") + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [creating, setCreating] = useState(false) + const [newChildName, setNewChildName] = useState("") + const [pairingCode, setPairingCode] = useState("") + + useEffect(() => { + fetch("/api/children") + .then((r) => r.json()) + .then((list) => { + if (Array.isArray(list)) { + setChildren(list) + if (list.length > 0) setSelectedChildId(list[0].id) + } + }) + .catch(() => {}) + }, []) + + useEffect(() => { + if (!selectedChildId) { + setLoading(false) + return + } + setLoading(true) + fetch(`/api/dashboard/summary?childId=${selectedChildId}`) + .then((r) => r.json()) + .then(setData) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [selectedChildId]) + + const handleCreateChild = async () => { + if (!newChildName.trim()) return + setCreating(true) + const res = await fetch("/api/children", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: newChildName.trim() }), + }) + if (res.ok) { + const child = await res.json() + setChildren((prev) => [...prev, child]) + setSelectedChildId(child.id) + setNewChildName("") + } + setCreating(false) + } + + const handleGeneratePairingCode = async () => { + if (!selectedChildId) return + const res = await fetch("/api/pairing", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action: "generate", childId: selectedChildId }), + }) + const data = await res.json() + if (data.pairingCode) { + setPairingCode(data.pairingCode) + } + } + + useEffect(() => { + if (!isPending && !session) { + router.push("/parent/auth/login") + } + }, [session, isPending, router]) + + if (isPending || !session) { + return ( +
Cargando...
+ ) + } + + const selectedChild = children.find((c) => c.id === selectedChildId) + + return ( +
+
+
+

Dashboard

+

+ Bienvenida, {session.user?.name || "Mamá"} +

+
+
+ + {children.length === 0 ? ( +
+

Agregar un niño

+
+ setNewChildName(e.target.value)} + placeholder="Nombre de la nena" + className="flex-1 rounded-xl border border-border bg-white px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-primary" + /> + +
+
+ ) : ( + <> +
+ {children.map((c) => ( + + ))} +
+ + {loading ? ( +
Cargando...
+ ) : data ? ( +
+
+ + 0 ? `${data.todaySessions} sesiones` : undefined} + variant="success" + /> + + +
+ +
+

+ Actividad semanal +

+ +
+ +
+

+ Maestría por skill +

+ +
+ +
+

Logros

+ +
+ + {data.stageProgress.length > 0 && ( +
+

Progreso curricular

+
+ {data.stageProgress.map((sp) => ( +
+ + {sp.topic === "lectura" ? "Lectura" : "Números"} — Etapa {sp.stage} + + + {sp.completedAt ? "✅ Completada" : "🔵 En curso"} + +
+ ))} +
+
+ )} + + {data.totalErrors > 0 && ( +
+

Patrones de error

+ +
+ )} + + {data.lastSession && ( +
+

Última sesión

+
+

{new Date(data.lastSession.startedAt).toLocaleDateString("es-AR")}

+

+ {Math.round((data.lastSession.durationSec ?? 0) / 60)} min ·{" "} + {data.lastSession.correctCount}/{data.lastSession.totalAttempts} correctas +

+

Skills: {data.lastSession.skillsPracticed || "—"}

+
+
+ )} + +
+ + +
+
+ ) : ( +
+ Seleccioná un niño para ver su progreso +
+ )} + + )} + + {pairingCode && ( +
+
+

Código de emparejamiento

+

+ Ingresá este código en el iPad de la nena +

+

+ {pairingCode} +

+ +
+
+ )} +
+ ) +} diff --git a/app/parent/layout.tsx b/app/parent/layout.tsx new file mode 100644 index 0000000..9051c1a --- /dev/null +++ b/app/parent/layout.tsx @@ -0,0 +1,86 @@ +"use client" + +import { useState } from "react" +import Link from "next/link" +import { usePathname } from "next/navigation" +import { authClient } from "@/lib/auth-client" +import { cn } from "@/lib/utils" + +const navItems = [ + { href: "/parent/dashboard", label: "Dashboard", emoji: "📊" }, + { href: "/parent/configuracion", label: "Configuración", emoji: "⚙️" }, +] + +export default function ParentLayout({ children }: { children: React.ReactNode }) { + const pathname = usePathname() + const { data: session } = authClient.useSession() + const [sidebarOpen, setSidebarOpen] = useState(false) + + if ( + pathname.startsWith("/parent/auth") + ) { + return
{children}
+ } + + return ( +
+
+
+ +

EduEasy

+
+
+ + {session?.user?.name || "Mamá"} + + +
+
+ + {sidebarOpen && ( +
setSidebarOpen(false)} + > + +
+ )} + +
{children}
+
+ ) +} diff --git a/app/parent/page.tsx b/app/parent/page.tsx new file mode 100644 index 0000000..168d0a4 --- /dev/null +++ b/app/parent/page.tsx @@ -0,0 +1,21 @@ +"use client" + +import { useEffect } from "react" +import { useRouter } from "next/navigation" +import { authClient } from "@/lib/auth-client" + +export default function ParentPage() { + const { data: session, isPending } = authClient.useSession() + const router = useRouter() + + useEffect(() => { + if (isPending) return + if (session) { + router.replace("/parent/dashboard") + } else { + router.replace("/parent/auth/login") + } + }, [session, isPending, router]) + + return null +} diff --git a/components.json b/components.json new file mode 100644 index 0000000..53f6fc3 --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/components/child/audio-unlock.tsx b/components/child/audio-unlock.tsx new file mode 100644 index 0000000..d1dd0b9 --- /dev/null +++ b/components/child/audio-unlock.tsx @@ -0,0 +1,43 @@ +"use client" + +import { useState } from "react" +import { motion } from "framer-motion" +import { GatoMascota } from "./gato-mascota" + +interface AudioUnlockProps { + onUnlock: () => void +} + +export default function AudioUnlock({ onUnlock }: AudioUnlockProps) { + const [pressed, setPressed] = useState(false) + + const handleUnlock = () => { + if (typeof window !== "undefined") { + const AudioCtx = window.AudioContext || (window as any).webkitAudioContext + if (AudioCtx) { + const ctx = new AudioCtx() + ctx.resume().then(() => { + ctx.close() + }) + } + } + setPressed(true) + setTimeout(onUnlock, 600) + } + + return ( +
+ + + + {pressed ? "¡Empezamos!" : "TOCÁ ACÁ"} + +
+ ) +} diff --git a/components/child/cpa-exercise.tsx b/components/child/cpa-exercise.tsx new file mode 100644 index 0000000..b89d7e3 --- /dev/null +++ b/components/child/cpa-exercise.tsx @@ -0,0 +1,33 @@ +"use client" + +import { speak } from "@/lib/speech" + +export interface CPAExerciseProps { + value: number + stage: "concrete" | "pictoric" | "abstract" + onCorrect: () => void + onIncorrect: () => void +} + +export function generateCPAItems(value: number, stage: string): Array<{ emoji: string; id: number }> { + const pool = ["🍎", "🐱", "⭐", "🌸", "🎈", "🐶", "🍪", "🌈", "🦋", "🧸"] + return Array.from({ length: value }, (_, i) => ({ + emoji: pool[i % pool.length], + id: i, + })) +} + +export function speakCPAInstruction(value: number, stage: string) { + if (stage === "concrete") { + speak(`Tocá ${value} ${getItemName(value)}`) + } else if (stage === "pictoric") { + speak(`¿Cuántos hay? Señalá el número`) + } else { + speak(`¿Este número es el ${value}?`) + } +} + +function getItemName(n: number): string { + if (n === 1) return "elemento" + return "elementos" +} diff --git a/components/child/cuisenaire-rod.tsx b/components/child/cuisenaire-rod.tsx new file mode 100644 index 0000000..6c14661 --- /dev/null +++ b/components/child/cuisenaire-rod.tsx @@ -0,0 +1,58 @@ +"use client" + +import { useState } from "react" +import { motion } from "framer-motion" + +export interface CuisenaireRodProps { + value: number + color: string + length: number + draggable?: boolean + onDrop?: (value: number) => void +} + +const rodColors: Record = { + 1: "#FFFFFF", + 2: "#E74C3C", + 3: "#2ECC71", + 4: "#E91E63", + 5: "#F1C40F", + 6: "#1ABC9C", + 7: "#2C3E50", + 8: "#3498DB", + 9: "#9B59B6", + 10: "#E67E22", +} + +const rodLengths: Record = { + 1: 20, 2: 40, 3: 60, 4: 80, 5: 100, + 6: 120, 7: 140, 8: 160, 9: 180, 10: 200, +} + +export { rodColors, rodLengths } + +export function CuisenaireRod({ value, color, length, draggable = false }: CuisenaireRodProps) { + const [isDragging, setIsDragging] = useState(false) + + return ( + setIsDragging(true)} + onDragEnd={() => setIsDragging(false)} + dragConstraints={{ left: -400, right: 400, top: -300, bottom: 300 }} + /> + ) +} diff --git a/components/child/gato-mascota.tsx b/components/child/gato-mascota.tsx new file mode 100644 index 0000000..4a785d9 --- /dev/null +++ b/components/child/gato-mascota.tsx @@ -0,0 +1,56 @@ +"use client" + +import { motion } from "framer-motion" + +interface GatoMascotaProps { + mood?: "happy" | "thinking" | "celebrate" | "coach" + message?: string + size?: "sm" | "md" | "lg" +} + +const moods = { + happy: { + emoji: "😺", + animation: { y: [0, -5, 0] }, + transition: { repeat: Infinity, duration: 2 }, + }, + thinking: { + emoji: "🤔", + animation: { rotate: [0, -10, 10, 0] }, + transition: { repeat: Infinity, duration: 1.5 }, + }, + celebrate: { + emoji: "🎉", + animation: { scale: [1, 1.2, 1] }, + transition: { repeat: Infinity, duration: 0.8 }, + }, + coach: { + emoji: "🐱", + animation: { y: 0 }, + transition: { duration: 0 }, + }, +} + +export function GatoMascota({ mood = "happy", message, size = "md" }: GatoMascotaProps) { + const config = moods[mood] + const sizeClass = size === "sm" ? "text-3xl" : size === "lg" ? "text-7xl" : "text-5xl" + + return ( +
+ + {config.emoji} + + {message && ( +

+ {message} +

+ )} +
+ ) +} + +export default GatoMascota diff --git a/components/child/no-child-error.tsx b/components/child/no-child-error.tsx new file mode 100644 index 0000000..467c090 --- /dev/null +++ b/components/child/no-child-error.tsx @@ -0,0 +1,53 @@ +"use client" + +import { useRouter } from "next/navigation" +import { GatoMascota } from "./gato-mascota" + +const CHILD_ID_KEY = "edueasy_child_id" + +interface Props { + onRetry?: () => void +} + +export default function NoChildError({ onRetry }: Props) { + const router = useRouter() + const hasChildId = typeof window !== "undefined" && localStorage.getItem(CHILD_ID_KEY) + + return ( +
+ +
+ {hasChildId ? ( +

+ No pude conectar con el servidor. Revisá la conexión. +

+ ) : ( + <> +

Pedile a mamá que te empareje desde su celular.

+

Mamá va a Dashboard → Emparejar nuevo iPad

+ + )} +
+
+ {hasChildId && onRetry && ( + + )} + +
+
+ ) +} diff --git a/components/child/temporizador-visual.tsx b/components/child/temporizador-visual.tsx new file mode 100644 index 0000000..b19eecf --- /dev/null +++ b/components/child/temporizador-visual.tsx @@ -0,0 +1,76 @@ +"use client" + +import { useEffect, useRef, useState } from "react" +import { motion } from "framer-motion" + +interface TemporizadorVisualProps { + durationMinutes: number + onComplete: () => void + running: boolean +} + +export default function TemporizadorVisual({ + durationMinutes, + onComplete, + running, +}: TemporizadorVisualProps) { + const [remaining, setRemaining] = useState(durationMinutes * 60) + const totalSec = durationMinutes * 60 + const intervalRef = useRef(null) + const onCompleteRef = useRef(onComplete) + onCompleteRef.current = onComplete + + useEffect(() => { + if (running) { + intervalRef.current = setInterval(() => { + setRemaining((prev) => { + if (prev <= 1) { + clearInterval(intervalRef.current!) + onCompleteRef.current() + return 0 + } + return prev - 1 + }) + }, 1000) + } else { + if (intervalRef.current) clearInterval(intervalRef.current) + } + + return () => { + if (intervalRef.current) clearInterval(intervalRef.current) + } + }, [running]) + + const fraction = remaining / totalSec + const degrees = fraction * 360 + const minutes = Math.floor(remaining / 60) + const seconds = remaining % 60 + + return ( +
+
+ + + 0.3 ? "#8CB8A0" : "#D4A574"} + strokeWidth="8" + strokeDasharray="264" + strokeDashoffset={264 * (1 - fraction)} + strokeLinecap="round" + className="transition-all duration-1000 ease-linear" + /> + + + {minutes}:{seconds.toString().padStart(2, "0")} + +
+
+ ) +} diff --git a/components/child/trazo-letra.tsx b/components/child/trazo-letra.tsx new file mode 100644 index 0000000..7b4f248 --- /dev/null +++ b/components/child/trazo-letra.tsx @@ -0,0 +1,158 @@ +"use client" + +import { useState, useRef, useCallback, useEffect } from "react" +import { getStroke } from "perfect-freehand" + +interface TrazoLetraProps { + letra: string + color?: string + onComplete?: () => void + promptLevel?: number +} + +export default function TrazoLetra({ + letra, + color = "#8CB8A0", + onComplete, + promptLevel = 0, +}: TrazoLetraProps) { + const canvasRef = useRef(null) + const [isDrawing, setIsDrawing] = useState(false) + const points = useRef([]) + const [showLetter, setShowLetter] = useState(promptLevel < 2) + + useEffect(() => { + if (promptLevel < 2) { + drawLetterGuide(letra) + } + }, [letra, promptLevel]) + + useEffect(() => { + const canvas = canvasRef.current + if (!canvas) return + canvas.style.touchAction = "none" + }, []) + + const drawLetterGuide = (l: string) => { + const canvas = canvasRef.current + if (!canvas) return + const ctx = canvas.getContext("2d") + if (!ctx) return + const w = canvas.width + const h = canvas.height + ctx.clearRect(0, 0, w, h) + + ctx.font = `${h * 0.6}px "Fredoka", sans-serif` + ctx.textAlign = "center" + ctx.textBaseline = "middle" + ctx.globalAlpha = 0.15 + ctx.fillStyle = "#2D3436" + ctx.fillText(l.toUpperCase(), w / 2, h / 2) + ctx.globalAlpha = 1 + } + + const startDrawing = useCallback((e: React.PointerEvent) => { + setIsDrawing(true) + points.current = [[e.clientX, e.clientY, e.pressure || 0.5]] + const canvas = canvasRef.current + if (canvas) canvas.setPointerCapture(e.pointerId) + }, []) + + const draw = useCallback((e: React.PointerEvent) => { + if (!isDrawing) return + points.current.push([e.clientX, e.clientY, e.pressure || 0.5]) + renderStroke() + }, [isDrawing]) + + const endDrawing = useCallback(() => { + setIsDrawing(false) + if (points.current.length > 5 && onComplete) { + onComplete() + } + points.current = [] + }, [onComplete]) + + const renderStroke = () => { + const canvas = canvasRef.current + if (!canvas) return + const ctx = canvas.getContext("2d") + if (!ctx) return + + const stroke = getStroke(points.current, { + size: 12, + thinning: 0.5, + smoothing: 0.5, + streamline: 0.5, + }) + + if (stroke.length < 2) return + + ctx.clearRect(0, 0, canvas.width, canvas.height) + if (showLetter) drawLetterGuide(letra) + + ctx.beginPath() + ctx.moveTo(stroke[0][0], stroke[0][1]) + for (let i = 1; i < stroke.length; i++) { + ctx.lineTo(stroke[i][0], stroke[i][1]) + } + ctx.strokeStyle = color + ctx.lineWidth = 3 + ctx.lineCap = "round" + ctx.lineJoin = "round" + ctx.fillStyle = color + ctx.fill() + + if (promptLevel < 3) { + drawDirectionArrow(ctx, letra) + } + } + + const drawDirectionArrow = (ctx: CanvasRenderingContext2D, l: string) => { + const w = ctx.canvas.width + const h = ctx.canvas.height + ctx.save() + ctx.globalAlpha = 0.3 + ctx.strokeStyle = "#8CB8A0" + ctx.lineWidth = 2 + ctx.fillStyle = "#8CB8A0" + + const arrowPaths: Record = { + a: { sx: w * 0.3, sy: h * 0.7, ex: w * 0.7, ey: h * 0.3 }, + e: { sx: w * 0.3, sy: h * 0.3, ex: w * 0.7, ey: h * 0.7 }, + i: { sx: w * 0.5, sy: h * 0.2, ex: w * 0.5, ey: h * 0.8 }, + o: { sx: w * 0.3, sy: h * 0.3, ex: w * 0.6, ey: h * 0.6 }, + u: { sx: w * 0.3, sy: h * 0.3, ex: w * 0.7, ey: h * 0.5 }, + } + + const path = arrowPaths[l.toLowerCase()] + if (path) { + ctx.beginPath() + ctx.moveTo(path.sx, path.sy) + ctx.lineTo(path.ex, path.ey) + ctx.stroke() + + const angle = Math.atan2(path.ey - path.sy, path.ex - path.sx) + const sz = 6 + ctx.beginPath() + ctx.moveTo(path.ex, path.ey) + ctx.lineTo(path.ex - sz * Math.cos(angle - Math.PI / 6), path.ey - sz * Math.sin(angle - Math.PI / 6)) + ctx.lineTo(path.ex - sz * Math.cos(angle + Math.PI / 6), path.ey - sz * Math.sin(angle + Math.PI / 6)) + ctx.closePath() + ctx.fill() + } + ctx.restore() + } + + return ( + + ) +} diff --git a/components/child/vocal-card.tsx b/components/child/vocal-card.tsx new file mode 100644 index 0000000..eb95cd1 --- /dev/null +++ b/components/child/vocal-card.tsx @@ -0,0 +1,43 @@ +"use client" + +import { motion } from "framer-motion" + +export interface VocalCardProps { + letra: string + fonema: string + palabra: string + imageEmoji: string + color?: string + onClick: () => void + disabled?: boolean +} + +export default function VocalCard({ + letra, + fonema, + palabra, + imageEmoji, + color = "#8CB8A0", + onClick, + disabled = false, +}: VocalCardProps) { + return ( + + + {letra.toUpperCase()} + + {imageEmoji} + + {palabra} + + + ) +} diff --git a/components/exercises/ejercicio-conciencia.tsx b/components/exercises/ejercicio-conciencia.tsx new file mode 100644 index 0000000..f799a3f --- /dev/null +++ b/components/exercises/ejercicio-conciencia.tsx @@ -0,0 +1,57 @@ +"use client" + +import { useState } from "react" +import { motion, AnimatePresence } from "framer-motion" +import { speak } from "@/lib/speech" +import { GatoMascota } from "@/components/child/gato-mascota" +import type { Exercise } from "@/curriculum/types" + +interface Props { + exercise: Exercise + onComplete: (correct: boolean) => void +} + +export default function EjercicioConciencia({ exercise, onComplete }: Props) { + const { content } = exercise + const options = content.options || [] + + const handleChoose = async (opt: (typeof options)[0]) => { + if (content.audioText) await speak(content.audioText) + onComplete(opt.correct) + } + + return ( +
+ + + {content.text && ( +

{content.text}

+ )} + + {content.emoji && ( + + {content.emoji} + + )} + +
+ {options.map((opt, i) => ( + handleChoose(opt)} + className="touch-target-lg rounded-2xl px-8 py-6 text-xl font-display font-bold shadow-md active:scale-95 transition-transform bg-white" + > + {opt.label} + + ))} +
+
+ ) +} diff --git a/components/exercises/ejercicio-eleccion.tsx b/components/exercises/ejercicio-eleccion.tsx new file mode 100644 index 0000000..563b532 --- /dev/null +++ b/components/exercises/ejercicio-eleccion.tsx @@ -0,0 +1,46 @@ +"use client" + +import { motion } from "framer-motion" +import { speak } from "@/lib/speech" +import { GatoMascota } from "@/components/child/gato-mascota" +import type { Exercise } from "@/curriculum/types" + +interface Props { + exercise: Exercise + onComplete: (correct: boolean) => void +} + +export default function EjercicioEleccion({ exercise, onComplete }: Props) { + const { content } = exercise + const options = content.options || [] + + const handleChoose = async (opt: (typeof options)[0]) => { + if (content.audioText) await speak(content.audioText) + onComplete(opt.correct) + } + + return ( +
+ + + {content.text && ( +

{content.text}

+ )} + +
+ {options.map((opt, i) => ( + handleChoose(opt)} + className="touch-target-lg rounded-2xl px-8 py-6 text-3xl font-display font-bold shadow-md active:scale-95 transition-transform bg-white" + > + {opt.label} + + ))} +
+
+ ) +} diff --git a/components/exercises/ejercicio-lectura.tsx b/components/exercises/ejercicio-lectura.tsx new file mode 100644 index 0000000..5483903 --- /dev/null +++ b/components/exercises/ejercicio-lectura.tsx @@ -0,0 +1,56 @@ +"use client" + +import { motion } from "framer-motion" +import { speak } from "@/lib/speech" +import { GatoMascota } from "@/components/child/gato-mascota" +import type { Exercise } from "@/curriculum/types" + +interface Props { + exercise: Exercise + onComplete: (correct: boolean) => void +} + +export default function EjercicioLectura({ exercise, onComplete }: Props) { + const { content } = exercise + const options = content.options || [] + + const handleChoose = async (opt: (typeof options)[0]) => { + if (content.audioText) await speak(content.audioText) + onComplete(opt.correct) + } + + return ( +
+ + + {content.text && ( + +

{content.text}

+
+ )} + + {content.imageEmoji && ( +
{content.imageEmoji}
+ )} + +
+ {options.map((opt, i) => ( + handleChoose(opt)} + className="touch-target-lg rounded-2xl px-8 py-5 text-lg font-display font-bold shadow-md active:scale-95 transition-transform bg-white" + > + {opt.label} + + ))} +
+
+ ) +} diff --git a/components/exercises/ejercicio-palabra.tsx b/components/exercises/ejercicio-palabra.tsx new file mode 100644 index 0000000..3dd3003 --- /dev/null +++ b/components/exercises/ejercicio-palabra.tsx @@ -0,0 +1,60 @@ +"use client" + +import { useState } from "react" +import { motion, AnimatePresence } from "framer-motion" +import { speak } from "@/lib/speech" +import { GatoMascota } from "@/components/child/gato-mascota" +import type { Exercise } from "@/curriculum/types" + +interface Props { + exercise: Exercise + onComplete: (correct: boolean) => void +} + +export default function EjercicioPalabra({ exercise, onComplete }: Props) { + const { content } = exercise + const options = content.options || [] + + const handleChoose = async (opt: (typeof options)[0]) => { + if (opt.id === "escuchar") { + if (content.audioText) await speak(content.audioText) + return + } + onComplete(opt.correct) + } + + return ( +
+ + + {content.imageEmoji && ( + + {content.imageEmoji} + + )} + +
+ {options.map((opt, i) => ( + handleChoose(opt)} + className={`touch-target-lg rounded-2xl px-8 py-6 text-xl font-display font-bold shadow-md active:scale-95 transition-transform ${ + opt.id === "escuchar" + ? "bg-accent text-accent-foreground" + : "bg-white" + }`} + > + {opt.label} + + ))} +
+
+ ) +} diff --git a/components/exercises/ejercicio-secuencia.tsx b/components/exercises/ejercicio-secuencia.tsx new file mode 100644 index 0000000..0c4a93e --- /dev/null +++ b/components/exercises/ejercicio-secuencia.tsx @@ -0,0 +1,54 @@ +"use client" + +import { useState } from "react" +import { motion, Reorder } from "framer-motion" +import { speak } from "@/lib/speech" +import { GatoMascota } from "@/components/child/gato-mascota" +import type { Exercise } from "@/curriculum/types" + +interface Props { + exercise: Exercise + onComplete: (correct: boolean) => void +} + +export default function EjercicioSecuencia({ exercise, onComplete }: Props) { + const { content } = exercise + const sequence = content.sequence || [] + + const [items, setItems] = useState(() => + [...sequence].sort(() => Math.random() - 0.5), + ) + + const handleReorder = (newOrder: string[]) => { + setItems(newOrder) + if (newOrder.every((item, i) => item === sequence[i])) { + if (content.audioText) speak(content.audioText) + onComplete(true) + } + } + + return ( +
+ + + + {items.map((item) => ( + + {item} + + ))} + + +

Arrastrá para ordenar

+
+ ) +} diff --git a/components/exercises/ejercicio-sensibilizacion.tsx b/components/exercises/ejercicio-sensibilizacion.tsx new file mode 100644 index 0000000..ce91564 --- /dev/null +++ b/components/exercises/ejercicio-sensibilizacion.tsx @@ -0,0 +1,61 @@ +"use client" + +import { motion } from "framer-motion" +import { speak } from "@/lib/speech" +import { GatoMascota } from "@/components/child/gato-mascota" +import type { Exercise } from "@/curriculum/types" + +interface Props { + exercise: Exercise + onComplete: (correct: boolean) => void +} + +export default function EjercicioSensibilizacion({ exercise, onComplete }: Props) { + const { content } = exercise + const hasOptions = content.options && content.options.length > 0 + + const handleClick = async (option: { label: string; correct: boolean }) => { + if (content.audioText) await speak(content.audioText) + onComplete(option.correct) + } + + return ( +
+ + + + {content.emoji && {content.emoji}} + + + {hasOptions && ( +
+ {content.options!.map((opt, i) => ( + handleClick(opt)} + className="touch-target-lg rounded-2xl px-8 py-6 text-xl font-display font-bold shadow-md active:scale-95 transition-transform bg-white" + > + {opt.label} + + ))} +
+ )} + + {!hasOptions && ( + + )} +
+ ) +} diff --git a/components/exercises/ejercicio-trazo.tsx b/components/exercises/ejercicio-trazo.tsx new file mode 100644 index 0000000..896ac56 --- /dev/null +++ b/components/exercises/ejercicio-trazo.tsx @@ -0,0 +1,66 @@ +"use client" + +import { motion } from "framer-motion" +import { speak } from "@/lib/speech" +import { GatoMascota } from "@/components/child/gato-mascota" +import TrazoLetra from "@/components/child/trazo-letra" +import type { Exercise } from "@/curriculum/types" + +interface Props { + exercise: Exercise + onComplete: (correct: boolean) => void +} + +export default function EjercicioTrazo({ exercise, onComplete }: Props) { + const { content } = exercise + const charToTrace = content.text && content.text.length === 1 + ? content.text.toUpperCase() + : null + + const handleAudio = async () => { + if (content.audioText) await speak(content.audioText) + } + + return ( +
+ + + {charToTrace ? ( + <> + onComplete(true)} + promptLevel={0} + /> + {content.audioText && ( + + )} + + ) : ( + <> + {content.emoji && ( + + {content.emoji} + + )} + + + )} +
+ ) +} diff --git a/components/exercises/exercise-dispatcher.tsx b/components/exercises/exercise-dispatcher.tsx new file mode 100644 index 0000000..57883fb --- /dev/null +++ b/components/exercises/exercise-dispatcher.tsx @@ -0,0 +1,34 @@ +"use client" + +import dynamic from "next/dynamic" +import type { Exercise, ExerciseType } from "@/curriculum/types" + +const EjercicioSensibilizacion = dynamic(() => import("./ejercicio-sensibilizacion"), { ssr: false }) +const EjercicioConciencia = dynamic(() => import("./ejercicio-conciencia"), { ssr: false }) +const EjercicioEleccion = dynamic(() => import("./ejercicio-eleccion"), { ssr: false }) +const EjercicioSecuencia = dynamic(() => import("./ejercicio-secuencia"), { ssr: false }) +const EjercicioPalabra = dynamic(() => import("./ejercicio-palabra"), { ssr: false }) +const EjercicioLectura = dynamic(() => import("./ejercicio-lectura"), { ssr: false }) +const EjercicioTrazo = dynamic(() => import("./ejercicio-trazo"), { ssr: false }) + +const componentMap: Record void }>> = { + sensibilizacion: EjercicioSensibilizacion, + conciencia: EjercicioConciencia, + trazo: EjercicioTrazo, + eleccion: EjercicioEleccion, + emparejar: EjercicioConciencia, + secuencia: EjercicioSecuencia, + palabra: EjercicioPalabra, + problema: EjercicioLectura, + lectura: EjercicioLectura, +} + +interface Props { + exercise: Exercise + onComplete: (correct: boolean) => void +} + +export default function ExerciseDispatcher({ exercise, onComplete }: Props) { + const Component = componentMap[exercise.type] || EjercicioSensibilizacion + return +} diff --git a/components/parent/error-patterns-chart.tsx b/components/parent/error-patterns-chart.tsx new file mode 100644 index 0000000..f178a8e --- /dev/null +++ b/components/parent/error-patterns-chart.tsx @@ -0,0 +1,68 @@ +"use client" + +const errorLabels: Record = { + "discriminacion-auditiva": "Discriminación auditiva", + "confusion-letra": "Confusión de letra", + "silaba-dificil": "Sílaba difícil", + "omision-letra": "Omisión de letra", + "inversion-letra": "Inversión de letra", +} + +const errorColors: Record = { + "discriminacion-auditiva": "bg-blue-200 text-blue-800", + "confusion-letra": "bg-red-200 text-red-800", + "silaba-dificil": "bg-yellow-200 text-yellow-800", + "omision-letra": "bg-purple-200 text-purple-800", + "inversion-letra": "bg-orange-200 text-orange-800", +} + +interface Props { + data: Record + total: number +} + +export default function ErrorPatternsChart({ data, total }: Props) { + const entries = Object.entries(data) + + if (total === 0) { + return ( +

+ Sin errores registrados. ¡Bien ahí! +

+ ) + } + + return ( +
+ {entries.map(([key, count]) => { + const pct = Math.round((count / total) * 100) + return ( +
+
+ {errorLabels[key] || key} + + {count} ({pct}%) + +
+
+
+
+
+ ) + })} +
+ ) +} diff --git a/components/parent/metric-card.tsx b/components/parent/metric-card.tsx new file mode 100644 index 0000000..0821077 --- /dev/null +++ b/components/parent/metric-card.tsx @@ -0,0 +1,26 @@ +import { cn } from "@/lib/utils" + +interface MetricCardProps { + title: string + value: string | number + subtitle?: string + variant?: "default" | "primary" | "success" | "accent" + className?: string +} + +const variants = { + default: "bg-white", + primary: "bg-primary/10 border-primary/20", + success: "bg-success/10 border-success/20", + accent: "bg-accent/10 border-accent/20", +} + +export function MetricCard({ title, value, subtitle, variant = "default", className }: MetricCardProps) { + return ( +
+

{title}

+

{value}

+ {subtitle &&

{subtitle}

} +
+ ) +} diff --git a/components/parent/milestones-list.tsx b/components/parent/milestones-list.tsx new file mode 100644 index 0000000..4ee4ed7 --- /dev/null +++ b/components/parent/milestones-list.tsx @@ -0,0 +1,40 @@ +interface MilestoneData { + skillCode: string + reachedAt: string +} + +const skillLabels: Record = { + "vocal-a": "Letra A", + "vocal-e": "Letra E", + "vocal-i": "Letra I", + "vocal-o": "Letra O", + "vocal-u": "Letra U", + "numero-1": "Número 1", + "numero-2": "Número 2", + "numero-3": "Número 3", + "numero-4": "Número 4", + "numero-5": "Número 5", +} + +export function MilestonesList({ data }: { data: MilestoneData[] }) { + if (!data || data.length === 0) { + return ( +
+ Todavía no hay logros +
+ ) + } + + return ( +
+ {data.map((m) => ( +
+ {skillLabels[m.skillCode] || m.skillCode} +
+ ))} +
+ ) +} diff --git a/components/parent/skill-mastery-chart.tsx b/components/parent/skill-mastery-chart.tsx new file mode 100644 index 0000000..c1d3ec7 --- /dev/null +++ b/components/parent/skill-mastery-chart.tsx @@ -0,0 +1,61 @@ +"use client" + +import { + RadarChart, + PolarGrid, + PolarAngleAxis, + PolarRadiusAxis, + Radar, + ResponsiveContainer, +} from "recharts" + +interface SkillData { + skillCode: string + stability: number + reps: number +} + +export function SkillMasteryChart({ data }: { data: SkillData[] }) { + if (!data || data.length === 0) { + return ( +
+ Sin skills todavía +
+ ) + } + + const chartData = data + .filter((s) => s.reps > 0) + .slice(0, 8) + .map((s) => ({ + skill: s.skillCode.replace("vocal-", "").replace("numero-", "num "), + mastery: Math.min(Math.round((s.stability / 100) * 100), 100), + })) + + if (chartData.length === 0) { + return ( +
+ Sin skills todavía +
+ ) + } + + return ( +
+ + + + + + + + +
+ ) +} diff --git a/components/parent/weekly-activity-chart.tsx b/components/parent/weekly-activity-chart.tsx new file mode 100644 index 0000000..6f7f4b9 --- /dev/null +++ b/components/parent/weekly-activity-chart.tsx @@ -0,0 +1,52 @@ +"use client" + +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + CartesianGrid, +} from "recharts" + +interface WeeklyData { + date: string + minutes: number +} + +export function WeeklyActivityChart({ data }: { data: WeeklyData[] }) { + if (!data || data.length === 0) { + return ( +
+ No hay actividad esta semana +
+ ) + } + + const daysOfWeek = ["dom", "lun", "mar", "mié", "jue", "vie", "sáb"] + const chartData = data.map((d) => { + const day = new Date(d.date).getDay() + return { + name: daysOfWeek[day], + minutos: d.minutes, + } + }) + + return ( +
+ + + + + + [`${value} min`, "Actividad"]} + /> + + + +
+ ) +} diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..51e507b --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..f000e3e --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..36496a2 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/components/ui/card.tsx b/components/ui/card.tsx new file mode 100644 index 0000000..f62edea --- /dev/null +++ b/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000..68551b9 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,22 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/components/ui/label.tsx b/components/ui/label.tsx new file mode 100644 index 0000000..5341821 --- /dev/null +++ b/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/components/ui/progress.tsx b/components/ui/progress.tsx new file mode 100644 index 0000000..5c87ea4 --- /dev/null +++ b/components/ui/progress.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)) +Progress.displayName = ProgressPrimitive.Root.displayName + +export { Progress } diff --git a/components/ui/skeleton.tsx b/components/ui/skeleton.tsx new file mode 100644 index 0000000..01b8b6d --- /dev/null +++ b/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/components/ui/tabs.tsx b/components/ui/tabs.tsx new file mode 100644 index 0000000..26eb109 --- /dev/null +++ b/components/ui/tabs.tsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/curriculum/index.ts b/curriculum/index.ts new file mode 100644 index 0000000..154fd34 --- /dev/null +++ b/curriculum/index.ts @@ -0,0 +1,46 @@ +import type { Exercise, Stage, TopicCurriculum } from "./types" + +const lecturaModules: Record = {} +const numerosModules: Record = {} + +async function loadStages( + topic: string, + modules: Record, + stageFiles: number[], +) { + for (const s of stageFiles) { + try { + const mod = await import(`./${topic}/etapa-${s}`) + modules[s] = mod.default + } catch { + // stage not yet implemented + } + } +} + +let lecturaLoaded = false +let numerosLoaded = false + +export function getAllLecturaExercises(): Exercise[] { + const stages = Object.values(getLecturaStages()) + return stages.flatMap((s) => s.exercises || []) +} + +export function getLecturaStages(): Record { + return lecturaModules as Record +} + +export function getNumerosStages(): Record { + return numerosModules as Record +} + +export async function initCurriculum() { + if (!lecturaLoaded) { + await loadStages("lectura", lecturaModules, [1, 2, 3, 4, 5, 6, 7, 8, 9]) + lecturaLoaded = true + } + if (!numerosLoaded) { + await loadStages("numeros", numerosModules, [1, 2, 3, 4, 5, 6, 7, 8]) + numerosLoaded = true + } +} diff --git a/curriculum/lectura/etapa-1.ts b/curriculum/lectura/etapa-1.ts new file mode 100644 index 0000000..a7bbf11 --- /dev/null +++ b/curriculum/lectura/etapa-1.ts @@ -0,0 +1,218 @@ +import type { Stage } from '../types' + +const stage: Stage = { + id: 1, + name: 'Sensibilización al lenguaje escrito', + description: 'Explorar sonidos del entorno, onomatopeyas, ritmo y conciencia de que el lenguaje se puede representar.', + exercises: [ + // --- Sonidos del entorno --- + { + id: 'lec1-sonido-perro', + stage: 1, + skillCode: 'sensibilizacion-sonidos', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: 'Escuchá este sonido. ¿Qué animal hace así?', + audioText: '¡El perro hace guau guau!', + options: [ + { label: '🐱 Gato', correct: false }, + { label: '🐕 Perro', correct: true }, + { label: '🐄 Vaca', correct: false }, + ], + emoji: '🐕', + }, + functionalContext: 'Identificar sonidos cotidianos del entorno.', + }, + { + id: 'lec1-sonido-gato', + stage: 1, + skillCode: 'sensibilizacion-sonidos', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: '¿Quién hace miau?', + audioText: 'El gato hace miau miau', + options: [ + { label: '🐕 Perro', correct: false }, + { label: '🐄 Vaca', correct: false }, + { label: '🐱 Gato', correct: true }, + ], + emoji: '🐱', + }, + functionalContext: 'Asociar sonidos con animales.', + }, + { + id: 'lec1-sonido-vaca', + stage: 1, + skillCode: 'sensibilizacion-sonidos', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: '¿Cuál hace muuuu?', + audioText: 'La vaca hace muuu', + options: [ + { label: '🐄 Vaca', correct: true }, + { label: '🐔 Gallina', correct: false }, + { label: '🐑 Oveja', correct: false }, + ], + emoji: '🐄', + }, + functionalContext: 'Reconocer sonidos de animales.', + }, + { + id: 'lec1-sonido-auto', + stage: 1, + skillCode: 'sensibilizacion-sonidos', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: '¿Qué hace run run?', + audioText: 'El auto hace run run', + options: [ + { label: '🚲 Bici', correct: false }, + { label: '🚗 Auto', correct: true }, + { label: '✈️ Avión', correct: false }, + ], + emoji: '🚗', + }, + functionalContext: 'Identificar sonidos de transporte.', + }, + { + id: 'lec1-sonido-lluvia', + stage: 1, + skillCode: 'sensibilizacion-sonidos', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: '¿Qué hace pío pío?', + audioText: 'El pajarito hace pío pío', + options: [ + { label: '🐟 Pez', correct: false }, + { label: '🐔 Gallina', correct: false }, + { label: '🐤 Pájaro', correct: true }, + ], + emoji: '🐤', + }, + functionalContext: 'Asociar onomatopeyas con animales.', + }, + // --- Sonidos de la naturaleza --- + { + id: 'lec1-sonido-abeja', + stage: 1, + skillCode: 'sensibilizacion-sonidos', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: '¿Quién hace zzzzz?', + audioText: 'La abeja hace zzzz', + options: [ + { label: '🐝 Abeja', correct: true }, + { label: '🦋 Mariposa', correct: false }, + { label: '🐞 Mariquita', correct: false }, + ], + emoji: '🐝', + }, + functionalContext: 'Discriminar sonidos de insectos.', + }, + { + id: 'lec1-sonido-agua', + stage: 1, + skillCode: 'sensibilizacion-sonidos', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: '¿Qué suena así: glu glu glu?', + audioText: 'El agua hace glu glu', + options: [ + { label: '💧 Agua', correct: true }, + { label: '🔥 Fuego', correct: false }, + { label: '💨 Viento', correct: false }, + ], + emoji: '💧', + }, + functionalContext: 'Reconocer sonidos del agua.', + }, + // --- Ritmo y rimas --- + { + id: 'lec1-ritmo-palmas', + stage: 1, + skillCode: 'sensibilizacion-ritmo', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: '¡Vamos a aplaudir! ¿Podés seguir el ritmo? DA-da-da, DA-da-da', + audioText: 'Vamos a aplaudir juntos', + emoji: '👏', + }, + functionalContext: 'Desarrollar conciencia rítmica.', + }, + { + id: 'lec1-cancion-infantil', + stage: 1, + skillCode: 'sensibilizacion-ritmo', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: 'Cantemos juntos: "Estrellita, ¿dónde estás?"', + audioText: 'Estrellita, ¿dónde estás? Me pregunto quién serás', + emoji: '⭐', + }, + functionalContext: 'Explorar ritmo y melodía del lenguaje.', + }, + { + id: 'lec1-silencio-ruido', + stage: 1, + skillCode: 'sensibilizacion-discriminacion', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: 'A veces hay silencio, a veces hay ruido. ¿Cuándo escuchamos silencio?', + audioText: 'El silencio es cuando no hay sonido', + options: [ + { label: '🔊 Música fuerte', correct: false }, + { label: '🤫 Todos callados', correct: true }, + { label: '📢 Gritos', correct: false }, + ], + emoji: '🤫', + }, + functionalContext: 'Discriminar presencia y ausencia de sonido.', + }, + { + id: 'lec1-eco', + stage: 1, + skillCode: 'sensibilizacion-eco', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: 'Vamos a hacer eco. Decí después de mí: PA... PA... PA', + audioText: 'PA... PA... PA... repetí después de mí', + options: [ + { label: '¡Lo hice!', correct: true }, + ], + emoji: '🔊', + }, + functionalContext: 'Tomar conciencia de la propia producción de sonidos.', + }, + { + id: 'lec1-onomatopeya-coco', + stage: 1, + skillCode: 'sensibilizacion-onomatopeya', + type: 'sensibilizacion', + prerequisites: [], + content: { + instruction: 'El reloj hace: tic-tac, tic-tac. ¿Cuál es el reloj?', + audioText: 'El reloj hace tic tac', + options: [ + { label: '🕐 Reloj', correct: true }, + { label: '📞 Teléfono', correct: false }, + { label: '🚪 Puerta', correct: false }, + ], + emoji: '🕐', + }, + functionalContext: 'Asociar objetos a sus sonidos.', + }, + ], +} + +export default stage diff --git a/curriculum/lectura/etapa-2.ts b/curriculum/lectura/etapa-2.ts new file mode 100644 index 0000000..6f704a4 --- /dev/null +++ b/curriculum/lectura/etapa-2.ts @@ -0,0 +1,345 @@ +import type { Stage } from '../types' + +const stage: Stage = { + id: 2, + name: 'Conciencia fonológica', + description: 'Rimas, conteo de sílabas, identificar sonidos iniciales y segmentación de palabras.', + exercises: [ + // --- Rimas --- + { + id: 'lec2-rima-sol', + stage: 2, + skillCode: 'conciencia-rimas', + type: 'conciencia', + prerequisites: ['lec1-sonido-perro', 'lec1-ritmo-palmas'], + content: { + instruction: '¿Cuál palabra rima con SOL?', + text: 'SOL', + audioText: 'Sol rima con...', + options: [ + { label: 'SOL', correct: false }, + { label: 'CARACOL', correct: true }, + { label: 'LUNA', correct: false }, + ], + emoji: '☀️', + }, + functionalContext: 'Identificar palabras que terminan igual.', + errorType: 'discriminacion-auditiva', + }, + { + id: 'lec2-rima-pan', + stage: 2, + skillCode: 'conciencia-rimas', + type: 'conciencia', + prerequisites: ['lec1-sonido-perro'], + content: { + instruction: '¿Qué rima con PAN?', + text: 'PAN', + audioText: 'Pan rima con...', + options: [ + { label: 'MANO', correct: false }, + { label: 'PAN', correct: false }, + { label: 'CHAPAN', correct: true }, + ], + emoji: '🍞', + }, + functionalContext: 'Reconocer terminaciones comunes.', + errorType: 'discriminacion-auditiva', + }, + { + id: 'lec2-rima-flor', + stage: 2, + skillCode: 'conciencia-rimas', + type: 'conciencia', + prerequisites: ['lec1-sonido-perro'], + content: { + instruction: 'Buscá la palabra que rima con FLOR', + text: 'FLOR', + audioText: 'Flor rima con...', + options: [ + { label: 'COLOR', correct: true }, + { label: 'HOJA', correct: false }, + { label: 'FLOR', correct: false }, + ], + emoji: '🌸', + }, + functionalContext: 'Identificar rimas en palabras conocidas.', + errorType: 'discriminacion-auditiva', + }, + { + id: 'lec2-rima-miel', + stage: 2, + skillCode: 'conciencia-rimas', + type: 'conciencia', + prerequisites: ['lec1-sonido-abeja'], + content: { + instruction: '¿Cuál rima con MIEL?', + text: 'MIEL', + audioText: 'Miel rima con...', + options: [ + { label: 'MIEL', correct: false }, + { label: 'PAN', correct: false }, + { label: 'PINCEL', correct: true }, + ], + emoji: '🍯', + }, + functionalContext: 'Reconocer patrones sonoros finales.', + errorType: 'discriminacion-auditiva', + }, + // --- Conteo de sílabas --- + { + id: 'lec2-silabas-2', + stage: 2, + skillCode: 'conciencia-silabas', + type: 'conciencia', + prerequisites: ['lec1-eco'], + content: { + instruction: 'Vamos a contar: ¿cuántas palmadas tiene MANO? (MA-NO)', + text: 'MANO', + audioText: 'MA-NO, dos palmadas', + options: [ + { label: '👏 Una', correct: false }, + { label: '👏👏 Dos', correct: true }, + { label: '👏👏👏 Tres', correct: false }, + ], + emoji: '✋', + }, + functionalContext: 'Desarrollar conciencia de segmentación silábica.', + errorType: 'discriminacion-auditiva', + }, + { + id: 'lec2-silabas-3', + stage: 2, + skillCode: 'conciencia-silabas', + type: 'conciencia', + prerequisites: ['lec2-silabas-2'], + content: { + instruction: '¿Cuántas partes tiene VENTANA? (VEN-TA-NA)', + text: 'VENTANA', + audioText: 'VEN-TA-NA, tres partes', + options: [ + { label: '👏 Dos', correct: false }, + { label: '👏👏👏 Tres', correct: true }, + { label: '👏👏👏👏 Cuatro', correct: false }, + ], + emoji: '🪟', + }, + functionalContext: 'Segmentar palabras en sílabas.', + errorType: 'discriminacion-auditiva', + }, + { + id: 'lec2-silabas-1', + stage: 2, + skillCode: 'conciencia-silabas', + type: 'conciencia', + prerequisites: ['lec2-silabas-2'], + content: { + instruction: '¿Cuántas palmadas para SOL? (SOL)', + text: 'SOL', + audioText: 'SOL, una sola palmada', + options: [ + { label: '👏 Una', correct: true }, + { label: '👏👏 Dos', correct: false }, + { label: '👏👏👏 Tres', correct: false }, + ], + emoji: '☀️', + }, + functionalContext: 'Reconocer palabras monosílabas.', + errorType: 'discriminacion-auditiva', + }, + { + id: 'lec2-silabas-4', + stage: 2, + skillCode: 'conciencia-silabas', + type: 'conciencia', + prerequisites: ['lec2-silabas-3'], + content: { + instruction: '¿Cuántas palmadas para MARIPOSA? (MA-RI-PO-SA)', + text: 'MARIPOSA', + audioText: 'MA-RI-PO-SA, cuatro partes', + options: [ + { label: '👏 Dos', correct: false }, + { label: '👏👏👏 Tres', correct: false }, + { label: '👏👏👏👏 Cuatro', correct: true }, + ], + emoji: '🦋', + }, + functionalContext: 'Segmentar palabras de 4 sílabas.', + errorType: 'discriminacion-auditiva', + }, + // --- Sílaba inicial --- + { + id: 'lec2-inicial-ma', + stage: 2, + skillCode: 'conciencia-inicial', + type: 'conciencia', + prerequisites: ['lec2-silabas-2'], + content: { + instruction: '¿Cuál empieza como MAMÁ? (MA-MÁ, MA-NO)', + text: 'MAMÁ → MA...', + audioText: 'Mamá empieza con MA', + options: [ + { label: 'MANO', correct: true }, + { label: 'PATO', correct: false }, + { label: 'SAPO', correct: false }, + ], + emoji: '👩', + }, + functionalContext: 'Identificar el sonido inicial de las palabras.', + errorType: 'discriminacion-auditiva', + }, + { + id: 'lec2-inicial-pa', + stage: 2, + skillCode: 'conciencia-inicial', + type: 'conciencia', + prerequisites: ['lec2-inicial-ma'], + content: { + instruction: '¿Cuál empieza como PAPÁ? (PA-PÁ, PA-TO)', + text: 'PAPÁ → PA...', + audioText: 'Papá empieza con PA', + options: [ + { label: 'PERRO', correct: false }, + { label: 'PATO', correct: true }, + { label: 'CASA', correct: false }, + ], + emoji: '👨', + }, + functionalContext: 'Generalizar identificación de sílaba inicial.', + errorType: 'discriminacion-auditiva', + }, + { + id: 'lec2-inicial-ta', + stage: 2, + skillCode: 'conciencia-inicial', + type: 'conciencia', + prerequisites: ['lec2-inicial-pa'], + content: { + instruction: 'Buscá la que empieza como TAZA (TA-ZA, TA-BLA)', + text: 'TAZA → TA...', + audioText: 'Taza empieza con TA', + options: [ + { label: 'MESA', correct: false }, + { label: 'TAMBOR', correct: true }, + { label: 'LÁPIZ', correct: false }, + ], + emoji: '☕', + }, + functionalContext: 'Reconocer sílaba TA al inicio.', + errorType: 'discriminacion-auditiva', + }, + { + id: 'lec2-inicial-sa', + stage: 2, + skillCode: 'conciencia-inicial', + type: 'conciencia', + prerequisites: ['lec2-inicial-ta'], + content: { + instruction: '¿Cuál empieza como SAPO? (SA-PO, SA-LTA)', + text: 'SAPO → SA...', + audioText: 'Sapo empieza con SA', + options: [ + { label: 'SAPO', correct: false }, + { label: 'SALTA', correct: true }, + { label: 'RANA', correct: false }, + ], + emoji: '🐸', + }, + functionalContext: 'Identificar sonido SA al inicio.', + errorType: 'discriminacion-auditiva', + }, + // --- Segmentación --- + { + id: 'lec2-segmentar-casa', + stage: 2, + skillCode: 'conciencia-segmentacion', + type: 'conciencia', + prerequisites: ['lec2-silabas-2', 'lec2-inicial-ma'], + content: { + instruction: 'Separala en partes: CA-SA. ¿Cuál tiene dos partes?', + text: 'CA-SA', + audioText: 'CA-SA', + options: [ + { label: 'SOL', correct: false }, + { label: 'CASA', correct: true }, + { label: 'MARIPOSA', correct: false }, + ], + emoji: '🏠', + }, + functionalContext: 'Segmentar palabras en sílabas.', + errorType: 'omision-letra', + }, + { + id: 'lec2-segmentar-mesa', + stage: 2, + skillCode: 'conciencia-segmentacion', + type: 'conciencia', + prerequisites: ['lec2-segmentar-casa'], + content: { + instruction: 'ME-SA. ¿Cuál es?', + text: 'ME-SA', + audioText: 'ME-SA', + options: [ + { label: 'MESA', correct: true }, + { label: 'MELÓN', correct: false }, + { label: 'SAPO', correct: false }, + ], + emoji: '🪑', + }, + functionalContext: 'Unir segmentos para formar palabras.', + errorType: 'omision-letra', + }, + { + id: 'lec2-segmentar-pato', + stage: 2, + skillCode: 'conciencia-segmentacion', + type: 'conciencia', + prerequisites: ['lec2-segmentar-mesa'], + content: { + instruction: 'PA-TO. ¿Qué palabra formamos?', + text: 'PA-TO', + audioText: 'PA-TO', + options: [ + { label: 'PATO', correct: true }, + { label: 'PERRO', correct: false }, + { label: 'GATO', correct: false }, + ], + emoji: '🦆', + }, + functionalContext: 'Sintetizar sílabas para formar palabras.', + errorType: 'omision-letra', + }, + { + id: 'lec2-ordenar-silabas', + stage: 2, + skillCode: 'conciencia-orden', + type: 'secuencia', + prerequisites: ['lec2-segmentar-pato'], + content: { + instruction: 'Ordená las sílabas para formar una palabra', + audioText: 'Ordená las sílabas', + sequence: ['TO', 'PA'], + emoji: '🧩', + }, + functionalContext: 'Desarrollar habilidad de ordenamiento fonológico.', + errorType: 'inversion-letra', + }, + { + id: 'lec2-ordenar-silabas-2', + stage: 2, + skillCode: 'conciencia-orden', + type: 'secuencia', + prerequisites: ['lec2-ordenar-silabas'], + content: { + instruction: 'Armá la palabra', + audioText: 'Ordená estas sílabas', + sequence: ['SA', 'CA'], + emoji: '🧩', + }, + functionalContext: 'Ordenar sílabas para formar palabras.', + errorType: 'inversion-letra', + }, + ], +} + +export default stage diff --git a/curriculum/lectura/etapa-3.ts b/curriculum/lectura/etapa-3.ts new file mode 100644 index 0000000..4bb65bb --- /dev/null +++ b/curriculum/lectura/etapa-3.ts @@ -0,0 +1,269 @@ +import type { Stage } from '../types' + +function vocalChoice(vocal: string, correctLabel: string, distractors: string[], emoji: string, instruction: string, audioText?: string) { + const options = [ + { label: correctLabel, correct: true, id: vocal }, + ...distractors.map((d) => ({ label: d, correct: false, id: d })), + ].sort(() => Math.random() - 0.5) + + return { + stage: 3, + skillCode: `vocal-${vocal}`, + type: 'eleccion' as const, + prerequisites: getVocalPrereqs(vocal), + content: { + instruction, + text: correctLabel, + options, + audioText: audioText || `${correctLabel}`, + emoji, + color: getVocalColor(vocal), + }, + functionalContext: `Identificar la vocal ${vocal.toUpperCase()} en diferentes contextos.`, + errorType: 'confusion-letra' as const, + } +} + +function getVocalColor(vocal: string): string { + const colors: Record = { a: '#FF6B6B', o: '#4ECDC4', i: '#45B7D1', e: '#96CEB4', u: '#DDA0DD' } + return colors[vocal] || '#FF6B6B' +} + +function getVocalPrereqs(vocal: string): string[] { + const order = ['a', 'o', 'i', 'e', 'u'] + const idx = order.indexOf(vocal) + if (idx === 0) return ['lec2-segmentar-pato'] + return [`vocal-${order[idx - 1]}-completa`] +} + +const gestos: Record = { + a: { gesto: 'Abrí la mano como una boca grande', palabra: 'ÁRBOL', fonema: 'AAA', emoji: '🌳' }, + o: { gesto: 'Redondeá la mano como una O', palabra: 'OJO', fonema: 'OOO', emoji: '👁️' }, + i: { gesto: 'Señalá el cielo con el dedo', palabra: 'INDIO', fonema: 'III', emoji: '🪶' }, + e: { gesto: 'Estirá la mano como una trompa de elefante', palabra: 'ELEFANTE', fonema: 'EEE', emoji: '🐘' }, + u: { gesto: 'Señalá el suelo con la mano', palabra: 'UNO', fonema: 'UUU', emoji: '1️⃣' }, +} + +function buildVocalExercises(vocal: string, index: number): any[] { + const g = gestos[vocal] + const exs: any[] = [] + + // Ejercicio de gesto (sensibilizacion) + exs.push({ + id: `lec3-${vocal}-gesto`, + stage: 3, + skillCode: `vocal-${vocal}`, + type: 'sensibilizacion', + prerequisites: getVocalPrereqs(vocal), + content: { + instruction: `Hacé así para la ${vocal.toUpperCase()}: ${g.gesto}`, + audioText: `Esta es la letra ${vocal.toUpperCase()}. ${g.palabra}. ${g.fonema}`, + emoji: g.emoji, + color: getVocalColor(vocal), + }, + functionalContext: `Aprender el gesto Borel-Maisonny para la vocal ${vocal.toUpperCase()}.`, + }) + + // Reconocer la vocal entre otras + exs.push(vocalChoice( + vocal, vocal.toUpperCase(), + ['A', 'O', 'I', 'E', 'U'].filter((v) => v !== vocal.toUpperCase()), + g.emoji, + `Señalá la letra ${vocal.toUpperCase()}`, + )) + + // ¿Con qué vocal empieza? + exs.push({ + id: `lec3-${vocal}-inicial`, + stage: 3, + skillCode: `vocal-${vocal}`, + type: 'eleccion', + prerequisites: [`lec3-${vocal}-gesto`], + content: { + instruction: `¿Con qué vocal empieza ${g.palabra}?`, + text: g.palabra, + audioText: `${g.palabra} empieza con ${vocal.toUpperCase()}`, + options: [ + { label: vocal.toUpperCase(), correct: true, id: vocal }, + ...['A', 'O', 'I', 'E', 'U'].filter((v) => v !== vocal.toUpperCase()).slice(0, 2).map((v) => ({ label: v, correct: false, id: v })), + ], + emoji: g.emoji, + }, + functionalContext: `Reconocer la vocal inicial en palabras.`, + errorType: 'discriminacion-auditiva', + }) + + // Emparejar imagen con vocal + exs.push({ + id: `lec3-${vocal}-emparejar`, + stage: 3, + skillCode: `vocal-${vocal}`, + type: 'conciencia', + prerequisites: [`lec3-${vocal}-inicial`], + content: { + instruction: `¿Cuál empieza con ${vocal.toUpperCase()}?`, + audioText: `¿Qué empieza con ${vocal.toUpperCase()}?`, + options: [ + { label: g.emoji, correct: true, id: vocal }, + ...Object.entries(gestos) + .filter(([k]) => k !== vocal) + .slice(0, 2) + .map(([k, v]) => ({ label: v.emoji, correct: false, id: k })), + ], + }, + functionalContext: `Asociar la vocal con imágenes que empiezan con ella.`, + errorType: 'confusion-letra', + }) + + // Discriminación auditiva + exs.push({ + id: `lec3-${vocal}-auditiva`, + stage: 3, + skillCode: `vocal-${vocal}`, + type: 'conciencia', + prerequisites: [`lec3-${vocal}-emparejar`], + content: { + instruction: `Escuchá bien. ¿Cuál suena como ${vocal.toUpperCase()}?`, + audioText: `${g.fonema}... ${g.fonema}... como ${g.palabra}`, + options: [ + { label: vocal.toUpperCase(), correct: true, id: vocal }, + ...['A', 'O', 'I', 'E', 'U'].filter((v) => v !== vocal.toUpperCase()).slice(0, 2).map((v) => ({ label: v, correct: false, id: v })), + ], + emoji: '👂', + }, + functionalContext: `Discriminar auditivamente la vocal ${vocal.toUpperCase()}.`, + errorType: 'discriminacion-auditiva', + }) + + // Escribir con el dedo (trazo) + exs.push({ + id: `lec3-${vocal}-trazo`, + stage: 3, + skillCode: `vocal-${vocal}`, + type: 'trazo', + prerequisites: [`lec3-${vocal}-auditiva`], + content: { + instruction: `Trazá la letra ${vocal.toUpperCase()} con tu dedo`, + text: vocal.toUpperCase(), + audioText: `Trazá la ${vocal.toUpperCase()}`, + color: getVocalColor(vocal), + emoji: g.emoji, + }, + functionalContext: `Desarrollar memoria motriz de la vocal ${vocal.toUpperCase()}.`, + errorType: 'confusion-letra', + }) + + // Repaso final + exs.push({ + id: `lec3-${vocal}-completa`, + stage: 3, + skillCode: `vocal-${vocal}`, + type: 'eleccion', + prerequisites: [`lec3-${vocal}-trazo`], + content: { + instruction: `¡Repaso! Señalá la letra ${vocal.toUpperCase()}`, + audioText: `Buscá la ${vocal.toUpperCase()}`, + options: [ + { label: vocal.toUpperCase(), correct: true, id: vocal }, + ...['A', 'O', 'I', 'E', 'U'].filter((v) => v !== vocal.toUpperCase()).map((v) => ({ label: v, correct: false, id: v })), + ], + emoji: g.emoji, + color: getVocalColor(vocal), + }, + functionalContext: `Consolidar reconocimiento de la vocal ${vocal.toUpperCase()}.`, + errorType: 'confusion-letra', + }) + + return exs +} + +// Additional cross-vowel discrimination exercises +const mixedExercises: any[] = [ + { + id: 'lec3-mix-ae', + stage: 3, + skillCode: 'vocal-discriminacion', + type: 'eleccion', + prerequisites: ['lec3-a-completa', 'lec3-e-completa'], + content: { + instruction: '¿Cuál es la A y cuál es la E? Señalá la A', + options: [ + { label: 'A', correct: true, id: 'a' }, + { label: 'E', correct: false, id: 'e' }, + { label: 'O', correct: false, id: 'o' }, + ], + emoji: '🔤', + }, + functionalContext: 'Discriminar entre A y E.', + errorType: 'confusion-letra', + }, + { + id: 'lec3-mix-iu', + stage: 3, + skillCode: 'vocal-discriminacion', + type: 'eleccion', + prerequisites: ['lec3-i-completa', 'lec3-u-completa'], + content: { + instruction: '¿Cuál es la I y cuál es la U? Señalá la I', + options: [ + { label: 'I', correct: true, id: 'i' }, + { label: 'U', correct: false, id: 'u' }, + { label: 'A', correct: false, id: 'a' }, + ], + emoji: '🔤', + }, + functionalContext: 'Discriminar entre I y U.', + errorType: 'confusion-letra', + }, + { + id: 'lec3-mix-ao', + stage: 3, + skillCode: 'vocal-discriminacion', + type: 'eleccion', + prerequisites: ['lec3-a-completa', 'lec3-o-completa'], + content: { + instruction: 'Señalá la letra O', + options: [ + { label: 'A', correct: false, id: 'a' }, + { label: 'O', correct: true, id: 'o' }, + { label: 'U', correct: false, id: 'u' }, + ], + emoji: '🔤', + }, + functionalContext: 'Discriminar entre A y O.', + errorType: 'confusion-letra', + }, + { + id: 'lec3-mix-ei', + stage: 3, + skillCode: 'vocal-discriminacion', + type: 'eleccion', + prerequisites: ['lec3-e-completa', 'lec3-i-completa'], + content: { + instruction: 'Señalá la E', + options: [ + { label: 'I', correct: false, id: 'i' }, + { label: 'E', correct: true, id: 'e' }, + { label: 'U', correct: false, id: 'u' }, + ], + emoji: '🔤', + }, + functionalContext: 'Discriminar entre E e I.', + errorType: 'confusion-letra', + }, +] + +const exercises: any[] = [] +for (const [i, vocal] of ['a', 'o', 'i', 'e', 'u'].entries()) { + exercises.push(...buildVocalExercises(vocal, i)) +} +exercises.push(...mixedExercises) + +const stage: Stage = { + id: 3, + name: 'Vocales A-O-I-E-U', + description: 'Aprender las 5 vocales con gestos Borel-Maisonny, discriminación auditiva y visual, trazo y reconocimiento.', + exercises, +} + +export default stage diff --git a/curriculum/lectura/etapa-4.ts b/curriculum/lectura/etapa-4.ts new file mode 100644 index 0000000..f6a676a --- /dev/null +++ b/curriculum/lectura/etapa-4.ts @@ -0,0 +1,235 @@ +import type { Stage } from '../types' + +type ConInfo = { + letra: string + palabra: string + emoji: string + silabas: string[] + gesto: string +} + +const consonantes: ConInfo[] = [ + { letra: 'M', palabra: 'MAMÁ', emoji: '👩', silabas: ['MA', 'ME', 'MI', 'MO', 'MU'], gesto: 'Manos en la boca como mmm' }, + { letra: 'P', palabra: 'PAPÁ', emoji: '👨', silabas: ['PA', 'PE', 'PI', 'PO', 'PU'], gesto: 'Manos que abren y cierran como pompa' }, + { letra: 'L', palabra: 'LUNA', emoji: '🌙', silabas: ['LA', 'LE', 'LI', 'LO', 'LU'], gesto: 'Lengua arriba como lámpara' }, + { letra: 'S', palabra: 'SOL', emoji: '☀️', silabas: ['SA', 'SE', 'SI', 'SO', 'SU'], gesto: 'Dedos como serpiente sss' }, + { letra: 'T', palabra: 'TASA', emoji: '☕', silabas: ['TA', 'TE', 'TI', 'TO', 'TU'], gesto: 'Golpecito con la mano' }, + { letra: 'N', palabra: 'NIDO', emoji: '🪺', silabas: ['NA', 'NE', 'NI', 'NO', 'NU'], gesto: 'Nariz como nido' }, + { letra: 'D', palabra: 'DEDO', emoji: '👉', silabas: ['DA', 'DE', 'DI', 'DO', 'DU'], gesto: 'Dedo señalando' }, + { letra: 'R', palabra: 'RATÓN', emoji: '🐭', silabas: ['RA', 'RE', 'RI', 'RO', 'RU'], gesto: 'Lengua que vibra como rrr' }, +] + +const exercises: any[] = [] + +for (const con of consonantes) { + // 1. Presentación con gesto + exercises.push({ + id: `lec4-${con.letra.toLowerCase()}-gesto`, + stage: 4, + skillCode: `consonante-${con.letra.toLowerCase()}`, + type: 'sensibilizacion', + prerequisites: con.letra === 'M' ? ['lec3-u-completa'] : [`lec4-${consonantes[consonantes.indexOf(con) - 1].letra.toLowerCase()}-completa`], + content: { + instruction: `Hacé así para la ${con.letra}: ${con.gesto}`, + audioText: `Esta es la letra ${con.letra}. ${con.palabra}. ${con.silabas[0].toLowerCase()}`, + emoji: con.emoji, + }, + functionalContext: `Conocer el gesto asociado a la consonante ${con.letra}.`, + }) + + // 2. Reconocer la consonante + exercises.push({ + id: `lec4-${con.letra.toLowerCase()}-reconocer`, + stage: 4, + skillCode: `consonante-${con.letra.toLowerCase()}`, + type: 'eleccion', + prerequisites: [`lec4-${con.letra.toLowerCase()}-gesto`], + content: { + instruction: `Señalá la letra ${con.letra}`, + text: `Buscá la ${con.letra}`, + audioText: `¿Dónde está la ${con.letra}?`, + options: [ + { label: con.letra, correct: true, id: con.letra.toLowerCase() }, + ...consonantes.filter(c => c.letra !== con.letra).slice(0, 3).map(c => ({ label: c.letra, correct: false, id: c.letra.toLowerCase() })), + ], + emoji: con.emoji, + }, + functionalContext: `Reconocer visualmente la consonante ${con.letra}.`, + errorType: 'confusion-letra', + }) + + // 3. Discriminación auditiva de la sílaba + exercises.push({ + id: `lec4-${con.letra.toLowerCase()}-auditiva`, + stage: 4, + skillCode: `consonante-${con.letra.toLowerCase()}`, + type: 'conciencia', + prerequisites: [`lec4-${con.letra.toLowerCase()}-reconocer`], + content: { + instruction: `Escuchá. ¿Cuál suena como ${con.silabas[0]}?`, + audioText: `${con.silabas[0]}... ${con.silabas[0]}... como ${con.palabra}`, + options: [ + { label: con.silabas[0], correct: true, id: con.silabas[0].toLowerCase() }, + ...consonantes.filter(c => c.letra !== con.letra).slice(0, 2).map(c => ({ label: c.silabas[0], correct: false, id: c.silabas[0].toLowerCase() })), + ], + emoji: '👂', + }, + functionalContext: `Discriminar auditivamente la sílaba ${con.silabas[0]}.`, + errorType: 'discriminacion-auditiva', + }) + + // 4. Emparejar sílaba con vocal + for (let vi = 1; vi < con.silabas.length; vi++) { + const sil = con.silabas[vi] + const vocal = sil[1] + const vocales = ['A', 'E', 'I', 'O', 'U'].filter(v => v !== vocal) + exercises.push({ + id: `lec4-${con.letra.toLowerCase()}-silaba-${sil.toLowerCase()}`, + stage: 4, + skillCode: `consonante-${con.letra.toLowerCase()}-silabas`, + type: 'eleccion', + prerequisites: [`lec4-${con.letra.toLowerCase()}-auditiva`, `lec4-${con.letra.toLowerCase()}-silaba-${con.silabas[vi - 1].toLowerCase()}`], + content: { + instruction: `¿Cuál es ${sil}?`, + text: sil, + audioText: `${sil}... como ${con.palabra.toLowerCase()}`, + options: [ + { label: sil, correct: true, id: sil.toLowerCase() }, + { label: `${con.letra}${vocales[0]}`, correct: false, id: `${con.letra}${vocales[0]}`.toLowerCase() }, + { label: `${con.letra}${vocales[1]}`, correct: false, id: `${con.letra}${vocales[1]}`.toLowerCase() }, + ], + emoji: con.emoji, + }, + functionalContext: `Leer la sílaba ${sil}.`, + errorType: 'confusion-letra', + }) + } + + // 5. Palabra con la consonante + exercises.push({ + id: `lec4-${con.letra.toLowerCase()}-palabra`, + stage: 4, + skillCode: `consonante-${con.letra.toLowerCase()}`, + type: 'palabra', + prerequisites: [`lec4-${con.letra.toLowerCase()}-silaba-${con.silabas[con.silabas.length - 1].toLowerCase()}`], + content: { + instruction: `¿Cuál palabra empieza con ${con.letra}?`, + audioText: `${con.palabra} empieza con ${con.letra}`, + imageEmoji: con.emoji, + options: [ + { label: con.palabra, correct: true, id: con.palabra.toLowerCase() }, + { label: 'CASA', correct: false, id: 'casa' }, + { label: 'GATO', correct: false, id: 'gato' }, + { label: 'Escuchar 👂', correct: false, id: 'escuchar' }, + ], + }, + functionalContext: `Asociar la consonante ${con.letra} con una palabra significativa.`, + errorType: 'discriminacion-auditiva', + }) + + // 6. Final: repaso completo + exercises.push({ + id: `lec4-${con.letra.toLowerCase()}-completa`, + stage: 4, + skillCode: `consonante-${con.letra.toLowerCase()}`, + type: 'eleccion', + prerequisites: [`lec4-${con.letra.toLowerCase()}-palabra`], + content: { + instruction: `¡Repaso! Señalá la letra ${con.letra}`, + audioText: `Buscá la ${con.letra}`, + options: [ + { label: con.letra, correct: true, id: con.letra.toLowerCase() }, + ...consonantes.filter(c => c.letra !== con.letra).slice(0, 3).map(c => ({ label: c.letra, correct: false, id: c.letra.toLowerCase() })), + ], + emoji: con.emoji, + }, + functionalContext: `Consolidar reconocimiento de la consonante ${con.letra}.`, + errorType: 'confusion-letra', + }) +} + +// Cross-consonant discrimination exercises +exercises.push({ + id: 'lec4-mix-mp', + stage: 4, + skillCode: 'consonante-discriminacion', + type: 'eleccion', + prerequisites: ['lec4-m-completa', 'lec4-p-completa'], + content: { + instruction: 'Señalá la M', + options: [ + { label: 'M', correct: true, id: 'm' }, + { label: 'P', correct: false, id: 'p' }, + { label: 'L', correct: false, id: 'l' }, + ], + emoji: '🔤', + }, + functionalContext: 'Discriminar entre M y P.', + errorType: 'confusion-letra', +}) + +exercises.push({ + id: 'lec4-mix-st', + stage: 4, + skillCode: 'consonante-discriminacion', + type: 'eleccion', + prerequisites: ['lec4-s-completa', 'lec4-t-completa'], + content: { + instruction: 'Señalá la T', + options: [ + { label: 'S', correct: false, id: 's' }, + { label: 'T', correct: true, id: 't' }, + { label: 'N', correct: false, id: 'n' }, + ], + emoji: '🔤', + }, + functionalContext: 'Discriminar entre S y T.', + errorType: 'confusion-letra', +}) + +exercises.push({ + id: 'lec4-mix-ld', + stage: 4, + skillCode: 'consonante-discriminacion', + type: 'eleccion', + prerequisites: ['lec4-l-completa', 'lec4-d-completa'], + content: { + instruction: 'Señalá la D', + options: [ + { label: 'L', correct: false, id: 'l' }, + { label: 'D', correct: true, id: 'd' }, + { label: 'R', correct: false, id: 'r' }, + ], + emoji: '🔤', + }, + functionalContext: 'Discriminar entre L y D.', + errorType: 'confusion-letra', +}) + +exercises.push({ + id: 'lec4-mix-nr', + stage: 4, + skillCode: 'consonante-discriminacion', + type: 'eleccion', + prerequisites: ['lec4-n-completa', 'lec4-r-completa'], + content: { + instruction: 'Señalá la R', + options: [ + { label: 'N', correct: false, id: 'n' }, + { label: 'R', correct: true, id: 'r' }, + { label: 'M', correct: false, id: 'm' }, + ], + emoji: '🔤', + }, + functionalContext: 'Discriminar entre N y R.', + errorType: 'confusion-letra', +}) + +const stage: Stage = { + id: 4, + name: 'Consonantes M-P-L-S-T-N-D-R', + description: 'Aprender las consonantes M, P, L, S, T, N, D, R con gestos, sílabas, y palabras significativas.', + exercises, +} + +export default stage diff --git a/curriculum/lectura/etapa-5.ts b/curriculum/lectura/etapa-5.ts new file mode 100644 index 0000000..fb7f5eb --- /dev/null +++ b/curriculum/lectura/etapa-5.ts @@ -0,0 +1,293 @@ +import type { Stage, Exercise } from '../types' + +type Info = { + letra: string + palabra: string + emoji: string + gesto: string + silaba: string + palabraLower: string +} + +const moduleA: Info[] = [ + { letra: 'A', palabra: 'ÁRBOL', emoji: '🌳', gesto: 'Abrí la mano como una boca grande', silaba: 'a', palabraLower: 'árbol' }, + { letra: 'E', palabra: 'ELEFANTE', emoji: '🐘', gesto: 'Estirá la mano como una trompa', silaba: 'e', palabraLower: 'elefante' }, + { letra: 'I', palabra: 'INDIO', emoji: '🪶', gesto: 'Señalá el cielo con el dedo', silaba: 'i', palabraLower: 'indio' }, + { letra: 'O', palabra: 'OJO', emoji: '👁️', gesto: 'Redondeá la mano como una O', silaba: 'o', palabraLower: 'ojo' }, + { letra: 'U', palabra: 'UNO', emoji: '1️⃣', gesto: 'Señalá el suelo con la mano', silaba: 'u', palabraLower: 'uno' }, +] + +const moduleB: Info[] = [ + { letra: 'M', palabra: 'MAMÁ', emoji: '👩', gesto: 'Manos en la boca como mmm', silaba: 'ma', palabraLower: 'mamá' }, + { letra: 'P', palabra: 'PAPÁ', emoji: '👨', gesto: 'Manos que abren y cierran como pompa', silaba: 'pa', palabraLower: 'papá' }, + { letra: 'L', palabra: 'LUNA', emoji: '🌙', gesto: 'Lengua arriba como lámpara', silaba: 'la', palabraLower: 'luna' }, + { letra: 'S', palabra: 'SOL', emoji: '☀️', gesto: 'Dedos como serpiente sss', silaba: 'sa', palabraLower: 'sol' }, +] + +const moduleC: Info[] = [ + { letra: 'T', palabra: 'TASA', emoji: '☕', gesto: 'Golpecito con la mano', silaba: 'ta', palabraLower: 'tasa' }, + { letra: 'N', palabra: 'NIDO', emoji: '🪺', gesto: 'Nariz como nido', silaba: 'na', palabraLower: 'nido' }, + { letra: 'D', palabra: 'DEDO', emoji: '👉', gesto: 'Dedo señalando', silaba: 'da', palabraLower: 'dedo' }, + { letra: 'R', palabra: 'RATÓN', emoji: '🐭', gesto: 'Lengua que vibra como rrr', silaba: 'ra', palabraLower: 'ratón' }, +] + +const allModules = [moduleA, moduleB, moduleC] + +function getDistractors(pool: Info[], current: Info, count: number): Info[] { + return pool.filter((x) => x.letra !== current.letra).slice(0, count) +} + +function getPoolIndex(info: Info): number { + for (let i = 0; i < allModules.length; i++) { + if (allModules[i].find((x) => x.letra === info.letra)) return i + } + return 0 +} + +function buildExercises(): Exercise[] { + const all: Exercise[] = [] + let prevId: string | null = 'lec4-r-completa' + + for (const pool of allModules) { + for (const info of pool) { + const MIN = info.letra.toLowerCase() + const dist = getDistractors(pool, info, 3) + + // 1. Descubrimiento del par + all.push({ + id: `lec5-${MIN}-descubre`, + stage: 5, + skillCode: `puente-${MIN}`, + type: 'sensibilizacion', + prerequisites: prevId ? [prevId] : [], + content: { + instruction: `${info.letra} y ${MIN} suenan igual`, + audioText: `${info.letra} grande y ${MIN} chica suenan igual: ${gestoKey(info.gesto)}`, + emoji: info.emoji, + }, + functionalContext: `Descubrir que ${info.letra} y ${MIN} son la misma letra.`, + }) + prevId = `lec5-${MIN}-descubre` + + // 2. Práctica emparejada: Tocá Mm + const pairOptions = [ + { label: `${info.letra}${MIN}`, correct: true, id: MIN }, + ...dist.map((d) => ({ + label: `${d.letra}${d.letra.toLowerCase()}`, + correct: false, + id: d.letra.toLowerCase(), + })), + ].sort(() => Math.random() - 0.5) + + all.push({ + id: `lec5-${MIN}-pareja`, + stage: 5, + skillCode: `puente-${MIN}`, + type: 'eleccion', + prerequisites: [prevId], + content: { + instruction: `Tocá la ${info.letra}${MIN}`, + audioText: `¿Dónde está ${info.letra}${MIN}?`, + options: pairOptions, + emoji: info.emoji, + }, + functionalContext: `Asociar el par ${info.letra}${MIN}.`, + errorType: 'confusion-letra', + }) + prevId = `lec5-${MIN}-pareja` + + // 3. Buscar mayúscula entre minúsculas: Tocá la M + const mayusOptions = [ + { label: info.letra, correct: true, id: MIN }, + ...dist.map((d) => ({ + label: d.letra.toLowerCase(), + correct: false, + id: `${d.letra.toLowerCase()}-may`, + })), + ].sort(() => Math.random() - 0.5) + + all.push({ + id: `lec5-${MIN}-busca-mayus`, + stage: 5, + skillCode: `puente-${MIN}`, + type: 'eleccion', + prerequisites: [prevId], + content: { + instruction: `Tocá la ${info.letra} grande`, + audioText: `¿Dónde está la ${info.letra} grande?`, + options: mayusOptions, + emoji: info.emoji, + }, + functionalContext: `Reconocer ${info.letra} mayúscula entre minúsculas.`, + errorType: 'confusion-letra', + }) + prevId = `lec5-${MIN}-busca-mayus` + + // 4. Buscar minúscula entre mayúsculas: Tocá la m (CLAVE) + const minusOptions = [ + { label: MIN, correct: true, id: MIN }, + ...dist.map((d) => ({ + label: d.letra, + correct: false, + id: `${d.letra.toLowerCase()}-min`, + })), + ].sort(() => Math.random() - 0.5) + + all.push({ + id: `lec5-${MIN}-busca-minus`, + stage: 5, + skillCode: `puente-${MIN}`, + type: 'eleccion', + prerequisites: [prevId], + content: { + instruction: `Tocá la ${MIN} chica`, + audioText: `¿Dónde está la ${MIN} chica?`, + options: minusOptions, + emoji: info.emoji, + }, + functionalContext: `Reconocer ${MIN} minúscula entre mayúsculas — discriminación clave para dislexia.`, + errorType: 'confusion-letra', + }) + prevId = `lec5-${MIN}-busca-minus` + + // 5. Pareja inversa: ¿Cuál es la pareja de M? → m + const parejaMayusOptions = [ + { label: MIN, correct: true, id: `${MIN}-pm` }, + ...dist.map((d) => ({ + label: d.letra.toLowerCase(), + correct: false, + id: `${d.letra.toLowerCase()}-pm`, + })), + ].sort(() => Math.random() - 0.5) + + all.push({ + id: `lec5-${MIN}-pareja-mayus`, + stage: 5, + skillCode: `puente-${MIN}`, + type: 'eleccion', + prerequisites: [prevId], + content: { + instruction: `¿Cuál es la pareja de ${info.letra}?`, + audioText: `Buscá la ${MIN} chica, pareja de la ${info.letra} grande`, + options: parejaMayusOptions, + emoji: info.emoji, + }, + functionalContext: `Asociar la mayúscula ${info.letra} con su minúscula ${MIN}.`, + errorType: 'confusion-letra', + }) + prevId = `lec5-${MIN}-pareja-mayus` + + // 6. Pareja inversa 2: ¿Cuál es la pareja de m? → M + const parejaMinusOptions = [ + { label: info.letra, correct: true, id: `${MIN}-pm2` }, + ...dist.map((d) => ({ + label: d.letra, + correct: false, + id: `${d.letra.toLowerCase()}-pm2`, + })), + ].sort(() => Math.random() - 0.5) + + all.push({ + id: `lec5-${MIN}-pareja-minus`, + stage: 5, + skillCode: `puente-${MIN}`, + type: 'eleccion', + prerequisites: [prevId], + content: { + instruction: `¿Cuál es la pareja de ${MIN}?`, + audioText: `Buscá la ${info.letra} grande, pareja de la ${MIN} chica`, + options: parejaMinusOptions, + emoji: info.emoji, + }, + functionalContext: `Asociar la minúscula ${MIN} con su mayúscula ${info.letra}.`, + errorType: 'confusion-letra', + }) + prevId = `lec5-${MIN}-pareja-minus` + + // 7. Sílabas en minúscula: Tocá ma + const silabaOptions = [ + { label: info.silaba, correct: true, id: `${MIN}-sil` }, + ...dist.map((d) => ({ + label: d.silaba, + correct: false, + id: `${d.letra.toLowerCase()}-sil`, + })), + ].sort(() => Math.random() - 0.5) + + all.push({ + id: `lec5-${MIN}-silaba`, + stage: 5, + skillCode: `puente-${MIN}`, + type: 'eleccion', + prerequisites: [prevId], + content: { + instruction: `Tocá ${info.silaba}`, + audioText: `¿Dónde está ${info.silaba}?`, + options: silabaOptions, + emoji: info.emoji, + }, + functionalContext: `Leer la sílaba ${info.silaba} en minúscula.`, + errorType: 'confusion-letra', + }) + prevId = `lec5-${MIN}-silaba` + + // 8. Palabra en minúscula: Tocá mamá + const palabraOptions = [ + { label: info.palabraLower, correct: true, id: `${MIN}-pal` }, + ...dist.map((d) => ({ + label: d.palabraLower, + correct: false, + id: `${d.letra.toLowerCase()}-pal`, + })), + ].sort(() => Math.random() - 0.5) + + all.push({ + id: `lec5-${MIN}-palabra`, + stage: 5, + skillCode: `puente-${MIN}`, + type: 'eleccion', + prerequisites: [prevId], + content: { + instruction: `Tocá ${info.palabraLower}`, + audioText: `¿Dónde dice ${info.palabraLower}?`, + options: palabraOptions, + emoji: info.emoji, + }, + functionalContext: `Leer la palabra ${info.palabraLower} en minúscula.`, + errorType: 'confusion-letra', + }) + prevId = `lec5-${MIN}-palabra` + } + } + + return all +} + +function gestoKey(gesto: string): string { + const map: Record = { + 'Abrí': 'a-a-a', + 'Estirá': 'e-e-e', + 'Señalá el cielo': 'i-i-i', + 'Redondeá': 'o-o-o', + 'Señalá el suelo': 'u-u-u', + 'Manos en la boca': 'mmm', + 'Manos que abren': 'p-p-p', + 'Dedos como serpiente': 'sss', + 'Lengua arriba': 'l-l-l', + 'Golpecito': 't-t-t', + 'Nariz como nido': 'n-n-n', + 'Dedo señalando': 'd-d-d', + 'Lengua que vibra': 'rrr', + } + const key = Object.keys(map).find((k) => gesto.startsWith(k)) + return key ? map[key] : gesto +} + +const stage: Stage = { + id: 5, + name: 'Puente Mayúscula-Minúscula', + description: 'Asociar letras mayúsculas con sus correspondientes minúsculas. 8 ejercicios por letra — 104 en total.', + exercises: buildExercises(), +} + +export default stage diff --git a/curriculum/lectura/etapa-6.ts b/curriculum/lectura/etapa-6.ts new file mode 100644 index 0000000..add103b --- /dev/null +++ b/curriculum/lectura/etapa-6.ts @@ -0,0 +1,119 @@ +import type { Stage } from '../types' + +const palabras2silabas = [ + { palabra: 'MAPA', silabas: ['MA', 'PA'], emoji: '🗺️' }, + { palabra: 'PASA', silabas: ['PA', 'SA'], emoji: '🔄' }, + { palabra: 'SALA', silabas: ['SA', 'LA'], emoji: '🏠' }, + { palabra: 'TAPA', silabas: ['TA', 'PA'], emoji: '📦' }, + { palabra: 'LATA', silabas: ['LA', 'TA'], emoji: '🥫' }, + { palabra: 'NIDO', silabas: ['NI', 'DO'], emoji: '🪺' }, + { palabra: 'DEDO', silabas: ['DE', 'DO'], emoji: '👉' }, + { palabra: 'RATO', silabas: ['RA', 'TO'], emoji: '⏰' }, + { palabra: 'MESA', silabas: ['ME', 'SA'], emoji: '🪑' }, + { palabra: 'PATO', silabas: ['PA', 'TO'], emoji: '🦆' }, + { palabra: 'LIMA', silabas: ['LI', 'MA'], emoji: '🍋' }, + { palabra: 'SOPA', silabas: ['SO', 'PA'], emoji: '🥣' }, + { palabra: 'TINA', silabas: ['TI', 'NA'], emoji: '🛁' }, + { palabra: 'MONO', silabas: ['MO', 'NO'], emoji: '🐵' }, + { palabra: 'PINO', silabas: ['PI', 'NO'], emoji: '🌲' }, + { palabra: 'LUNES', silabas: ['LU', 'NES'], emoji: '📅' }, + { palabra: 'SAPO', silabas: ['SA', 'PO'], emoji: '🐸' }, + { palabra: 'TELO', silabas: ['TE', 'LO'], emoji: '🧶' }, + { palabra: 'DAMA', silabas: ['DA', 'MA'], emoji: '👩' }, + { palabra: 'RAMA', silabas: ['RA', 'MA'], emoji: '🌿' }, +] + +const exercises: any[] = [] + +for (const { palabra, silabas, emoji } of palabras2silabas) { + const lowerPalabra = palabra.toLowerCase().replace(/[^a-z]/g, '') + const id = `lec6-palabra-${lowerPalabra}` + const letras = palabra.split('') + const conLetras = letras[0]?.toLowerCase() + const consonantDep = conLetras && ['m', 'p', 'l', 's', 't', 'n', 'd', 'r'].includes(conLetras) + ? `lec4-${conLetras}-completa` + : 'lec3-u-completa' + + // Leer la palabra (ver las sílabas separadas) + exercises.push({ + id: `${id}-leer`, + stage: 6, + skillCode: `silabas-${lowerPalabra}`, + type: 'eleccion', + prerequisites: [consonantDep], + content: { + instruction: `Leé esta palabra: ${silabas.join('-')}`, + text: `${silabas.join(' ')}`, + audioText: `${palabra}: ${silabas.join(', ')}`, + emoji, + options: [ + { label: palabra, correct: true, id: lowerPalabra }, + ...palabras2silabas + .filter(p => p.palabra !== palabra) + .slice(0, 2) + .map(p => ({ label: p.palabra, correct: false, id: p.palabra.toLowerCase() })), + ], + }, + functionalContext: `Leer la palabra ${palabra} combinando sílabas.`, + errorType: 'silaba-dificil', + }) + + // Emparejar con imagen + exercises.push({ + id: `${id}-imagen`, + stage: 6, + skillCode: `silabas-${lowerPalabra}`, + type: 'palabra', + prerequisites: [`${id}-leer`], + content: { + instruction: `¿Cuál palabra es esta?`, + imageEmoji: emoji, + audioText: `¿Qué palabra es?`, + options: [ + ...palabras2silabas + .filter(p => p.palabra !== palabra) + .slice(0, 2) + .map(p => ({ label: p.palabra, correct: false, id: p.palabra.toLowerCase() })), + { label: palabra, correct: true, id: lowerPalabra }, + { label: 'Escuchar 👂', correct: false, id: 'escuchar' }, + ], + }, + functionalContext: `Asociar la palabra ${palabra} con su imagen.`, + errorType: 'confusion-letra', + }) +} + +// Silaba order exercises +const secuencias = [ + { palabra: 'MAPA', silabas: ['PA', 'MA'], emoji: '🗺️' }, + { palabra: 'PATO', silabas: ['TO', 'PA'], emoji: '🦆' }, + { palabra: 'SAPO', silabas: ['PO', 'SA'], emoji: '🐸' }, +] + +for (const { palabra, silabas, emoji } of secuencias) { + const lower = palabra.toLowerCase() + exercises.push({ + id: `lec6-ordenar-${lower}`, + stage: 6, + skillCode: `silabas-orden-${lower}`, + type: 'secuencia', + prerequisites: [`lec6-palabra-${lower}-leer`], + content: { + instruction: `Ordená las sílabas para formar ${palabra}`, + audioText: `Ordená para formar ${palabra}`, + sequence: silabas, + emoji, + }, + functionalContext: `Ordenar sílabas para formar ${palabra}.`, + errorType: 'inversion-letra', + }) +} + +const stage: Stage = { + id: 6, + name: 'Sílabas y palabras de 2 sílabas', + description: 'Combinar consonantes + vocales para formar y leer palabras de 2 sílabas.', + exercises, +} + +export default stage diff --git a/curriculum/lectura/etapa-7.ts b/curriculum/lectura/etapa-7.ts new file mode 100644 index 0000000..0ddce86 --- /dev/null +++ b/curriculum/lectura/etapa-7.ts @@ -0,0 +1,109 @@ +import type { Stage } from '../types' + +const palabras3silabas = [ + { palabra: 'MALETA', silabas: ['MA', 'LE', 'TA'], emoji: '🧳' }, + { palabra: 'PALETA', silabas: ['PA', 'LE', 'TA'], emoji: '🎨' }, + { palabra: 'SALIDA', silabas: ['SA', 'LI', 'DA'], emoji: '🚪' }, + { palabra: 'TELÉFONO', silabas: ['TE', 'LÉ', 'FO', 'NO'], emoji: '📞' }, + { palabra: 'MARIPOSA', silabas: ['MA', 'RI', 'PO', 'SA'], emoji: '🦋' }, + { palabra: 'MUSICA', silabas: ['MU', 'SI', 'CA'], emoji: '🎵' }, + { palabra: 'PLATO', silabas: ['PLA', 'TO'], emoji: '🍽️' }, + { palabra: 'TRISTE', silabas: ['TRIS', 'TE'], emoji: '😢' }, + { palabra: 'PLUMA', silabas: ['PLU', 'MA'], emoji: '🪶' }, + { palabra: 'PANTALLA', silabas: ['PAN', 'TA', 'LLA'], emoji: '📺' }, + { palabra: 'PRIMAVERA', silabas: ['PRI', 'MA', 'VE', 'RA'], emoji: '🌸' }, + { palabra: 'MELÓN', silabas: ['ME', 'LÓN'], emoji: '🍈' }, + { palabra: 'SILLA', silabas: ['SI', 'LLA'], emoji: '🪑' }, + { palabra: 'TORTUGA', silabas: ['TOR', 'TU', 'GA'], emoji: '🐢' }, + { palabra: 'DORMIR', silabas: ['DOR', 'MIR'], emoji: '😴' }, + { palabra: 'RATÓN', silabas: ['RA', 'TÓN'], emoji: '🐭' }, + { palabra: 'NARIZ', silabas: ['NA', 'RIZ'], emoji: '👃' }, + { palabra: 'LÁPIZ', silabas: ['LÁ', 'PIZ'], emoji: '✏️' }, + { palabra: 'DINOSAURIO', silabas: ['DI', 'NO', 'SAU', 'RIO'], emoji: '🦕' }, + { palabra: 'ELEFANTE', silabas: ['E', 'LE', 'FAN', 'TE'], emoji: '🐘' }, +] + +const exercises: any[] = [] + +for (const { palabra, silabas, emoji } of palabras3silabas) { + const lowerPalabra = palabra.toLowerCase().replace(/[^a-záéíóú]/g, '') + + // Read the word + exercises.push({ + id: `lec7-${lowerPalabra}-leer`, + stage: 7, + skillCode: `palabra-${lowerPalabra}`, + type: 'eleccion', + prerequisites: ['lec6-palabra-mapa-leer'], + content: { + instruction: `Leé esta palabra: ${palabra}`, + text: palabra, + audioText: `${palabra}`, + emoji, + options: [ + { label: palabra, correct: true, id: lowerPalabra }, + ...palabras3silabas + .filter(p => p.palabra !== palabra) + .slice(0, 2) + .map(p => ({ label: p.palabra, correct: false, id: p.palabra.toLowerCase().replace(/[^a-záéíóú]/g, '') })), + ], + }, + functionalContext: `Leer la palabra ${palabra}.`, + errorType: 'silaba-dificil', + }) + + // Count syllables + exercises.push({ + id: `lec7-${lowerPalabra}-silabas`, + stage: 7, + skillCode: `palabra-${lowerPalabra}`, + type: 'conciencia', + prerequisites: [`lec7-${lowerPalabra}-leer`], + content: { + instruction: `¿Cuántas sílabas tiene ${palabra}?`, + text: palabra, + audioText: `¿Cuántas sílabas?`, + options: [ + { label: `${'👏'.repeat(silabas.length)} ${silabas.length}`, correct: true, id: `${silabas.length}` }, + { label: `${'👏'.repeat(Math.max(1, silabas.length - 1))} ${silabas.length - 1}`, correct: false, id: `${silabas.length - 1}` }, + { label: `${'👏'.repeat(silabas.length + 1)} ${silabas.length + 1}`, correct: false, id: `${silabas.length + 1}` }, + ], + emoji, + }, + functionalContext: `Contar sílabas en ${palabra}.`, + errorType: 'discriminacion-auditiva', + }) + + // Match image + exercises.push({ + id: `lec7-${lowerPalabra}-imagen`, + stage: 7, + skillCode: `palabra-${lowerPalabra}`, + type: 'palabra', + prerequisites: [`lec7-${lowerPalabra}-silabas`], + content: { + instruction: `¿Qué palabra es?`, + imageEmoji: emoji, + audioText: `¿Cuál es?`, + options: [ + ...palabras3silabas + .filter(p => p.palabra !== palabra) + .slice(0, 2) + .map(p => ({ label: p.palabra, correct: false, id: p.palabra.toLowerCase().replace(/[^a-záéíóú]/g, '') })), + { label: palabra, correct: true, id: lowerPalabra }, + { label: 'Escuchar 👂', correct: false, id: 'escuchar' }, + ], + }, + functionalContext: `Asociar la palabra ${palabra} con su imagen.`, + errorType: 'confusion-letra', + }) +} + +const stage: Stage = { + id: 7, + name: 'Palabras de 3+ sílabas', + description: 'Leer y comprender palabras de 3 o más sílabas con sílabas compuestas.', + exercises, +} + +export default stage diff --git a/curriculum/lectura/etapa-8.ts b/curriculum/lectura/etapa-8.ts new file mode 100644 index 0000000..1d52e6d --- /dev/null +++ b/curriculum/lectura/etapa-8.ts @@ -0,0 +1,147 @@ +import type { Stage } from '../types' + +const frases = [ + { + frase: 'Mi mamá me ama.', + opciones: ['Mi papá me ama.', 'Mi mamá me ama.', 'Mi mamá me mima.'], + correcta: 'Mi mamá me ama.', + emoji: '💕', + }, + { + frase: 'El sol sale temprano.', + opciones: ['La luna sale tarde.', 'El sol sale temprano.', 'El sol se esconde.'], + correcta: 'El sol sale temprano.', + emoji: '☀️', + }, + { + frase: 'La luna está en el cielo.', + opciones: ['El sol está en el mar.', 'La luna está en el cielo.', 'La luna es redonda.'], + correcta: 'La luna está en el cielo.', + emoji: '🌙', + }, + { + frase: 'El pato nada en el lago.', + opciones: ['El pato vuela alto.', 'El pato nada en el lago.', 'El gato nada en el lago.'], + correcta: 'El pato nada en el lago.', + emoji: '🦆', + }, + { + frase: 'Mi gato es muy lindo.', + opciones: ['Mi perro es grande.', 'Mi gato es muy lindo.', 'Mi gato salta alto.'], + correcta: 'Mi gato es muy lindo.', + emoji: '🐱', + }, + { + frase: 'La mariposa vuela entre las flores.', + opciones: ['La mariposa está dormida.', 'La mariposa vuela entre las flores.', 'La abeja hace miel.'], + correcta: 'La mariposa vuela entre las flores.', + emoji: '🦋', + }, + { + frase: 'Tomo leche con galletitas.', + opciones: ['Tomo agua con hielo.', 'Tomo leche con galletitas.', 'Como sopa caliente.'], + correcta: 'Tomo leche con galletitas.', + emoji: '🥛', + }, + { + frase: 'El perro corre en el parque.', + opciones: ['El perro duerme en casa.', 'El perro corre en el parque.', 'El gato corre en el parque.'], + correcta: 'El perro corre en el parque.', + emoji: '🐕', + }, + { + frase: 'Mi hermana tiene un lápiz rojo.', + opciones: ['Mi mamá tiene una mesa.', 'Mi hermana tiene un lápiz rojo.', 'Mi hermana tiene un libro.'], + correcta: 'Mi hermana tiene un lápiz rojo.', + emoji: '✏️', + }, + { + frase: 'La tortuga camina despacio.', + opciones: ['La liebre salta rápido.', 'La tortuga camina despacio.', 'El pez nada veloz.'], + correcta: 'La tortuga camina despacio.', + emoji: '🐢', + }, + { + frase: 'El elefante es muy grande.', + opciones: ['El ratón es chiquito.', 'El elefante es muy grande.', 'El perro es mediano.'], + correcta: 'El elefante es muy grande.', + emoji: '🐘', + }, + { + frase: 'Me gusta pintar con mis dedos.', + opciones: ['Me gusta cantar canciones.', 'Me gusta pintar con mis dedos.', 'Me gusta mirar tele.'], + correcta: 'Me gusta pintar con mis dedos.', + emoji: '🎨', + }, + { + frase: 'El sol es amarillo.', + opciones: ['La luna es blanca.', 'El sol es amarillo.', 'El cielo es azul.'], + correcta: 'El sol es amarillo.', + emoji: '🟡', + }, + { + frase: 'Mi papá me lee un cuento.', + opciones: ['Mi mamá me canta.', 'Mi papá me lee un cuento.', 'Mi papá cocina rico.'], + correcta: 'Mi papá me lee un cuento.', + emoji: '📖', + }, + { + frase: 'La mesa está en la sala.', + opciones: ['La silla está en la cocina.', 'La mesa está en la sala.', 'La cama está en mi pieza.'], + correcta: 'La mesa está en la sala.', + emoji: '🪑', + }, +] + +const exercises: any[] = [] + +for (const { frase, opciones, correcta, emoji } of frases) { + const id = `lec8-${frase.toLowerCase().replace(/[^a-záéíóú]/g, '').slice(0, 20)}` + + exercises.push({ + id: `${id}-leer`, + stage: 8, + skillCode: 'frases', + type: 'lectura', + prerequisites: ['lec7-maleta-leer'], + content: { + instruction: 'Leé esta oración en voz alta', + text: frase, + audioText: frase, + emoji, + options: [ + { label: '¡Leída!', correct: true, id: 'leida' }, + ], + }, + functionalContext: 'Leer oraciones simples.', + }) + + exercises.push({ + id: `${id}-comprender`, + stage: 8, + skillCode: 'frases', + type: 'lectura', + prerequisites: [`${id}-leer`], + content: { + instruction: `¿Cuál oración es la correcta?`, + emoji, + audioText: `¿Cuál es correcta?`, + options: opciones.map(o => ({ + label: o, + correct: o === correcta, + id: o.toLowerCase().replace(/[^a-záéíóú]/g, '').slice(0, 15), + })), + }, + functionalContext: 'Comprender la oración leída.', + errorType: 'omision-letra', + }) +} + +const stage: Stage = { + id: 8, + name: 'Frases y oraciones', + description: 'Leer oraciones simples y comprender su significado.', + exercises, +} + +export default stage diff --git a/curriculum/lectura/etapa-9.ts b/curriculum/lectura/etapa-9.ts new file mode 100644 index 0000000..25161c4 --- /dev/null +++ b/curriculum/lectura/etapa-9.ts @@ -0,0 +1,176 @@ +import type { Stage } from '../types' + +const lecturas = [ + { + texto: 'El sol sale por la mañana. Las flores se abren. Los pájaros cantan. Es un lindo día.', + pregunta: '¿Qué pasa por la mañana?', + opciones: [ + { label: 'La luna sale', correct: false }, + { label: 'El sol sale', correct: true }, + { label: 'Las estrellas brillan', correct: false }, + ], + emoji: '🌄', + }, + { + texto: 'Mamá prepara la mesa. Papá sirve la sopa. Todos comen juntos. Después lavan los platos.', + pregunta: '¿Qué hace papá?', + opciones: [ + { label: 'Lava los platos', correct: false }, + { label: 'Prepara la mesa', correct: false }, + { label: 'Sirve la sopa', correct: true }, + ], + emoji: '🍲', + }, + { + texto: 'El gato de mi casa es negro. Le gusta dormir al sol. Por la noche sale a pasear.', + pregunta: '¿De qué color es el gato?', + opciones: [ + { label: 'Blanco', correct: false }, + { label: 'Negro', correct: true }, + { label: 'Gris', correct: false }, + ], + emoji: '🐈‍⬛', + }, + { + texto: 'La nena juega en el parque. Sube al tobogán. Baja rápido y se ríe. Después juega en la arena.', + pregunta: '¿Dónde juega la nena?', + opciones: [ + { label: 'En su casa', correct: false }, + { label: 'En la escuela', correct: false }, + { label: 'En el parque', correct: true }, + ], + emoji: '🛝', + }, + { + texto: 'En la granja hay vacas, ovejas y gallinas. Las vacas dan leche. Las gallinas ponen huevos.', + pregunta: '¿Qué dan las vacas?', + opciones: [ + { label: 'Huevos', correct: false }, + { label: 'Leche', correct: true }, + { label: 'Lana', correct: false }, + ], + emoji: '🐄', + }, + { + texto: 'Luna tiene cuatro patas, dos orejas largas y una cola corta. Come zanahorias. Es una coneja.', + pregunta: '¿Qué es Luna?', + opciones: [ + { label: 'Un perro', correct: false }, + { label: 'Un gato', correct: false }, + { label: 'Una coneja', correct: true }, + ], + emoji: '🐰', + }, + { + texto: 'El libro de cuentos tiene dibujos lindos. La nena lo mira todas las noches. Su mamá le lee antes de dormir.', + pregunta: '¿Cuándo lee mamá?', + opciones: [ + { label: 'Por la mañana', correct: false }, + { label: 'Antes de dormir', correct: true }, + { label: 'A la tarde', correct: false }, + ], + emoji: '📚', + }, + { + texto: 'La tortuga Tula vive en un estanque. Le gusta nadar y tomar sol en una piedra. Es muy tranquila.', + pregunta: '¿Dónde vive Tula?', + opciones: [ + { label: 'En el mar', correct: false }, + { label: 'En un estanque', correct: true }, + { label: 'En una casa', correct: false }, + ], + emoji: '🐢', + }, + { + texto: 'Tomás tiene tres años. Juega con bloques de colores. Arma una torre muy alta. Después la derriba. ¡Plaf!', + pregunta: '¿Qué arma Tomás?', + opciones: [ + { label: 'Un castillo', correct: false }, + { label: 'Una torre', correct: true }, + { label: 'Un puente', correct: false }, + ], + emoji: '🧱', + }, + { + texto: 'La abuela teje una bufanda roja para el invierno. Usa lana suave y dos agujas. La bufanda queda muy linda.', + pregunta: '¿Qué teje la abuela?', + opciones: [ + { label: 'Un gorro', correct: false }, + { label: 'Una bufanda', correct: true }, + { label: 'Un guante', correct: false }, + ], + emoji: '🧣', + }, + { + texto: 'En la plaza hay un árbol grande. Tiene hojas verdes y flores rosadas. Los pájaros hacen nidos allí.', + pregunta: '¿De qué color son las flores?', + opciones: [ + { label: 'Rosadas', correct: true }, + { label: 'Amarillas', correct: false }, + { label: 'Blancas', correct: false }, + ], + emoji: '🌳', + }, + { + texto: 'El niño se pone las botas de lluvia. Abre el paraguas azul. Salta en los charcos. ¡Qué divertido!', + pregunta: '¿Qué se pone el niño?', + opciones: [ + { label: 'Un abrigo', correct: false }, + { label: 'Botas de lluvia', correct: true }, + { label: 'Un sombrero', correct: false }, + ], + emoji: '☂️', + }, +] + +const exercises: any[] = [] + +for (const { texto, pregunta, opciones, emoji } of lecturas) { + const id = `lec9-${texto.toLowerCase().replace(/[^a-záéíóú]/g, '').slice(0, 15)}` + + exercises.push({ + id: `${id}-texto`, + stage: 9, + skillCode: 'comprension', + type: 'lectura', + prerequisites: ['lec8-frase-mimamameama-leer'], + content: { + instruction: 'Leé este texto', + text: texto, + audioText: texto, + emoji, + options: [ + { label: '¡Leído!', correct: true, id: 'leido' }, + ], + }, + functionalContext: 'Leer un texto corto.', + }) + + exercises.push({ + id: `${id}-pregunta`, + stage: 9, + skillCode: 'comprension', + type: 'lectura', + prerequisites: [`${id}-texto`], + content: { + instruction: `Respondé: ${pregunta}`, + text: texto, + audioText: pregunta, + emoji, + options: [ + ...opciones.map(o => ({ label: o.label, correct: o.correct, id: o.label.toLowerCase().replace(/[^a-záéíóú]/g, '') })), + ], + }, + functionalContext: 'Comprender y responder preguntas sobre el texto.', + errorType: 'omision-letra', + }) +} + +const stage: Stage = { + id: 9, + name: 'Comprensión lectora', + description: 'Leer textos cortos y responder preguntas de comprensión.', + exercises, +} + +export default stage diff --git a/curriculum/numeros/etapa-1.ts b/curriculum/numeros/etapa-1.ts new file mode 100644 index 0000000..cad9fb7 --- /dev/null +++ b/curriculum/numeros/etapa-1.ts @@ -0,0 +1,82 @@ +import type { Stage } from '../types' + +const exercises: any[] = [] + +// Count 1-5 +for (let n = 1; n <= 5; n++) { + const emojis = '⭐'.repeat(n) + exercises.push({ + id: `num1-contar-${n}`, + stage: 1, + skillCode: `contar-${n}`, + type: 'sensibilizacion', + prerequisites: n === 1 ? [] : [`num1-contar-${n - 1}`], + content: { + instruction: `Contá. ¿Cuántas estrellas hay?`, + audioText: `Uno, dos, tres...`, + emoji: emojis, + options: [ + ...Array.from({ length: 5 }, (_, i) => i + 1) + .filter(j => j !== n) + .slice(0, 2) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + { label: String(n), correct: true, id: String(n) }, + ].sort(() => Math.random() - 0.5), + }, + functionalContext: `Contar hasta ${n} elementos.`, + errorType: 'discriminacion-auditiva', + }) +} + +// Recognize written digits 1-5 +for (let n = 1; n <= 5; n++) { + exercises.push({ + id: `num1-digito-${n}`, + stage: 1, + skillCode: `digito-${n}`, + type: 'eleccion', + prerequisites: [`num1-contar-${n}`], + content: { + instruction: `Señalá el número ${n}`, + audioText: `Buscá el número ${n}`, + options: [ + ...Array.from({ length: 5 }, (_, i) => i + 1) + .filter(j => j !== n) + .slice(0, 2) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + { label: String(n), correct: true, id: String(n) }, + ].sort(() => Math.random() - 0.5), + emoji: String(n), + }, + functionalContext: `Reconocer el dígito ${n}.`, + errorType: 'confusion-letra', + }) +} + +// Trace with finger +for (let n = 1; n <= 5; n++) { + exercises.push({ + id: `num1-trazo-${n}`, + stage: 1, + skillCode: `digito-${n}`, + type: 'trazo', + prerequisites: [`num1-digito-${n}`], + content: { + instruction: `Trazá el número ${n} con tu dedo`, + text: String(n), + audioText: `Trazá el número ${n}`, + emoji: '✍️', + }, + functionalContext: `Aprender a trazar el número ${n}.`, + errorType: 'confusion-letra', + }) +} + +const stage: Stage = { + id: 1, + name: 'Conteo del 1 al 5', + description: 'Contar objetos, reconocer dígitos y trazar números del 1 al 5.', + exercises, +} + +export default stage diff --git a/curriculum/numeros/etapa-2.ts b/curriculum/numeros/etapa-2.ts new file mode 100644 index 0000000..46c2608 --- /dev/null +++ b/curriculum/numeros/etapa-2.ts @@ -0,0 +1,99 @@ +import type { Stage } from '../types' + +const exercises: any[] = [] + +// Count 6-10 +for (let n = 6; n <= 10; n++) { + const emojis = '🟢'.repeat(n) + exercises.push({ + id: `num2-contar-${n}`, + stage: 2, + skillCode: `contar-${n}`, + prerequisites: n === 6 ? ['num1-trazo-5'] : [`num2-contar-${n - 1}`], + type: 'sensibilizacion', + content: { + instruction: `¿Cuántos círculos verdes hay?`, + audioText: `Contá los círculos`, + emoji: emojis, + options: [ + ...Array.from({ length: 5 }, (_, i) => i + 6) + .filter(j => j !== n) + .slice(0, 2) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + { label: String(n), correct: true, id: String(n) }, + ].sort(() => Math.random() - 0.5), + }, + functionalContext: `Contar hasta ${n} elementos.`, + errorType: 'discriminacion-auditiva', + }) +} + +// Recognize written digits 6-10 +for (let n = 6; n <= 10; n++) { + exercises.push({ + id: `num2-digito-${n}`, + stage: 2, + skillCode: `digito-${n}`, + type: 'eleccion', + prerequisites: [`num2-contar-${n}`], + content: { + instruction: `Señalá el número ${n}`, + audioText: `Buscá el ${n}`, + options: [ + ...Array.from({ length: 5 }, (_, i) => i + 6) + .filter(j => j !== n) + .slice(0, 2) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + { label: String(n), correct: true, id: String(n) }, + ].sort(() => Math.random() - 0.5), + emoji: String(n), + }, + functionalContext: `Reconocer el dígito ${n}.`, + errorType: 'confusion-letra', + }) +} + +// Order sequence 1-10 +exercises.push({ + id: 'num2-secuencia-1-10', + stage: 2, + skillCode: 'secuencia-1-10', + type: 'secuencia', + prerequisites: ['num2-digito-10'], + content: { + instruction: 'Ordená los números del 1 al 10', + audioText: 'Ordená del 1 al 10', + sequence: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], + emoji: '🔢', + }, + functionalContext: 'Conocer la secuencia numérica del 1 al 10.', + errorType: 'inversion-letra', +}) + +// Trace 6-10 +for (let n = 6; n <= 10; n++) { + exercises.push({ + id: `num2-trazo-${n}`, + stage: 2, + skillCode: `digito-${n}`, + type: 'trazo', + prerequisites: [`num2-digito-${n}`, ...(n > 6 ? [`num2-trazo-${n - 1}`] : [])], + content: { + instruction: `Trazá el número ${n} con tu dedo`, + text: String(n), + audioText: `Trazá el ${n}`, + emoji: '✍️', + }, + functionalContext: `Aprender a trazar el número ${n}.`, + errorType: 'confusion-letra', + }) +} + +const stage: Stage = { + id: 2, + name: 'Conteo del 6 al 10', + description: 'Contar objetos, reconocer dígitos y trazar números del 6 al 10, y ordenar del 1 al 10.', + exercises, +} + +export default stage diff --git a/curriculum/numeros/etapa-3.ts b/curriculum/numeros/etapa-3.ts new file mode 100644 index 0000000..2b4895d --- /dev/null +++ b/curriculum/numeros/etapa-3.ts @@ -0,0 +1,107 @@ +import type { Stage } from '../types' + +const exercises: any[] = [] + +// Pair number with quantity +for (let n = 1; n <= 10; n++) { + const emojis = ['🐶', '🐱', '🐰', '🐸', '🐵', '🐼', '🐨', '🦊', '🐻', '🐯'] + const animal = emojis[n - 1] + const howMany = animal.repeat(Math.min(n, 5)) + (n > 5 ? ` (${n})` : '') + + exercises.push({ + id: `num3-cantidad-${n}`, + stage: 3, + skillCode: `correspondencia-${n}`, + type: 'conciencia', + prerequisites: n === 1 ? ['num2-trazo-10'] : [`num3-cantidad-${n - 1}`], + content: { + instruction: `¿Cuántos ${animal} hay?`, + audioText: `Contá los animalitos`, + text: n <= 5 ? animal.repeat(n) : `${animal.repeat(5)}... +${n - 5}`, + options: [ + ...Array.from({ length: 10 }, (_, i) => i + 1) + .filter(j => j !== n) + .slice(0, 2) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + { label: String(n), correct: true, id: String(n) }, + ].sort(() => Math.random() - 0.5), + emoji: animal, + }, + functionalContext: 'Asociar cantidad con número.', + errorType: 'discriminacion-auditiva', + }) +} + +// Match groups that are equal +const pares = [ + { a: '🐶🐶🐶', b: '3️⃣', num: 3 }, + { a: '🐱🐱🐱🐱', b: '4️⃣', num: 4 }, + { a: '🐰🐰🐰🐰🐰', b: '5️⃣', num: 5 }, + { a: '🐸🐸', b: '2️⃣', num: 2 }, + { a: '🐵🐵🐵🐵🐵🐵', b: '6️⃣', num: 6 }, +] + +for (const { a, b, num } of pares) { + exercises.push({ + id: `num3-emparejar-${num}`, + stage: 3, + skillCode: 'correspondencia', + type: 'conciencia', + prerequisites: [`num3-cantidad-${num}`], + content: { + instruction: `¿Cuál número le corresponde a ${a}?`, + audioText: `¿Qué número es?`, + text: a, + options: [ + ...pares.filter(p => p.num !== num).slice(0, 2).map(p => ({ label: p.b, correct: false, id: String(p.num) })), + { label: b, correct: true, id: String(num) }, + ].sort(() => Math.random() - 0.5), + emoji: '🔄', + }, + functionalContext: 'Emparejar grupos con su número.', + errorType: 'confusion-letra', + }) +} + +// More/less comparison +const comparaciones = [ + { a: 3, b: 5, mas: 5, menos: 3 }, + { a: 2, b: 7, mas: 7, menos: 2 }, + { a: 4, b: 4, mas: 4, menos: 4 }, + { a: 6, b: 3, mas: 6, menos: 3 }, + { a: 1, b: 9, mas: 9, menos: 1 }, +] + +for (const { a, b, mas, menos } of comparaciones) { + const idA = '⭐'.repeat(a) + const idB = '⭐'.repeat(b) + exercises.push({ + id: `num3-mas-${a}-${b}`, + stage: 3, + skillCode: 'comparacion', + type: 'conciencia', + prerequisites: [`num3-cantidad-${Math.max(a, b)}`], + content: { + instruction: `¿Dónde hay MÁS?`, + text: `${idA} vs ${idB}`, + audioText: `¿Cuál tiene más?`, + options: [ + { label: `👈 ${a}`, correct: mas === a, id: `izq-${a}` }, + { label: `👉 ${b}`, correct: mas === b, id: `der-${b}` }, + { label: '😐 Igual', correct: a === b, id: 'igual' }, + ], + emoji: '⚖️', + }, + functionalContext: 'Comparar cantidades (más/menos).', + errorType: 'discriminacion-auditiva', + }) +} + +const stage: Stage = { + id: 3, + name: 'Correspondencia uno a uno', + description: 'Asociar cantidades con números, emparejar grupos iguales, comparar más y menos.', + exercises, +} + +export default stage diff --git a/curriculum/numeros/etapa-4.ts b/curriculum/numeros/etapa-4.ts new file mode 100644 index 0000000..bc8793f --- /dev/null +++ b/curriculum/numeros/etapa-4.ts @@ -0,0 +1,83 @@ +import type { Stage } from '../types' + +const exercises: any[] = [] + +for (let n = 11; n <= 20; n++) { + exercises.push({ + id: `num4-contar-${n}`, + stage: 4, + skillCode: `contar-${n}`, + type: 'conciencia', + prerequisites: n === 11 ? ['num3-cantidad-10'] : [`num4-contar-${n - 1}`], + content: { + instruction: `Contá. ¿Cuántos hay? (${n})`, + audioText: `Son ${n}`, + text: `🔵🔵🔵🔵🔵 🟣🟣🟣🟣🟣 ${n > 10 ? `+${n - 10}` : ''}`, + options: [ + ...Array.from({ length: 5 }, (_, i) => n - 2 + i) + .filter(j => j >= 10 && j <= 20 && j !== n) + .slice(0, 2) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + { label: String(n), correct: true, id: String(n) }, + ].sort(() => Math.random() - 0.5), + emoji: String(n), + }, + functionalContext: `Reconocer el número ${n}.`, + errorType: 'confusion-letra', + }) +} + +// Decenas y unidades visual +for (let n = 10; n <= 20; n++) { + const decenas = Math.floor(n / 10) + const unidades = n % 10 + exercises.push({ + id: `num4-descomponer-${n}`, + stage: 4, + skillCode: 'decenas-unidades', + type: 'eleccion', + prerequisites: [`num4-contar-${n}`], + content: { + instruction: `${n} son ${decenas} decena${decenas > 1 ? 's' : ''} y ${unidades} unidades`, + text: `🔟🔟${'●'.repeat(unidades)} = ${n}`, + audioText: `${n} es ${decenas} decenas y ${unidades} unidades`, + options: [ + { label: '🔟 Decenas', correct: false, id: `decenas-${n}` }, + { label: String(n), correct: true, id: String(n) }, + ...Array.from({ length: 3 }, (_, i) => n + i - 1) + .filter(j => j >= 10 && j <= 20 && j !== n) + .slice(0, 1) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + ].sort(() => Math.random() - 0.5), + emoji: '🔟', + }, + functionalContext: 'Introducir decenas y unidades.', + errorType: 'confusion-letra', + }) +} + +// Secuencia 11-20 +exercises.push({ + id: 'num4-secuencia-11-20', + stage: 4, + skillCode: 'secuencia-11-20', + type: 'secuencia', + prerequisites: ['num4-descomponer-20'], + content: { + instruction: 'Ordená los números del 11 al 20', + audioText: 'Ordená del 11 al 20', + sequence: ['11', '12', '13', '14', '15', '16', '17', '18', '19', '20'], + emoji: '🔢', + }, + functionalContext: 'Conocer la secuencia del 11 al 20.', + errorType: 'inversion-letra', +}) + +const stage: Stage = { + id: 4, + name: 'Números del 11 al 20', + description: 'Aprender los números del 11 al 20. Introducir decenas y unidades.', + exercises, +} + +export default stage diff --git a/curriculum/numeros/etapa-5.ts b/curriculum/numeros/etapa-5.ts new file mode 100644 index 0000000..90fc91c --- /dev/null +++ b/curriculum/numeros/etapa-5.ts @@ -0,0 +1,90 @@ +import type { Stage } from '../types' + +const sumas = [ + { a: 1, b: 1, r: 2, emoji: '🐶+🐶' }, + { a: 2, b: 1, r: 3, emoji: '🐱+🐶' }, + { a: 2, b: 2, r: 4, emoji: '🐱+🐱' }, + { a: 3, b: 1, r: 5, emoji: '🐰+🐶' }, + { a: 3, b: 2, r: 5, emoji: '🐰+🐱' }, + { a: 4, b: 1, r: 5, emoji: '🐸+🐶' }, + { a: 5, b: 1, r: 6, emoji: '🐵+🐶' }, + { a: 3, b: 3, r: 6, emoji: '🐰+🐰' }, + { a: 5, b: 2, r: 7, emoji: '🐵+🐱' }, + { a: 4, b: 3, r: 7, emoji: '🐸+🐰' }, + { a: 5, b: 3, r: 8, emoji: '🐵+🐰' }, + { a: 4, b: 4, r: 8, emoji: '🐸+🐸' }, + { a: 5, b: 4, r: 9, emoji: '🐵+🐸' }, + { a: 5, b: 5, r: 10, emoji: '🐵+🐵' }, + { a: 6, b: 1, r: 7, emoji: '🐼+🐶' }, + { a: 6, b: 2, r: 8, emoji: '🐼+🐱' }, + { a: 7, b: 1, r: 8, emoji: '🐨+🐶' }, + { a: 7, b: 2, r: 9, emoji: '🐨+🐱' }, + { a: 8, b: 1, r: 9, emoji: '🦊+🐶' }, + { a: 8, b: 2, r: 10, emoji: '🦊+🐱' }, + { a: 9, b: 1, r: 10, emoji: '🐻+🐶' }, + { a: 3, b: 4, r: 7, emoji: '🐰+🐸' }, + { a: 4, b: 5, r: 9, emoji: '🐸+🐵' }, + { a: 6, b: 3, r: 9, emoji: '🐼+🐰' }, + { a: 7, b: 3, r: 10, emoji: '🐨+🐰' }, +] + +const exercises: any[] = [] + +for (const { a, b, r, emoji } of sumas) { + const id = `num5-suma-${a}-${b}` + exercises.push({ + id, + stage: 5, + skillCode: 'suma', + type: 'problema', + prerequisites: a <= 5 && b <= 5 ? ['num4-secuencia-11-20'] : [`num5-suma-${a}-${b - 1}`], + content: { + instruction: `¿Cuánto es ${a} + ${b}?`, + text: `${a} + ${b} = ?`, + audioText: `${a} más ${b}`, + emoji, + options: [ + ...Array.from({ length: 4 }, (_, i) => r - 2 + i + (i >= 2 ? 2 : 0)) + .filter(j => j >= 1 && j <= 12 && j !== r) + .slice(0, 2) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + { label: String(r), correct: true, id: String(r) }, + ].sort(() => Math.random() - 0.5), + }, + functionalContext: 'Sumar dos números pequeños.', + errorType: 'discriminacion-auditiva', + }) +} + +// Represent sum with objects +for (const { a, b, r } of sumas.slice(0, 8)) { + exercises.push({ + id: `num5-suma-visual-${a}-${b}`, + stage: 5, + skillCode: 'suma-visual', + type: 'conciencia', + prerequisites: [`num5-suma-${a}-${b}`], + content: { + instruction: `${a} + ${b} es lo mismo que...`, + text: `${'●'.repeat(a)} + ${'●'.repeat(b)}`, + audioText: `Juntamos ${a} y ${b}, tenemos ${r}`, + options: [ + { label: '●'.repeat(r), correct: true, id: String(r) }, + { label: '●'.repeat(r + 1), correct: false, id: String(r + 1) }, + { label: '●'.repeat(Math.max(1, r - 1)), correct: false, id: String(r - 1) }, + ], + emoji: '➕', + }, + functionalContext: 'Visualizar la suma como unión de conjuntos.', + errorType: 'discriminacion-auditiva', + }) +} + +const stage: Stage = { + id: 5, + name: 'Sumas simples (1-10)', + description: 'Sumar números del 1 al 10 con ayudas visuales y ejercicios progresivos.', + exercises, +} + +export default stage diff --git a/curriculum/numeros/etapa-6.ts b/curriculum/numeros/etapa-6.ts new file mode 100644 index 0000000..9159f8e --- /dev/null +++ b/curriculum/numeros/etapa-6.ts @@ -0,0 +1,95 @@ +import type { Stage } from '../types' + +const restas = [ + { a: 2, b: 1, r: 1, emoji: '🍎-🍎' }, + { a: 3, b: 1, r: 2, emoji: '🍎🍎-🍎' }, + { a: 3, b: 2, r: 1, emoji: '🍎🍎-🍎🍎' }, + { a: 4, b: 1, r: 3, emoji: '🍪🍪🍪🍪-🍪' }, + { a: 4, b: 2, r: 2, emoji: '🍪🍪🍪🍪-🍪🍪' }, + { a: 5, b: 1, r: 4, emoji: '⭐-⭐' }, + { a: 5, b: 2, r: 3, emoji: '⭐⭐-⭐⭐' }, + { a: 5, b: 3, r: 2, emoji: '⭐⭐⭐-⭐⭐⭐' }, + { a: 5, b: 4, r: 1, emoji: '⭐⭐⭐⭐-⭐⭐⭐⭐' }, + { a: 6, b: 1, r: 5, emoji: '🌸-🌸' }, + { a: 6, b: 2, r: 4, emoji: '🌸🌸-🌸🌸' }, + { a: 6, b: 3, r: 3, emoji: '🌸🌸🌸-🌸🌸🌸' }, + { a: 7, b: 2, r: 5, emoji: '🌻🌻-🌻🌻' }, + { a: 7, b: 3, r: 4, emoji: '🌻🌻🌻-🌻🌻🌻' }, + { a: 8, b: 3, r: 5, emoji: '🍄🍄🍄-🍄🍄🍄' }, + { a: 8, b: 4, r: 4, emoji: '🍄🍄🍄🍄-🍄🍄🍄🍄' }, + { a: 9, b: 4, r: 5, emoji: '🍂🍂🍂🍂-🍂🍂🍂🍂' }, + { a: 9, b: 5, r: 4, emoji: '🍂🍂🍂🍂🍂-🍂🍂🍂🍂🍂' }, + { a: 10, b: 1, r: 9, emoji: '💎-💎' }, + { a: 10, b: 2, r: 8, emoji: '💎💎-💎💎' }, + { a: 10, b: 3, r: 7, emoji: '💎💎💎-💎💎💎' }, + { a: 10, b: 4, r: 6, emoji: '💎💎💎💎-💎💎💎💎' }, + { a: 10, b: 5, r: 5, emoji: '💎💎💎💎💎-💎💎💎💎💎' }, + { a: 8, b: 5, r: 3, emoji: '🍀🍀🍀🍀🍀-🍀🍀🍀🍀🍀' }, + { a: 7, b: 5, r: 2, emoji: '🌺🌺🌺🌺🌺-🌺🌺🌺🌺🌺' }, + { a: 6, b: 5, r: 1, emoji: '🌷🌷🌷🌷🌷-🌷🌷🌷🌷🌷' }, +] + +const exercises: any[] = [] + +for (const { a, b, r, emoji } of restas) { + exercises.push({ + id: `num6-resta-${a}-${b}`, + stage: 6, + skillCode: 'resta', + type: 'problema', + prerequisites: a <= 5 ? ['num5-suma-visual-1-1'] : [`num6-resta-${a - 1}-${Math.max(1, b - 1)}` || 'num5-suma-10-1'], + content: { + instruction: `¿Cuánto es ${a} - ${b}?`, + text: `${a} - ${b} = ?`, + audioText: `${a} menos ${b}`, + emoji, + options: [ + ...Array.from({ length: 4 }, (_, i) => r - 2 + i + (i >= 2 ? 2 : 0)) + .filter(j => j >= 0 && j <= 10 && j !== r) + .slice(0, 2) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + { label: String(r), correct: true, id: String(r) }, + ].sort(() => Math.random() - 0.5), + }, + functionalContext: 'Restar números pequeños.', + errorType: 'discriminacion-auditiva', + }) +} + +// Visual subtraction: take away +const visuales = [ + { a: 3, b: 1, r: 2 }, + { a: 4, b: 2, r: 2 }, + { a: 5, b: 3, r: 2 }, +] +for (const { a, b, r } of visuales) { + exercises.push({ + id: `num6-resta-visual-${a}-${b}`, + stage: 6, + skillCode: 'resta-visual', + type: 'conciencia', + prerequisites: [`num6-resta-${a}-${b}`], + content: { + instruction: `${a} - ${b} = ? Sacamos ${b}`, + text: `${'🟢'.repeat(a)}\nSacamos ${'🔴'.repeat(b)}\nQuedan...`, + audioText: `Sacamos ${b}, quedan ${r}`, + options: [ + { label: '🟢'.repeat(r), correct: true, id: String(r) }, + { label: '🟢'.repeat(r + 2), correct: false, id: String(r + 2) }, + { label: '🟢'.repeat(Math.max(1, r + 1)), correct: false, id: String(r + 1) }, + ], + emoji: '➖', + }, + functionalContext: 'Visualizar la resta como quitar elementos.', + errorType: 'discriminacion-auditiva', + }) +} + +const stage: Stage = { + id: 6, + name: 'Restas simples (1-10)', + description: 'Restar números del 1 al 10 con ayudas visuales.', + exercises, +} + +export default stage diff --git a/curriculum/numeros/etapa-7.ts b/curriculum/numeros/etapa-7.ts new file mode 100644 index 0000000..7a3cf30 --- /dev/null +++ b/curriculum/numeros/etapa-7.ts @@ -0,0 +1,103 @@ +import type { Stage } from '../types' + +const numeros2d = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 30, 33, 40, 44, 50] + +const exercises: any[] = [] + +for (const n of numeros2d) { + const dec = Math.floor(n / 10) + const uni = n % 10 + const decStr = '🔟'.repeat(dec) + const uniStr = '●'.repeat(uni) + + exercises.push({ + id: `num7-componer-${n}`, + stage: 7, + skillCode: 'valor-posicional', + type: 'eleccion', + prerequisites: n <= 15 ? ['num6-resta-visual-3-1'] : [`num7-componer-${numeros2d[numeros2d.indexOf(n) - 1]}`], + content: { + instruction: `${dec} decenas + ${uni} unidades = ?`, + text: `${dec}×10 + ${uni}×1 = ?`, + audioText: `${n}: ${dec} decenas y ${uni} unidades`, + options: [ + ...Array.from({ length: 4 }, (_, i) => n - 2 + i + (i >= 2 ? 2 : 0)) + .filter(j => j >= 10 && j <= 55 && j !== n) + .slice(0, 2) + .map(j => ({ label: String(j), correct: false, id: String(j) })), + { label: String(n), correct: true, id: String(n) }, + ].sort(() => Math.random() - 0.5), + emoji: decStr, + }, + functionalContext: 'Componer números de 2 dígitos en decenas y unidades.', + errorType: 'confusion-letra', + }) +} + +// Write the number of tens and units +for (const n of [15, 23, 30, 47, 50]) { + const dec = Math.floor(n / 10) + const uni = n % 10 + exercises.push({ + id: `num7-descomponer-${n}`, + stage: 7, + skillCode: 'valor-posicional', + type: 'conciencia', + prerequisites: [`num7-componer-${n}`], + content: { + instruction: `¿Cuántas decenas y unidades tiene ${n}?`, + text: `${n} = ? decenas + ? unidades`, + audioText: `${n} tiene ${dec} decenas y ${uni} unidades`, + options: [ + { label: `${dec} dec, ${uni} uni`, correct: true, id: `d${dec}u${uni}` }, + { label: `${uni} dec, ${dec} uni`, correct: false, id: `d${uni}u${dec}` }, + { label: `${dec + 1} dec, ${Math.max(0, uni - 1)} uni`, correct: false, id: `d${dec + 1}u${Math.max(0, uni - 1)}` }, + ], + emoji: '🔟', + }, + functionalContext: 'Descomponer números en decenas y unidades.', + errorType: 'inversion-letra', + }) +} + +// Compare 2-digit numbers +const comparaciones = [ + { a: 15, b: 20 }, + { a: 23, b: 18 }, + { a: 30, b: 25 }, + { a: 42, b: 39 }, + { a: 50, b: 49 }, +] + +for (const { a, b } of comparaciones) { + const mayor = a > b ? a : b + const menor = a < b ? a : b + exercises.push({ + id: `num7-comparar-${a}-${b}`, + stage: 7, + skillCode: 'comparacion-2d', + type: 'eleccion', + prerequisites: [`num7-descomponer-${mayor}`], + content: { + instruction: `¿Cuál número es más grande?`, + text: `${a} vs ${b}`, + audioText: `¿Cuál es más grande?`, + options: [ + { label: String(a), correct: a === mayor, id: String(a) }, + { label: String(b), correct: b === mayor, id: String(b) }, + ], + emoji: '📏', + }, + functionalContext: 'Comparar números de 2 dígitos.', + errorType: 'confusion-letra', + }) +} + +const stage: Stage = { + id: 7, + name: 'Decenas y unidades', + description: 'Componer y descomponer números de 2 dígitos en decenas y unidades. Comparar números.', + exercises, +} + +export default stage diff --git a/curriculum/numeros/etapa-8.ts b/curriculum/numeros/etapa-8.ts new file mode 100644 index 0000000..02212b5 --- /dev/null +++ b/curriculum/numeros/etapa-8.ts @@ -0,0 +1,214 @@ +import type { Stage } from '../types' + +const problemas = [ + { + text: 'Tenés 2 manzanas. Mamá te da 1 más. ¿Cuántas manzanas tenés?', + respuesta: 3, + opciones: [2, 3, 4], + emoji: '🍎', + op: 'suma', + }, + { + text: 'Hay 3 pájaros en el árbol. 1 se va volando. ¿Cuántos quedan?', + respuesta: 2, + opciones: [1, 2, 4], + emoji: '🐦', + op: 'resta', + }, + { + text: 'Tenés 4 galletitas. Te comés 1. ¿Cuántas te quedan?', + respuesta: 3, + opciones: [2, 3, 5], + emoji: '🍪', + op: 'resta', + }, + { + text: 'En el patio hay 2 perros y 3 gatos. ¿Cuántos animales hay?', + respuesta: 5, + opciones: [3, 4, 5], + emoji: '🐕', + op: 'suma', + }, + { + text: 'Tenés 5 lápices. Tu amiga te presta 2 más. ¿Cuántos lápices tenés ahora?', + respuesta: 7, + opciones: [5, 7, 8], + emoji: '✏️', + op: 'suma', + }, + { + text: 'Hay 6 flores en el jardín. Cortás 2 para mamá. ¿Cuántas quedan?', + respuesta: 4, + opciones: [3, 4, 8], + emoji: '🌸', + op: 'resta', + }, + { + text: 'En la mesa hay 3 platos y 3 vasos. ¿Cuántas cosas hay?', + respuesta: 6, + opciones: [5, 6, 9], + emoji: '🍽️', + op: 'suma', + }, + { + text: 'Tenés 7 stickers. Le das 2 a tu hermana. ¿Cuántos te quedan?', + respuesta: 5, + opciones: [4, 5, 9], + emoji: '⭐', + op: 'resta', + }, + { + text: 'Compraste 8 globos. 3 se explotan. ¿Cuántos globos quedan?', + respuesta: 5, + opciones: [3, 5, 11], + emoji: '🎈', + op: 'resta', + }, + { + text: 'En la pecera hay 4 peces naranjas y 2 azules. ¿Cuántos peces hay?', + respuesta: 6, + opciones: [4, 6, 8], + emoji: '🐠', + op: 'suma', + }, + { + text: 'Tenés 9 bloques. Usás 4 para hacer una torre. ¿Cuántos sobran?', + respuesta: 5, + opciones: [4, 5, 13], + emoji: '🧱', + op: 'resta', + }, + { + text: 'En tu plato hay 5 frutillas y 3 uvas. ¿Cuántas frutas tenés?', + respuesta: 8, + opciones: [5, 8, 15], + emoji: '🍓', + op: 'suma', + }, + { + text: 'Hay 10 libros en el estante. Sacás 4 para leer. ¿Cuántos quedan?', + respuesta: 6, + opciones: [4, 6, 14], + emoji: '📚', + op: 'resta', + }, + { + text: 'Tenés 2 manos. Cada mano tiene 5 dedos. ¿Cuántos dedos tenés en total?', + respuesta: 10, + opciones: [5, 7, 10], + emoji: '🖐️', + op: 'suma', + }, + { + text: 'Compartís 6 caramelos con tu amiga. Le das la mitad. ¿Cuántos te quedan?', + respuesta: 3, + opciones: [2, 3, 6], + emoji: '🍬', + op: 'resta', + }, + { + text: 'Hay 7 días en la semana. Ya pasaron 3. ¿Cuántos días quedan?', + respuesta: 4, + opciones: [3, 4, 10], + emoji: '📅', + op: 'resta', + }, + { + text: 'Viste 3 mariquitas en una hoja y 2 en otra. ¿Cuántas viste?', + respuesta: 5, + opciones: [3, 5, 6], + emoji: '🐞', + op: 'suma', + }, + { + text: 'Tenés 4 monedas. Encontrás 3 más en tu bolsillo. ¿Cuántas tenés?', + respuesta: 7, + opciones: [4, 7, 12], + emoji: '🪙', + op: 'suma', + }, + { + text: 'Son las 3 de la tarde. Pasaron 2 horas. ¿Qué hora es?', + respuesta: 5, + opciones: [1, 5, 6], + emoji: '🕐', + op: 'suma', + }, + { + text: 'El nene tiene 8 años. La nena tiene 3 años menos. ¿Cuántos años tiene la nena?', + respuesta: 5, + opciones: [3, 5, 11], + emoji: '👶', + op: 'resta', + }, + { + text: 'En la granja hay 2 caballos, 3 vacas y 1 cerdo. ¿Cuántos animales?', + respuesta: 6, + opciones: [4, 6, 7], + emoji: '🐴', + op: 'suma', + }, + { + text: 'Papá horneó 12 galletitas. Te comiste 2. ¿Cuántas quedan?', + respuesta: 10, + opciones: [8, 10, 14], + emoji: '🍪', + op: 'resta', + }, + { + text: 'Un tren tiene 4 vagones rojos y 4 azules. ¿Cuántos vagones tiene?', + respuesta: 8, + opciones: [4, 8, 16], + emoji: '🚂', + op: 'suma', + }, + { + text: 'Tenés 5 años. Cuando cumplas 5 más, ¿cuántos vas a tener?', + respuesta: 10, + opciones: [5, 10, 15], + emoji: '🎂', + op: 'suma', + }, + { + text: 'El árbol tiene 10 manzanas. Caen 3. ¿Cuántas manzanas quedan en el árbol?', + respuesta: 7, + opciones: [3, 7, 13], + emoji: '🍎', + op: 'resta', + }, +] + +const exercises: any[] = [] + +for (let i = 0; i < problemas.length; i++) { + const p = problemas[i] + exercises.push({ + id: `num8-problema-${i + 1}`, + stage: 8, + skillCode: 'problemas-cotidianos', + type: 'problema', + prerequisites: i === 0 ? ['num7-comparar-15-20'] : [`num8-problema-${i}`], + content: { + instruction: 'Escuchá bien el problema', + text: p.text, + audioText: p.text, + emoji: p.emoji, + options: p.opciones.map(o => ({ + label: String(o), + correct: o === p.respuesta, + id: String(o), + })).sort(() => Math.random() - 0.5), + }, + functionalContext: 'Resolver problemas matemáticos de la vida cotidiana.', + errorType: p.op === 'resta' ? 'inversion-letra' : 'discriminacion-auditiva', + }) +} + +const stage: Stage = { + id: 8, + name: 'Problemas de la vida cotidiana', + description: 'Resolver problemas cotidianos que requieren sumar y restar números hasta 12.', + exercises, +} + +export default stage diff --git a/curriculum/types.ts b/curriculum/types.ts new file mode 100644 index 0000000..457a82a --- /dev/null +++ b/curriculum/types.ts @@ -0,0 +1,54 @@ +export type ExerciseType = + | "sensibilizacion" + | "conciencia" + | "trazo" + | "eleccion" + | "emparejar" + | "secuencia" + | "palabra" + | "problema" + | "lectura" + +export type ErrorType = + | "discriminacion-auditiva" + | "confusion-letra" + | "silaba-dificil" + | "omision-letra" + | "inversion-letra" + | null + +export interface ExerciseContent { + instruction: string + text?: string + options?: { label: string; correct: boolean; id?: string }[] + pairs?: { left: string; right: string; id: string }[] + sequence?: string[] + emoji?: string + color?: string + audioText?: string + imageEmoji?: string +} + +export interface Exercise { + id: string + stage: number + skillCode: string + type: ExerciseType + content: ExerciseContent + prerequisites: string[] + errorType?: ErrorType + functionalContext?: string +} + +export interface Stage { + id: number + name: string + description: string + exercises: Exercise[] +} + +export interface TopicCurriculum { + topic: string + name: string + stages: Stage[] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e276d1b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,45 @@ +services: + postgres: + image: postgres:16-alpine + container_name: edueasy-db + restart: unless-stopped + environment: + POSTGRES_DB: edueasy + POSTGRES_USER: edueasy + POSTGRES_PASSWORD: edueasy_pass + volumes: + - edueasy_pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U edueasy -d edueasy"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - caddy + + app: + build: + context: . + dockerfile: Dockerfile + container_name: edueasy + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + environment: + DATABASE_URL: "postgresql://edueasy:edueasy_pass@postgres:5432/edueasy?schema=public" + BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET:-change-me-in-production}" + BETTER_AUTH_URL: "${BETTER_AUTH_URL:-https://mate.cbcren.online}" + NODE_ENV: production + networks: + - caddy + labels: + caddy: mate.cbcren.online + caddy.reverse_proxy: "{{upstreams 3000}}" + +networks: + caddy: + external: true + +volumes: + edueasy_pgdata: diff --git a/dominios-libres.md b/dominios-libres.md new file mode 100644 index 0000000..597be0d --- /dev/null +++ b/dominios-libres.md @@ -0,0 +1,40 @@ +# Dominios Libres para Edueasy + +> Generado: 2026-07-20 +> IP del servidor: `194.163.191.200` + +## Dominios disponibles (sin contenedor activo) + +| Dominio | Ex-servicio | Ideal para | +|---------|-------------|------------| +| `mate.cbcren.online` | math-frontend + math-backend | API, dashboard, web app | +| `manga.cbcren.online` | Kavita (lector manga) | Static site, app web | +| `eco2.cbcren.online` | eco2-app (economía) | App Node.js / web | +| `simulacro.cbcren.online` | simulacro-app (flask) | Python app, web app | + +> **Nota:** `am.cbcren.online` (Open WebUI) y `demo2.cbcren.online` (pymesbot) no están libres — responden 401/307, probablemente con contenedor activo. + +## Cómo usar un dominio + +```bash +# 1. Crear el contenedor conectado a la red caddy +docker run -d --name edueasy-api --network caddy edueasy-image + +# 2. Editar /home/ren/immich/Caddyfile agregando: +# mate.cbcren.online { +# reverse_proxy edueasy-api:3000 +# } + +# 3. Recargar Caddy sin downtime +docker exec caddy-ingress caddy reload --config /etc/caddy/Caddyfile +``` + +## Config existente en Caddy (referencia) + +Cada dominio ya está definido en `/home/ren/immich/Caddyfile`. Solo hay que cambiar el `reverse_proxy` al nuevo contenedor: + +```caddy +mate.cbcren.online { + reverse_proxy nuevo-contenedor:3000 +} +``` diff --git a/e2e/api-curriculum.spec.ts b/e2e/api-curriculum.spec.ts new file mode 100644 index 0000000..bc06f48 --- /dev/null +++ b/e2e/api-curriculum.spec.ts @@ -0,0 +1,71 @@ +import { test, expect } from "@playwright/test" + +test.describe("Curriculum API", () => { + test("TTS endpoint generates audio", async ({ request }) => { + const res = await request.get("/api/tts?text=hola%20mundo") + expect(res.ok()).toBeTruthy() + expect(res.headers()["content-type"]).toBe("audio/wav") + const body = await res.body() + expect(body.length).toBeGreaterThan(1000) + }) + + test("TTS requires text param", async ({ request }) => { + const res = await request.get("/api/tts") + expect(res.status()).toBe(400) + const json = await res.json() + expect(json.error).toContain("text param") + }) + + test("TTS text too long returns 400", async ({ request }) => { + const long = "a".repeat(250) + const res = await request.get(`/api/tts?text=${long}`) + expect(res.status()).toBe(400) + }) + + test("curriculum/next requires childId", async ({ request }) => { + const res = await request.get("/api/curriculum/next") + expect(res.status()).toBe(400) + const json = await res.json() + expect(json.error).toContain("childId") + }) + + test("curriculum/next accepts childId param", async ({ request }) => { + const res = await request.get("/api/curriculum/next?childId=any&topic=lectura") + expect(res.status()).toBe(200) + const json = await res.json() + expect(json).toBeDefined() + }) + + test("curriculum/grade missing fields returns 400", async ({ request }) => { + const res = await request.post("/api/curriculum/grade", { + data: {}, + }) + expect(res.status()).toBe(400) + const json = await res.json() + expect(json.error).toBe("Missing required fields") + }) + + test("curriculum/grade validates required fields", async ({ request }) => { + const res = await request.post("/api/curriculum/grade", { + data: { childId: "test", exerciseId: "ex1", skillCode: "test" }, + headers: { "Content-Type": "application/json" }, + }) + expect(res.status()).toBe(400) + const json = await res.json() + expect(json.error).toBe("No active session") + }) + + test("curriculum/progress requires childId", async ({ request }) => { + const res = await request.get("/api/curriculum/progress") + expect(res.status()).toBe(400) + const json = await res.json() + expect(json.error).toContain("childId") + }) + + test("dashboard/summary requires childId", async ({ request }) => { + const res = await request.get("/api/dashboard/summary") + expect(res.status()).toBe(400) + const json = await res.json() + expect(json.error).toContain("childId") + }) +}) diff --git a/e2e/exercise-content.spec.ts b/e2e/exercise-content.spec.ts new file mode 100644 index 0000000..6ab6efb --- /dev/null +++ b/e2e/exercise-content.spec.ts @@ -0,0 +1,78 @@ +import { test, expect } from "@playwright/test" +import { readFileSync } from "fs" +import { join } from "path" + +const lecturaStages = 9 +const numerosStages = 8 + +test.describe("Exercise content integrity", () => { + for (let i = 1; i <= lecturaStages; i++) { + test(`lectura etapa ${i} file exists and has exercises`, () => { + const file = join(process.cwd(), "curriculum", "lectura", `etapa-${i}.ts`) + const content = readFileSync(file, "utf-8") + expect(content.length).toBeGreaterThan(200) + expect(content).toContain("stage") + expect(content).toContain("exercises") + }) + } + + for (let i = 1; i <= numerosStages; i++) { + test(`numeros etapa ${i} file exists and has exercises`, () => { + const file = join(process.cwd(), "curriculum", "numeros", `etapa-${i}.ts`) + const content = readFileSync(file, "utf-8") + expect(content.length).toBeGreaterThan(200) + expect(content).toContain("stage") + expect(content).toContain("exercises") + }) + } + + test("curriculum index imports all stages via loadStages", () => { + const file = join(process.cwd(), "curriculum", "index.ts") + const content = readFileSync(file, "utf-8") + expect(content).toContain("initCurriculum") + expect(content).toContain("loadStages") + expect(content).toContain("[1, 2, 3, 4, 5, 6, 7, 8, 9]") + }) + + test("type definitions export required types", () => { + const file = join(process.cwd(), "curriculum", "types.ts") + const content = readFileSync(file, "utf-8") + expect(content).toContain("export type Exercise") + expect(content).toContain("export interface Stage") + expect(content).toContain("export type ExerciseType") + expect(content).toContain("export type ErrorType") + }) + + test("exercise dispatcher maps all exercise types", () => { + const file = join(process.cwd(), "components", "exercises", "exercise-dispatcher.tsx") + const content = readFileSync(file, "utf-8") + const types = ["sensibilizacion", "conciencia", "trazo", "eleccion", "emparejar", "secuencia", "palabra", "problema", "lectura"] + for (const t of types) { + expect(content).toContain(t) + } + }) + + test("all exercise component files exist", () => { + const components = [ + "ejercicio-sensibilizacion", + "ejercicio-conciencia", + "ejercicio-eleccion", + "ejercicio-secuencia", + "ejercicio-palabra", + "ejercicio-lectura", + "ejercicio-trazo", + "exercise-dispatcher", + ] + for (const c of components) { + const file = join(process.cwd(), "components", "exercises", `${c}.tsx`) + const content = readFileSync(file, "utf-8") + expect(content.length).toBeGreaterThan(100) + } + }) + + test("TTS route exists", () => { + const file = join(process.cwd(), "app", "api", "tts", "route.ts") + const content = readFileSync(file, "utf-8") + expect(content).toContain("espeak-ng") + }) +}) diff --git a/e2e/pairing-flow.spec.ts b/e2e/pairing-flow.spec.ts new file mode 100644 index 0000000..30bf0f8 --- /dev/null +++ b/e2e/pairing-flow.spec.ts @@ -0,0 +1,50 @@ +import { test, expect } from "@playwright/test" + +async function dismissAudioUnlock(page: any) { + const btn = page.getByRole("button", { name: /tocá/i }) + if (await btn.isVisible({ timeout: 2000 }).catch(() => false)) { + await btn.click() + await page.waitForTimeout(1200) + } +} + +test.describe("Pairing flow", () => { + + test("pairing page loads form with code input", async ({ page }) => { + await page.goto("/child/pairing", { waitUntil: "networkidle", timeout: 15000 }) + await dismissAudioUnlock(page) + await expect(page.getByPlaceholder("Código")).toBeVisible({ timeout: 10000 }) + }) + + test("pairing page has submit button", async ({ page }) => { + await page.goto("/child/pairing", { waitUntil: "networkidle", timeout: 15000 }) + await dismissAudioUnlock(page) + await expect(page.getByRole("button", { name: "Listo" })).toBeVisible({ timeout: 10000 }) + }) + + test("pairing code input accepts text", async ({ page }) => { + await page.goto("/child/pairing", { waitUntil: "networkidle", timeout: 15000 }) + await dismissAudioUnlock(page) + const input = page.getByPlaceholder("Código") + await input.fill("ABCD1234") + await expect(input).toHaveValue("ABCD1234") + }) + + test("lectura page shows pairing message when not paired", async ({ page }) => { + await page.goto("/child/lectura", { waitUntil: "networkidle", timeout: 15000 }) + await dismissAudioUnlock(page) + await expect(page.getByText("emparejar", { exact: false }).first()).toBeVisible({ timeout: 10000 }) + }) + + test("numeros page shows pairing message when not paired", async ({ page }) => { + await page.goto("/child/numeros", { waitUntil: "networkidle", timeout: 15000 }) + await dismissAudioUnlock(page) + await expect(page.getByText("emparejar", { exact: false }).first()).toBeVisible({ timeout: 10000 }) + }) + + test("lectura page has ir a emparejar button", async ({ page }) => { + await page.goto("/child/lectura", { waitUntil: "networkidle", timeout: 15000 }) + await dismissAudioUnlock(page) + await expect(page.getByRole("button", { name: /emparejar/i })).toBeVisible({ timeout: 10000 }) + }) +}) diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..0e1a156 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,14 @@ +import { dirname } from "path" +import { fileURLToPath } from "url" +import { FlatCompat } from "@eslint/eslintrc" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +const compat = new FlatCompat({ baseDirectory: __dirname }) + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals"), +] + +export default eslintConfig diff --git a/lib/auth-client.ts b/lib/auth-client.ts new file mode 100644 index 0000000..cba42e1 --- /dev/null +++ b/lib/auth-client.ts @@ -0,0 +1,9 @@ +"use client" + +import { createAuthClient } from "better-auth/react" + +export const authClient = createAuthClient({ + baseURL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000", +}) + +export const { signIn, signUp, useSession } = authClient diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 0000000..1d76528 --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,31 @@ +import { betterAuth } from "better-auth" +import { prismaAdapter } from "better-auth/adapters/prisma" +import { prisma } from "@/lib/db" + +export const auth = betterAuth({ + database: prismaAdapter(prisma, { + provider: "postgresql", + }), + emailAndPassword: { + enabled: true, + }, + user: { + modelName: "Parent", + }, + session: { + modelName: "Session", + }, + databaseHooks: { + user: { + create: { + after: async (user) => { + const family = await prisma.family.create({ data: { name: "" } }) + await prisma.parent.update({ + where: { id: user.id }, + data: { familyId: family.id }, + }) + }, + }, + }, + }, +}) diff --git a/lib/curriculum/engine.ts b/lib/curriculum/engine.ts new file mode 100644 index 0000000..032e613 --- /dev/null +++ b/lib/curriculum/engine.ts @@ -0,0 +1,124 @@ +import { prisma } from "@/lib/db" +import { initCurriculum, getLecturaStages, getNumerosStages, getAllLecturaExercises } from "@/curriculum/index" +import type { Exercise } from "@/curriculum/types" + +const STAGE_THRESHOLD = 0.8 + +async function ensureInitialStage(childId: string, topic: string) { + try { + const exists = await prisma.curriculumProgress.findFirst({ + where: { childId, topic }, + }) + if (!exists) { + await prisma.curriculumProgress.create({ + data: { childId, topic, stage: 1 }, + }) + } + } catch { + // Child may not exist yet in FK constraint — proceed gracefully + } +} + +export async function getCurrentStage(childId: string, topic: string) { + await ensureInitialStage(childId, topic) + const progress = await prisma.curriculumProgress.findFirst({ + where: { childId, topic }, + orderBy: { stage: "asc" }, + }) + return progress +} + +export async function getNextExercise( + childId: string, + topic: "lectura" | "numeros", +): Promise { + await initCurriculum() + await ensureInitialStage(childId, topic) + + const progress = await prisma.curriculumProgress.findFirst({ + where: { childId, topic, completedAt: null }, + orderBy: { stage: "asc" }, + }) + + if (!progress) return null + + const stages = topic === "lectura" ? getLecturaStages() : getNumerosStages() + const stage = stages[progress.stage] + if (!stage || !stage.exercises.length) return null + + const allAttemps = await prisma.skillAttempt.findMany({ + where: { exerciseId: { not: null }, sessionLog: { childId } }, + select: { exerciseId: true, correct: true }, + }) + + const mastered = new Set( + allAttemps + .filter((a) => a.correct) + .map((a) => a.exerciseId), + ) + + const attempted = new Set(allAttemps.map((a) => a.exerciseId)) + + const available = stage.exercises.filter((ex) => { + if (mastered.has(ex.id)) return false + const prereqsMet = ex.prerequisites.every((p) => mastered.has(p)) + return prereqsMet + }) + + if (available.length === 0) { + if (attempted.size > 0 && stage.exercises.length > 0) { + const masteryRatio = mastered.size / stage.exercises.length + if (masteryRatio >= STAGE_THRESHOLD) { + await advanceStage(childId, topic, progress.stage) + return getNextExercise(childId, topic) + } + } + return null + } + + const fsrsCards = await prisma.fsrsCard.findMany({ + where: { childId, skillCode: { in: available.map((e) => e.skillCode) } }, + }) + + const dueMap = new Map(fsrsCards.map((c) => [c.skillCode, c.dueAt.getTime()])) + const now = Date.now() + + const unsorted = [...available] + unsorted.sort((a, b) => { + const dueA = dueMap.get(a.skillCode) ?? 0 + const dueB = dueMap.get(b.skillCode) ?? 0 + const isDueA = dueA <= now ? 0 : 1 + const isDueB = dueB <= now ? 0 : 1 + if (isDueA !== isDueB) return isDueA - isDueB + if (!attempted.has(a.id) && !attempted.has(b.id)) return 0 + if (!attempted.has(a.id)) return -1 + if (!attempted.has(b.id)) return 1 + return dueA - dueB + }) + + return unsorted[0] || null +} + +async function advanceStage(childId: string, topic: string, currentStage: number) { + await prisma.curriculumProgress.updateMany({ + where: { childId, topic, stage: currentStage }, + data: { completedAt: new Date() }, + }) + const nextStage = currentStage + 1 + const exists = await prisma.curriculumProgress.findUnique({ + where: { childId_topic_stage: { childId, topic, stage: nextStage } }, + }) + if (!exists) { + await prisma.curriculumProgress.create({ + data: { childId, topic, stage: nextStage }, + }) + } +} + +export function getErrorTypeForExercise( + exercise: Exercise, + correct: boolean, +): string | null { + if (correct) return null + return exercise.errorType || "discriminacion-auditiva" +} diff --git a/lib/db.ts b/lib/db.ts new file mode 100644 index 0000000..17cca96 --- /dev/null +++ b/lib/db.ts @@ -0,0 +1,9 @@ +import { PrismaClient } from "@prisma/client" + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined +} + +export const prisma = globalForPrisma.prisma ?? new PrismaClient() + +if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma diff --git a/lib/fsrs.ts b/lib/fsrs.ts new file mode 100644 index 0000000..59da7d1 --- /dev/null +++ b/lib/fsrs.ts @@ -0,0 +1,50 @@ +import { fsrs as createFSRS, createEmptyCard, Rating, type Card, type Grade } from "ts-fsrs" + +const scheduler = createFSRS({ maximum_interval: 365, request_retention: 0.9 }) + +export type FsrsGrade = "again" | "hard" | "good" | "easy" + +const gradeMap: Record = { + again: Rating.Again, + hard: Rating.Hard, + good: Rating.Good, + easy: Rating.Easy, +} + +export function buildFsrsCard(params: { + stability?: number + difficulty?: number + reps?: number + lapses?: number + due?: Date +}): Card { + const card = createEmptyCard(params.due ?? new Date()) + return { + ...card, + stability: params.stability ?? card.stability, + difficulty: params.difficulty ?? card.difficulty, + reps: params.reps ?? 0, + lapses: params.lapses ?? 0, + due: params.due ?? card.due, + } +} + +export function gradeCard(card: Card, grade: FsrsGrade, now: Date = new Date()) { + const result = scheduler.next(card, now, gradeMap[grade]) + return { + card: result.card, + log: result.log, + } +} + +export function getRetrievability(card: Card, now: Date = new Date()): number { + const r = scheduler.get_retrievability(card, now, false) + return typeof r === "number" ? r : parseFloat(r) +} + +export function skillCodeFromGrade(correct: boolean, responseMs: number): FsrsGrade { + if (!correct) return "again" + if (responseMs > 8000) return "hard" + if (responseMs > 4000) return "good" + return "easy" +} diff --git a/lib/pedagogia/borel-maisonny.ts b/lib/pedagogia/borel-maisonny.ts new file mode 100644 index 0000000..919d827 --- /dev/null +++ b/lib/pedagogia/borel-maisonny.ts @@ -0,0 +1,67 @@ +export interface Fonema { + letra: string + fonema: string + palabra: string + imageEmoji: string + gesto: string // description of Borel-Maisonny gesture + color: string + orden: number +} + +// Vocales first (Jolly Phonics order adapted for Spanish) +export const vocales: Fonema[] = [ + { + letra: "a", + fonema: "a", + palabra: "avión", + imageEmoji: "✈️", + gesto: "Abrir la mano como si saludaras, con los dedos juntos.", + color: "#E74C3C", + orden: 1, + }, + { + letra: "e", + fonema: "e", + palabra: "elefante", + imageEmoji: "🐘", + gesto: "Señalar hacia la derecha con la mano abierta.", + color: "#3498DB", + orden: 2, + }, + { + letra: "i", + fonema: "i", + palabra: "iglú", + imageEmoji: "🏔️", + gesto: "Señalar hacia arriba con el dedo índice.", + color: "#9B59B6", + orden: 3, + }, + { + letra: "o", + fonema: "o", + palabra: "oso", + imageEmoji: "🐻", + gesto: "Hacer un círculo con la mano frente a la boca.", + color: "#F39C12", + orden: 4, + }, + { + letra: "u", + fonema: "u", + palabra: "uva", + imageEmoji: "🍇", + gesto: "Estirar los dos brazos hacia adelante como un tubo.", + color: "#1ABC9C", + orden: 5, + }, +] + +export function getFonema(letra: string): Fonema | undefined { + return vocales.find((v) => v.letra === letra) +} + +export function getGestoDescription(letra: string): string { + const f = getFonema(letra) + return f?.gesto || "" +} diff --git a/lib/pedagogia/cpa-progression.ts b/lib/pedagogia/cpa-progression.ts new file mode 100644 index 0000000..b2b82a7 --- /dev/null +++ b/lib/pedagogia/cpa-progression.ts @@ -0,0 +1,56 @@ +export type CPAStage = "concrete" | "pictoric" | "abstract" + +export type GalperinStage = "hand" | "external_speech" | "inner_speech" | "mental" | "mastered" + +export interface CPAState { + stage: CPAStage + currentValue: number + attempts: number + mastered: boolean + galperinStage: GalperinStage +} + +export function getCPAProgression(skillValue: number): CPAStage[] { + return ["concrete", "pictoric", "abstract"] +} + +export function createCPAState(value: number): CPAState { + return { + stage: "concrete", + currentValue: value, + attempts: 0, + mastered: false, + galperinStage: "hand", + } +} + +export function advanceStage(state: CPAState): CPAState { + const stages: CPAStage[] = ["concrete", "pictoric", "abstract"] + const currentIdx = stages.indexOf(state.stage) + if (currentIdx < stages.length - 1) { + return { ...state, stage: stages[currentIdx + 1], galperinStage: "hand" } + } + return { ...state, mastered: true } +} + +export function advanceGalperin(stage: GalperinStage): GalperinStage { + const order: GalperinStage[] = ["hand", "external_speech", "inner_speech", "mental", "mastered"] + const idx = order.indexOf(stage) + if (idx < order.length - 1) return order[idx + 1] + return "mastered" +} + +export function getGalperinInstruction(stage: GalperinStage, value: number): string { + switch (stage) { + case "hand": + return `Tocá ${value} cosas con tu dedo` + case "external_speech": + return "Contá en voz alta mientras tocás" + case "inner_speech": + return "Ahora contá susurrando" + case "mental": + return "Pensá el número en tu cabeza y señalalo" + case "mastered": + return "¡Ya sabés este número!" + } +} diff --git a/lib/pedagogia/cuisenaire.ts b/lib/pedagogia/cuisenaire.ts new file mode 100644 index 0000000..c2edcc5 --- /dev/null +++ b/lib/pedagogia/cuisenaire.ts @@ -0,0 +1,47 @@ +export interface Rod { + value: number + color: string + lengthPx: number + label: string +} + +export const rodColors: Record = { + 1: "#FFFFFF", + 2: "#E74C3C", + 3: "#2ECC71", + 4: "#E91E63", + 5: "#F1C40F", + 6: "#1ABC9C", + 7: "#2C3E50", + 8: "#3498DB", + 9: "#9B59B6", + 10: "#E67E22", +} + +const rodLengths: Record = { + 1: 24, + 2: 44, + 3: 64, + 4: 84, + 5: 104, + 6: 124, + 7: 144, + 8: 164, + 9: 184, + 10: 204, +} + +export function getRods(maxValue: number = 10): Rod[] { + return Array.from({ length: maxValue }, (_, i) => ({ + value: i + 1, + color: rodColors[i + 1], + lengthPx: rodLengths[i + 1], + label: `${i + 1}`, + })) +} + +export function compareRods(a: number, b: number): "mayor" | "menor" | "igual" { + if (a > b) return "mayor" + if (a < b) return "menor" + return "igual" +} diff --git a/lib/pedagogia/errorless.ts b/lib/pedagogia/errorless.ts new file mode 100644 index 0000000..0054a06 --- /dev/null +++ b/lib/pedagogia/errorless.ts @@ -0,0 +1,63 @@ +export type PromptLevel = 0 | 1 | 2 | 3 | 4 + +// Galperin stages mapped to prompt levels +export const GALPERIN_STAGES = { + HAND: 0, // Concrete: full model, show the answer + EXTERNAL_SPEECH: 1, // Prompts the child to say out loud + INNER_SPEECH: 2, // Partial prompt, whisper + MENTAL: 3, // Verbal cue only + MASTERED: 4, // No prompt +} as const + +export interface PromptState { + level: PromptLevel + consecutiveCorrect: number + consecutiveIncorrect: number +} + +export function getInitialState(): PromptState { + return { + level: 0, + consecutiveCorrect: 0, + consecutiveIncorrect: 0, + } +} + +export function evaluateResponse( + state: PromptState, + correct: boolean, + masterThreshold: number = 3, + regressThreshold: number = 2, +): PromptState { + if (correct) { + const newCorrect = state.consecutiveCorrect + 1 + const newLevel = + newCorrect >= masterThreshold + ? (Math.min(state.level + 1, 4) as PromptLevel) + : state.level + return { + level: newLevel, + consecutiveCorrect: newCorrect, + consecutiveIncorrect: 0, + } + } + + const newIncorrect = state.consecutiveIncorrect + 1 + const newLevel = + newIncorrect >= regressThreshold + ? (Math.max(state.level - 1, 0) as PromptLevel) + : state.level + return { + level: newLevel, + consecutiveCorrect: 0, + consecutiveIncorrect: newIncorrect, + } +} + +export function shouldShowPrompt(level: PromptLevel): boolean { + return level < 3 +} + +export function isMastered(level: PromptLevel): boolean { + return level >= 4 +} diff --git a/lib/session-client.ts b/lib/session-client.ts new file mode 100644 index 0000000..09f53f3 --- /dev/null +++ b/lib/session-client.ts @@ -0,0 +1,53 @@ +"use client" + +export interface SkillAttemptPayload { + skillCode: string + promptLevel: number + correct: boolean + responseMs: number +} + +export interface SessionPayload { + childId: string + startedAt: string + endedAt: string + durationSec: number + skillsPracticed: string + attempts: SkillAttemptPayload[] +} + +export async function syncSession(payload: SessionPayload): Promise { + try { + const res = await fetch("/api/sessions/sync", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + childId: payload.childId, + session: { + startedAt: payload.startedAt, + endedAt: payload.endedAt, + durationSec: payload.durationSec, + skillsPracticed: payload.skillsPracticed, + }, + attempts: payload.attempts, + }), + }) + const data = await res.json() + return data.sessionId ?? null + } catch { + return null + } +} + +export async function createMilestone(childId: string, skillCode: string): Promise { + try { + const res = await fetch("/api/milestones", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ childId, skillCode }), + }) + return res.ok + } catch { + return false + } +} diff --git a/lib/speech.ts b/lib/speech.ts new file mode 100644 index 0000000..0a61ee3 --- /dev/null +++ b/lib/speech.ts @@ -0,0 +1,29 @@ +"use client" + +let utterance: SpeechSynthesisUtterance | null = null + +export async function speak( + text: string, + _rate = 0.85, +): Promise { + return new Promise((resolve) => { + stopSpeaking() + + const u = new SpeechSynthesisUtterance(text) + u.lang = "es-AR" + u.rate = _rate + + u.onend = () => resolve() + u.onerror = () => resolve() + + utterance = u + speechSynthesis.speak(u) + }) +} + +export function stopSpeaking(): void { + if (utterance) { + speechSynthesis.cancel() + utterance = null + } +} diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000..d825cfc --- /dev/null +++ b/middleware.ts @@ -0,0 +1,55 @@ +import { NextResponse } from "next/server" +import type { NextRequest } from "next/server" + +const CHILD_HOST = "mate" +const PARENT_HOST = "simulacro" +const isDev = process.env.NODE_ENV === "development" + +export function middleware(request: NextRequest) { + const hostname = request.headers.get("host") || "" + const { pathname } = request.nextUrl + + if ( + pathname.startsWith("/api") || + pathname.startsWith("/_next") || + pathname.startsWith("/static") || + pathname === "/favicon.ico" + ) { + return NextResponse.next() + } + + if (isDev) { + if (pathname.startsWith("/child") || pathname.startsWith("/parent")) { + return NextResponse.next() + } + const url = request.nextUrl.clone() + url.pathname = "/child" + return NextResponse.rewrite(url) + } + + if (hostname.includes(CHILD_HOST)) { + if (pathname === "/" || pathname.startsWith("/child")) { + return NextResponse.next() + } + const url = request.nextUrl.clone() + url.pathname = `/child${pathname === "/" ? "" : pathname}` + return NextResponse.rewrite(url) + } + + if (hostname.includes(PARENT_HOST)) { + if (pathname === "/" || pathname.startsWith("/parent")) { + return NextResponse.next() + } + const url = request.nextUrl.clone() + url.pathname = `/parent${pathname === "/" ? "" : pathname}` + return NextResponse.rewrite(url) + } + + const url = request.nextUrl.clone() + url.pathname = "/child" + return NextResponse.rewrite(url) +} + +export const config = { + matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], +} diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..f2f1261 --- /dev/null +++ b/next.config.ts @@ -0,0 +1,8 @@ +import type { NextConfig } from "next" + +const nextConfig: NextConfig = { + output: "standalone", + images: { unoptimized: true }, +} + +export default nextConfig diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c4de66c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7681 @@ +{ + "name": "edueasy", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "edueasy", + "version": "0.1.0", + "dependencies": { + "@prisma/client": "^5.20.0", + "@radix-ui/react-avatar": "^1.2.3", + "@radix-ui/react-label": "^2.1.12", + "@radix-ui/react-progress": "^1.1.13", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.18", + "@use-gesture/react": "^10.3.0", + "better-auth": "^1.0.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "framer-motion": "^11.0.0", + "lucide-react": "^0.400.0", + "next": "^15.0.0", + "perfect-freehand": "^1.2.0", + "prisma": "^5.20.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "recharts": "^2.12.0", + "tailwind-merge": "^2.5.0", + "tailwindcss-animate": "^1.0.7", + "ts-fsrs": "^4.0.0" + }, + "devDependencies": { + "@playwright/test": "^1.61.1", + "@types/node": "^20.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "autoprefixer": "^10.5.4", + "eslint": "^8.57.0", + "eslint-config-next": "^15.0.0", + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.6.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@better-auth/core": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.23.tgz", + "integrity": "sha512-beEhOs0uVeOxYOZKUfIEBd/nQV2Bd4/6wyLxZ0OFkn6CMTK2Vi+hXuZLnyPBeB6RdHpebEoJWiHqwHxBIxgPDQ==", + "license": "MIT", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.39.0", + "@standard-schema/spec": "^1.1.0", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", + "@cloudflare/workers-types": ">=4", + "@opentelemetry/api": "^1.9.0", + "better-call": "1.3.7", + "jose": "^6.1.0", + "kysely": "^0.28.5 || ^0.29.0", + "nanostores": "^1.0.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@better-auth/drizzle-adapter": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.6.23.tgz", + "integrity": "sha512-2+/PTVfIP9E7iz6af8TB3lhnowHUj9ljC66kECmHaFEdUqPgzHoWux9epotKwO7XDg2ui4ttWQ8CMeNFLvQeKQ==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", + "drizzle-orm": "^0.45.2" + }, + "peerDependenciesMeta": { + "drizzle-orm": { + "optional": true + } + } + }, + "node_modules/@better-auth/kysely-adapter": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.6.23.tgz", + "integrity": "sha512-zbNJsMbG09exfkGyvFqBLLqWoMPAUWjxCuUnEK5AsjbYoZeIjj/QGZgdf4CapVWryKxjA9Q6Jlr6fbiPpC3VAg==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", + "kysely": "^0.28.17 || ^0.29.0" + }, + "peerDependenciesMeta": { + "kysely": { + "optional": true + } + } + }, + "node_modules/@better-auth/memory-adapter": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.6.23.tgz", + "integrity": "sha512-krIiR0pIVkaKlAzm690n5bcMW4NGbqeMg0HQSD9fz/KcQF/eWLqcq9gG/BhHTj2i/y96qH+W5JWPmaSOS5iTgQ==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2" + } + }, + "node_modules/@better-auth/mongo-adapter": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.6.23.tgz", + "integrity": "sha512-7+QdevitGlKBbP6JbiSk5SBnzPsKV/mDrQBGBn8hwByQLeJwqpqbuBPw7ZI8vzUlFfAAnyFiqwP3Eb8mxnp7pA==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", + "mongodb": "^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "mongodb": { + "optional": true + } + } + }, + "node_modules/@better-auth/prisma-adapter": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.23.tgz", + "integrity": "sha512-2qSdzidq4tkb1eS5TTqb4Nzg0mdZWm3Qky9SYeXeb8PpVQbC2sxqJhEM5mK7y12uU6I8hc64wO9f7AFVNL+6UQ==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@prisma/client": { + "optional": true + }, + "prisma": { + "optional": true + } + } + }, + "node_modules/@better-auth/telemetry": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.6.23.tgz", + "integrity": "sha512-/R2Kb+z2BpDOOWwVHqOk+c0VNpuwfCv4Hp5Yr9003WIZPax/zyNraGLB9CFE8qF2gZW8Dsz419k4I8CPrGzpDA==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.23", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1" + } + }, + "node_modules/@better-auth/utils": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.2.tgz", + "integrity": "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1" + } + }, + "node_modules/@better-fetch/fetch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.3.1.tgz", + "integrity": "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==", + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@next/env": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.20.tgz", + "integrity": "sha512-dXh51Wvddf8daEyBXryZZEe1FdVxEWx9lgaTseLZUtC1XP/W8Wri+Z+VPOElHlByk23CyqHdc2oVByX7wsTWsw==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.20.tgz", + "integrity": "sha512-MZUgFpVd9rGSZpb8bNceUWvkAZe6aQw/6h2SSqHQuYzKfaiEUEVPIO6mXqaBmiBEBSN1f1sOc1uV8GCM90oegg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.20.tgz", + "integrity": "sha512-in0yXG7/pRBVjWeEl7f7ZZETpletSMFKXVS4GJgHENTPVrJFNJKPrYewa9rpZcvdjwFece5fZP0CK34G4PxowA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.20.tgz", + "integrity": "sha512-0hsFshdPnTzGJdDTHeHJ+XPUShOpnyp9pUFDwDhqctsA0Cd8NcIVGRPtptYhgYY9DjkKgCDRkXxmgRc+CgT5Wg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.20.tgz", + "integrity": "sha512-DMvkoBtAABOzE6pMZRW/xNm7sKqql3wzzzZJ1R/d/rp4BCxv6LykouD3tHjGY8WdQqGpZs11t+R9AtjPxvvljw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.20.tgz", + "integrity": "sha512-RQmDfeYBtXV2FSId7dfA1hE6M/T6+g7wdbYnFQ47tw/gUBwV+CccLVejNmCGa9yLDitk83foeg8hl/3DjfYQ5g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.20.tgz", + "integrity": "sha512-DkWLEdKajJwdGt27M3i1VEO2kelTvZrK6Pcb7JvW2BY+nofWm7FBsBNDj7g7Pr1NuQ5PLJvqEqYa20GTsBDnKQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.20.tgz", + "integrity": "sha512-rAO5b7pKHvX+ExdmJskusDXTNbiNZfptifIPZItbUx+AOXxxTydVBsPt7Oz84DRd5mY8e0DcE8kvLj3AIfjE6w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.20.tgz", + "integrity": "sha512-Hp3zFsN8N8Kj9+vY6L4vnZ9EtA9eXyATu0q4EfGbZTiocgPUNSfz8NWhym6xvaOmHpJ8EuoypuU1WejCPsTFtg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.20.tgz", + "integrity": "sha512-T/L7CXpR1M0wij/xbF3rT1+7KvSkfOLr7C+ToHHWZTG2eKmb52C5WvsyGCBNtkVvDEUESWkRUbbqSH4rSbOCYQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/ciphers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz", + "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@prisma/client": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.6.tgz", + "integrity": "sha512-w9hl+724uYEgCGR3bhuRepjBtrNB/6gkhCnAf58Ke+SLbHPPQqVZZB59z60roB+5H+nh3nWTcdJhQdFMEydWmw==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.3.tgz", + "integrity": "sha512-peavtnApRB1tABx42tHw+rPU83GSg5tXicMYO/Xi1/lqNcRsF6jkr6L7Njo7gj4q/xtDRDKBkqJvbMtoOMYWtA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.6", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", + "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", + "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", + "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.12.tgz", + "integrity": "sha512-dxioNQ7VOrYKKWJIxMRmJPDSWQN0gNCUy3zaqUSBwsuFAiFzI0yLGJr2q3ml07k/HlOk55N8KEfwa1ZgfprJ3w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.8.tgz", + "integrity": "sha512-0hhyrQdXMaATgq4ammLG9+iPqsXxzZkgTSIxdrJHdfLnXO4Uo5L7BoO3/Xf0AEaettadGZWGGJMw6ujzQvIpGA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", + "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.13.tgz", + "integrity": "sha512-1dUdKDd63Tz9FfbTw20MVr28ohG4v7HOJ1dsavGBBPBS3KGzLOyLKiMJAC1OdgiY18nTSHpD4fULGK5gsLY/ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.16.tgz", + "integrity": "sha512-w7lLsTSd3940vFYEshKkHw+NGf7H0QDJPHYsy8NRjDCVbO6ZdKW1X/xoJSYHZtttnrdZiYqbN2O/2uHGB0zasw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.6", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.4", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", + "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.18.tgz", + "integrity": "sha512-1zq2XkQkK/KfbZn84edytYpOLquhNalra5LXc3NAMKhNRSGtyXqjMv6OyC9jlSuNKpqvQtsb57WKoICNk1v/sQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.6", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.8", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.16", + "@radix-ui/react-use-controllable-state": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.4.tgz", + "integrity": "sha512-cx2DixxmSfjCcEoRvDvy1NLd6SWK94XFcEEOZUcharUlXbmahFQGKCfwdKZL2ub34iIwOPOEFVF80xb+yfLYiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.6", + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz", + "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.64.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.44", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.44.tgz", + "integrity": "sha512-T3ghW+sl/ZJ8w1v/yQx3qvJ9040DWoLBz8JT/CILbAKcFyG9b2MRe75v6W5uXjv6uH1lumK2Kv46y2zSkcej0Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/better-auth": { + "version": "1.6.23", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.23.tgz", + "integrity": "sha512-4vOaRd9UiKGKm9R+ej0jjU1es3MiJIiNc9Qq3VCnYqOZ4/nb5272QqTxWYoDxyUXl5x6A2x2we5KZKQO9teTQQ==", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.6.23", + "@better-auth/drizzle-adapter": "1.6.23", + "@better-auth/kysely-adapter": "1.6.23", + "@better-auth/memory-adapter": "1.6.23", + "@better-auth/mongo-adapter": "1.6.23", + "@better-auth/prisma-adapter": "1.6.23", + "@better-auth/telemetry": "1.6.23", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", + "@noble/ciphers": "^2.1.1", + "@noble/hashes": "^2.0.1", + "better-call": "1.3.7", + "defu": "^6.1.4", + "jose": "^6.1.3", + "kysely": "^0.28.17 || ^0.29.0", + "nanostores": "^1.1.1", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@lynx-js/react": "*", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "@sveltejs/kit": "^2.0.0", + "@tanstack/react-start": "^1.0.0", + "@tanstack/solid-start": "^1.0.0", + "better-sqlite3": "^12.0.0", + "drizzle-kit": ">=0.31.4", + "drizzle-orm": "^0.45.2", + "mongodb": "^6.0.0 || ^7.0.0", + "mysql2": "^3.0.0", + "next": "^14.0.0 || ^15.0.0 || ^16.0.0", + "pg": "^8.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "solid-js": "^1.0.0", + "svelte": "^4.0.0 || ^5.0.0", + "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@lynx-js/react": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "@tanstack/react-start": { + "optional": true + }, + "@tanstack/solid-start": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "drizzle-kit": { + "optional": true + }, + "drizzle-orm": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "next": { + "optional": true + }, + "pg": { + "optional": true + }, + "prisma": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vitest": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/better-call": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.7.tgz", + "integrity": "sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w==", + "license": "MIT", + "dependencies": { + "@better-auth/utils": "^0.4.0", + "@better-fetch/fetch": "^1.1.21", + "rou3": "^0.7.12", + "set-cookie-parser": "^3.0.1" + }, + "peerDependencies": { + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.393", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz", + "integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.20.tgz", + "integrity": "sha512-Pl/I5544gmkcVVWcnaOMfhJSBK8lZ1NCJ+mGBkd2qvbGt2Gi7sEpgHF06OR13a2p6THODlncpvGsZzY2vUqwxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "15.5.20", + "@rushstack/eslint-patch": "^1.10.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.1.tgz", + "integrity": "sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kysely": { + "version": "0.29.4", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.4.tgz", + "integrity": "sha512-y5mVgQNkMbs1eK9Xyc0pmNdabN2wHhRYY/5r4W5HrUT1rYCEPeVNSj1RUJeSDKT3U0p+mXCvLgkrFuIafYI6BA==", + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lucide-react": { + "version": "0.400.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.400.0.tgz", + "integrity": "sha512-rpp7pFHh3Xd93KHixNgB0SqThMHpYNzsGUu69UaQbSZ75Q/J3m5t6EhKyMT3m4w2WOxmJ2mY0tD3vebnXqQryQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanostores": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.4.0.tgz", + "integrity": "sha512-i0tloweeudshAEuddpDxcg9Ik6pkPfVsHIgKyf143JrgG7/MOh0+q7BypdLXZPoOP7fOYt1eTcwGkyiVmhJFkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "15.5.20", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.20.tgz", + "integrity": "sha512-cvyS3/geydan1xLtE3FA8VCgdoQ/Gg/dlOldFkFCbB5VcVYJV7090hQLBnvTW2PwT76Z/dHdzDZCsVhZpoOlUA==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.20", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.20", + "@next/swc-darwin-x64": "15.5.20", + "@next/swc-linux-arm64-gnu": "15.5.20", + "@next/swc-linux-arm64-musl": "15.5.20", + "@next/swc-linux-x64-gnu": "15.5.20", + "@next/swc-linux-x64-musl": "15.5.20", + "@next/swc-win32-arm64-msvc": "15.5.20", + "@next/swc-win32-x64-msvc": "15.5.20", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/perfect-freehand": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.2.3.tgz", + "integrity": "sha512-bHZSfqDHGNlPpgH2yxXgPHlQSPpEbo+qg7li0M78J9vNAi2yjwLeA4x79BEQhX44lEWpCLSFCeRZwpw0niiXPA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz", + "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-import/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prisma": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "5.22.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "deprecated": "1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rou3": { + "version": "0.7.12", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.12.tgz", + "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==", + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/tailwindcss/node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-fsrs": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/ts-fsrs/-/ts-fsrs-4.7.1.tgz", + "integrity": "sha512-uYqKbSCNWLRPT0PfqFFy2rn0YuYz2fKmtgpgHOBd5/DP14oj6diG7P271AwJ8iGrKnmIspzXCF+nTKxict+Lcg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..370b43b --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "edueasy", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "db:generate": "prisma generate", + "db:push": "prisma db push", + "db:migrate": "prisma migrate dev", + "db:studio": "prisma studio" + }, + "dependencies": { + "@prisma/client": "^5.20.0", + "@radix-ui/react-avatar": "^1.2.3", + "@radix-ui/react-label": "^2.1.12", + "@radix-ui/react-progress": "^1.1.13", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.18", + "@use-gesture/react": "^10.3.0", + "better-auth": "^1.0.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "framer-motion": "^11.0.0", + "lucide-react": "^0.400.0", + "next": "^15.0.0", + "perfect-freehand": "^1.2.0", + "prisma": "^5.20.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "recharts": "^2.12.0", + "tailwind-merge": "^2.5.0", + "tailwindcss-animate": "^1.0.7", + "ts-fsrs": "^4.0.0" + }, + "devDependencies": { + "@playwright/test": "^1.61.1", + "@types/node": "^20.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "autoprefixer": "^10.5.4", + "eslint": "^8.57.0", + "eslint-config-next": "^15.0.0", + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.6.0" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..cda9e3b --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from "@playwright/test" + +export default defineConfig({ + testDir: "./e2e", + timeout: 30000, + expect: { timeout: 5000 }, + fullyParallel: false, + retries: 0, + workers: 1, + reporter: [["list"]], + use: { + baseURL: "http://localhost:3459", + trace: "on-first-retry", + }, + webServer: { + command: "npx next dev -p 3459", + url: "http://localhost:3459", + reuseExistingServer: false, + timeout: 30000, + + }, +}) diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/prisma/migrations/20260720161822_init/migration.sql b/prisma/migrations/20260720161822_init/migration.sql new file mode 100644 index 0000000..b698b9f --- /dev/null +++ b/prisma/migrations/20260720161822_init/migration.sql @@ -0,0 +1,180 @@ +-- CreateTable +CREATE TABLE "Parent" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "name" TEXT, + "emailVerified" BOOLEAN NOT NULL DEFAULT false, + "image" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "familyId" TEXT NOT NULL, + + CONSTRAINT "Parent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "email" TEXT, + "userId" TEXT, + "parentId" TEXT, + "token" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "ipAddress" TEXT, + "userAgent" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Account" ( + "id" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "providerId" TEXT NOT NULL, + "parentId" TEXT NOT NULL, + "accessToken" TEXT, + "refreshToken" TEXT, + "idToken" TEXT, + "accessTokenExpiresAt" TIMESTAMP(3), + "scope" TEXT, + "password" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Verification" ( + "id" TEXT NOT NULL, + "identifier" TEXT NOT NULL, + "value" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Verification_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Family" ( + "id" TEXT NOT NULL, + "name" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Family_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Child" ( + "id" TEXT NOT NULL, + "name" TEXT, + "avatar" TEXT, + "birthdate" TIMESTAMP(3), + "profileNotes" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "familyId" TEXT NOT NULL, + + CONSTRAINT "Child_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Device" ( + "id" TEXT NOT NULL, + "deviceFingerprint" TEXT NOT NULL, + "pairedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastSeen" TIMESTAMP(3) NOT NULL, + "role" TEXT NOT NULL DEFAULT 'child', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "childId" TEXT NOT NULL, + + CONSTRAINT "Device_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SessionLog" ( + "id" TEXT NOT NULL, + "startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "endedAt" TIMESTAMP(3), + "durationSec" INTEGER, + "skillsPracticed" TEXT, + "childId" TEXT NOT NULL, + + CONSTRAINT "SessionLog_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SkillAttempt" ( + "id" TEXT NOT NULL, + "skillCode" TEXT NOT NULL, + "promptLevel" INTEGER NOT NULL DEFAULT 0, + "correct" BOOLEAN NOT NULL, + "responseMs" INTEGER, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "sessionLogId" TEXT NOT NULL, + + CONSTRAINT "SkillAttempt_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FsrsCard" ( + "id" TEXT NOT NULL, + "skillCode" TEXT NOT NULL, + "dueAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "stability" DOUBLE PRECISION NOT NULL DEFAULT 0, + "difficulty" DOUBLE PRECISION NOT NULL DEFAULT 0, + "reps" INTEGER NOT NULL DEFAULT 0, + "lapses" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "childId" TEXT NOT NULL, + + CONSTRAINT "FsrsCard_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Milestone" ( + "id" TEXT NOT NULL, + "skillCode" TEXT NOT NULL, + "reachedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "childId" TEXT NOT NULL, + + CONSTRAINT "Milestone_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Parent_email_key" ON "Parent"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token"); + +-- AddForeignKey +ALTER TABLE "Parent" ADD CONSTRAINT "Parent_familyId_fkey" FOREIGN KEY ("familyId") REFERENCES "Family"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Parent"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Parent"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Child" ADD CONSTRAINT "Child_familyId_fkey" FOREIGN KEY ("familyId") REFERENCES "Family"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Device" ADD CONSTRAINT "Device_childId_fkey" FOREIGN KEY ("childId") REFERENCES "Child"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SessionLog" ADD CONSTRAINT "SessionLog_childId_fkey" FOREIGN KEY ("childId") REFERENCES "Child"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SkillAttempt" ADD CONSTRAINT "SkillAttempt_sessionLogId_fkey" FOREIGN KEY ("sessionLogId") REFERENCES "SessionLog"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FsrsCard" ADD CONSTRAINT "FsrsCard_childId_fkey" FOREIGN KEY ("childId") REFERENCES "Child"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Milestone" ADD CONSTRAINT "Milestone_childId_fkey" FOREIGN KEY ("childId") REFERENCES "Child"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..8438a42 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,180 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Parent { + id String @id @default(cuid()) + email String @unique + name String? + emailVerified Boolean @default(false) + image String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + familyId String? + family Family? @relation(fields: [familyId], references: [id]) + + sessions Session[] + accounts Account[] +} + +model Session { + id String @id @default(cuid()) + email String? + userId String? + parentId String? + parent Parent? @relation(fields: [parentId], references: [id]) + token String @unique + expiresAt DateTime + ipAddress String? + userAgent String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Account { + id String @id @default(cuid()) + accountId String + providerId String + userId String + user Parent @relation(fields: [userId], references: [id]) + accessToken String? + refreshToken String? + idToken String? + accessTokenExpiresAt DateTime? + scope String? + password String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Verification { + id String @id @default(cuid()) + identifier String + value String + expiresAt DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Family { + id String @id @default(cuid()) + name String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + parents Parent[] + children Child[] +} + +model Child { + id String @id @default(cuid()) + name String? + avatar String? + birthdate DateTime? + profileNotes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + familyId String + family Family @relation(fields: [familyId], references: [id]) + + devices Device[] + sessions SessionLog[] + fsrsCards FsrsCard[] + milestones Milestone[] + curriculumProgress CurriculumProgress[] +} + +model Device { + id String @id @default(cuid()) + deviceFingerprint String + pairedAt DateTime @default(now()) + lastSeen DateTime @updatedAt + role String @default("child") + createdAt DateTime @default(now()) + + childId String + child Child @relation(fields: [childId], references: [id]) +} + +model SessionLog { + id String @id @default(cuid()) + startedAt DateTime @default(now()) + endedAt DateTime? + durationSec Int? + skillsPracticed String? + + childId String + child Child @relation(fields: [childId], references: [id]) + + skillAttempts SkillAttempt[] +} + +model SkillAttempt { + id String @id @default(cuid()) + skillCode String + promptLevel Int @default(0) + correct Boolean + responseMs Int? + errorType String? + exerciseId String? + stage Int? + createdAt DateTime @default(now()) + + sessionLogId String + sessionLog SessionLog @relation(fields: [sessionLogId], references: [id]) +} + +model CurriculumProgress { + id String @id @default(cuid()) + topic String + stage Int + unlockedAt DateTime @default(now()) + completedAt DateTime? + + childId String + child Child @relation(fields: [childId], references: [id]) + + @@unique([childId, topic, stage]) +} + +model FsrsCard { + id String @id @default(cuid()) + skillCode String + dueAt DateTime @default(now()) + stability Float @default(0) + difficulty Float @default(0) + reps Int @default(0) + lapses Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + childId String + child Child @relation(fields: [childId], references: [id]) +} + +model Milestone { + id String @id @default(cuid()) + skillCode String + reachedAt DateTime @default(now()) + + childId String + child Child @relation(fields: [childId], references: [id]) + + @@unique([childId, skillCode]) +} + +model PendingPairing { + id String @id @default(cuid()) + code String @unique + deviceFingerprint String? + childId String? + createdAt DateTime @default(now()) + expiresAt DateTime +} diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..f2237e2 --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/tts-cache/0ad066a5d29f3f2a2a1c7c17dd082a79.wav b/public/tts-cache/0ad066a5d29f3f2a2a1c7c17dd082a79.wav new file mode 100644 index 0000000..3c5f6bd Binary files /dev/null and b/public/tts-cache/0ad066a5d29f3f2a2a1c7c17dd082a79.wav differ diff --git a/public/tts-cache/1178ef73e06c257fdb9ec2b72ae6e781.wav b/public/tts-cache/1178ef73e06c257fdb9ec2b72ae6e781.wav new file mode 100644 index 0000000..143699d Binary files /dev/null and b/public/tts-cache/1178ef73e06c257fdb9ec2b72ae6e781.wav differ diff --git a/public/tts-cache/4d186321c1a7f0f354b297e8914ab240.wav b/public/tts-cache/4d186321c1a7f0f354b297e8914ab240.wav new file mode 100644 index 0000000..948d1f8 Binary files /dev/null and b/public/tts-cache/4d186321c1a7f0f354b297e8914ab240.wav differ diff --git a/public/tts-cache/75043ff664f05dfc0d863251555d7745.wav b/public/tts-cache/75043ff664f05dfc0d863251555d7745.wav new file mode 100644 index 0000000..9ccf8d3 Binary files /dev/null and b/public/tts-cache/75043ff664f05dfc0d863251555d7745.wav differ diff --git a/public/tts-cache/7efecaecce8338e591042f8e26137bf7.wav b/public/tts-cache/7efecaecce8338e591042f8e26137bf7.wav new file mode 100644 index 0000000..75dda70 Binary files /dev/null and b/public/tts-cache/7efecaecce8338e591042f8e26137bf7.wav differ diff --git a/public/tts-cache/d852f92d887c3788efb8c08c38788969.wav b/public/tts-cache/d852f92d887c3788efb8c08c38788969.wav new file mode 100644 index 0000000..d97c438 Binary files /dev/null and b/public/tts-cache/d852f92d887c3788efb8c08c38788969.wav differ diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..a2f9ebb --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,70 @@ +import type { Config } from "tailwindcss" + +const config: Config = { + darkMode: ["class"], + content: [ + "./app/**/*.{ts,tsx}", + "./components/**/*.{ts,tsx}", + ], + theme: { + extend: { + colors: { + background: "#FFF8F0", + foreground: "#2D3436", + primary: { + DEFAULT: "#8CB8A0", + foreground: "#FFFFFF", + }, + secondary: { + DEFAULT: "#6B8FA3", + foreground: "#FFFFFF", + }, + accent: { + DEFAULT: "#D4A574", + foreground: "#FFFFFF", + }, + success: "#7FB685", + muted: "#E8E0D8", + card: "#FFFFFF", + border: "#E8E0D8", + ring: "#8CB8A0", + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + fontFamily: { + display: ["'Fredoka One'", "Fredoka", "sans-serif"], + sans: ["Inter", "system-ui", "sans-serif"], + }, + keyframes: { + "fade-in": { + from: { opacity: "0" }, + to: { opacity: "1" }, + }, + "slide-up": { + from: { transform: "translateY(10px)", opacity: "0" }, + to: { transform: "translateY(0)", opacity: "1" }, + }, + "bounce-soft": { + "0%, 100%": { transform: "translateY(0)" }, + "50%": { transform: "translateY(-5px)" }, + }, + "pulse-soft": { + "0%, 100%": { opacity: "1" }, + "50%": { opacity: "0.7" }, + }, + }, + animation: { + "fade-in": "fade-in 0.4s ease-out", + "slide-up": "slide-up 0.4s ease-out", + "bounce-soft": "bounce-soft 2s ease-in-out infinite", + "pulse-soft": "pulse-soft 2s ease-in-out infinite", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} + +export default config diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000..cbcc1fb --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d81d4ee --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./*" + ] + }, + "target": "ES2017" + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +}