diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..624f6c9
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,12 @@
+# Copie para .env (server/.env). NUNCA commite o .env real.
+# LiteLLM proxy local (gratuito, dentro da assinatura Token Plan)
+LITELLM_BASE_URL=http://10.8.0.1:4000/v1
+LITELLM_API_KEY=coloqu…aqui
+
+# Modelos
+CHAT_MODEL=qwen3.8-max # juiz de feedback / geracao de missões
+FAST_MODEL=qwen3.8-flash # drills e tarefas leves
+OMNI_MODEL=qwen3-omni-flash # transcricao de audio do aluno (ASR tolerante a sotaque)
+
+# Porta do server
+PORT=8787
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bd84e28
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+node_modules/
+dist/
+build/
+.env
+.env.local
+*.log
+.DS_Store
+Thumbs.db
+server/cache/
+app/dist-ssr/
+coverage/
diff --git a/README.md b/README.md
index a5bd3ee..91f006f 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,151 @@
-# cursoitaliano
+# 🇮🇹 Italiano Vivo
+App de italiano **situacional-first** com voz livre e scoring de inteligibilidade.
+Aprenda como uma criança: errando mas se comunicando.
+
+## Visão
+
+- **Scoring em 3 eixos**: Sentido transmitido / Naturalidade / Precisão gramatical
+- **Tolerância estilo criança**: erro gramatical que não impede entendimento = sucesso comunicativo
+- **Voz livre**: grave, receba feedback estruturado, nunca "Errado"
+- **Salvavidas (Salvavita)**: treine estratégias de comunicação quando a palavra some
+- **SRS de chunks**: revisão espaçada de frases, não palavras isoladas
+- **PWA offline**: catálogo, áudio e SRS funcionam sem conexão
+- **CEFR can-do**: badges baseados em competências reais ("você consegue reclamar de um item com defeito")
+
+## Como Rodar
+
+### Pré-requisitos
+- Node.js ≥ 20
+- LiteLLM proxy rodando (ou configure `LITELLM_BASE_URL` para outro endpoint OpenAI-compatible)
+
+### Instalação
+
+```bash
+# Instalar dependências do monorepo
+npm install
+
+# Configurar variáveis de ambiente
+cp .env.example server/.env
+# Edite server/.env com sua chave da API
+```
+
+### Desenvolvimento
+
+```bash
+# Roda app + server simultaneamente
+npm run dev
+```
+
+- App: http://localhost:5173
+- Server: http://localhost:8787
+- Health check: http://localhost:8787/health
+
+### Build
+
+```bash
+npm run build # Build server + app
+npm run typecheck # Verificação de tipos
+```
+
+## Endpoints do Server
+
+| Método | Rota | Descrição |
+|--------|------|-----------|
+| GET | `/health` | Status do servidor |
+| POST | `/api/judge` | Avalia fala do aluno em 3 eixos (sentido, naturalidade, gramática) |
+| POST | `/api/transcribe` | Transcreve áudio via modelo omni (ASR tolerante a sotaque) |
+| GET | `/api/tts-proxy?text=...&voice=elsa\|diego` | Serve áudio TTS pré-gerado do cache local |
+
+### POST /api/judge
+
+**Entrada:**
+```json
+{
+ "missionId": "bambino-m01",
+ "scenario": "Al bar",
+ "learnerGoalPt": "Pedir um café",
+ "expectedIt": "Vorrei un caffè, per favore",
+ "utteranceText": "io volere caffe",
+ "transcriptRaw": "io volere caffe",
+ "audioBase64": "..."
+}
+```
+
+**Saída:**
+```json
+{
+ "meaning_conveyed": "full",
+ "naturalness": 45,
+ "natural_version": "Vorrei un caffè, per favore",
+ "grammar": [
+ {
+ "it_said": "io volere",
+ "problem_pt": "Infinitivo em vez de conjugação",
+ "fix_it": "vorrei / voglio",
+ "severity": "minor"
+ }
+ ],
+ "next_move": {
+ "italian": "Certo! Un caffè. Lo vuole al banco o al tavolo?",
+ "portuguese": "Claro! Um café. Quer no balcão ou na mesa?"
+ },
+ "praise_or_push": "Quase lá! Um italiano diria: Vorrei un caffè, per favore"
+}
+```
+
+## Estrutura de Pastas
+
+```
+cursoitaliano/
+├── app/ # PWA React + Vite + TypeScript
+│ ├── public/ # manifest.webmanifest, ícones
+│ └── src/
+│ ├── db/db.ts # IndexedDB via Dexie
+│ ├── engine/srs.ts # SM-2 simplificado
+│ ├── types/content.ts # Tipos do conteúdo JSON
+│ ├── App.tsx # Componente principal
+│ ├── main.tsx # Entry point
+│ └── styles.css # Design system
+├── server/ # Node + Express + TypeScript
+│ ├── cache/audio/ # Áudios TTS pré-gerados (offline)
+│ └── src/
+│ ├── lib/llm.ts # Cliente LiteLLM singleton
+│ ├── routes/
+│ │ ├── judge.ts # Motor de feedback 3 eixos
+│ │ ├── transcribe.ts # ASR via omni model
+│ │ └── tts.ts # Proxy de áudio TTS
+│ └── index.ts # Express app
+├── content/ # JSONs versionados (gerados por pipeline)
+├── audio/ # Assets de áudio por missão
+├── SPEC.md # Especificação completa do produto
+├── .env.example # Template de variáveis de ambiente
+└── package.json # Monorepo root (npm workspaces)
+```
+
+## Modelos Utilizados
+
+| Modelo | Uso | Variável |
+|--------|-----|----------|
+| qwen3.8-max | Juiz de feedback, geração de missões | `CHAT_MODEL` |
+| qwen3.8-flash | Drills rápidos, fallback de quota | `FAST_MODEL` |
+| qwen3-omni-flash | Transcrição de áudio (ASR) | `OMNI_MODEL` |
+
+Todos acessados via LiteLLM proxy (`LITELLM_BASE_URL`).
+
+## Roadmap
+
+- [x] Esqueleto do monorepo (app PWA + server LiteLLM)
+- [ ] Geração de conteúdo: Trilha Bambino (12 unidades) + Viaggio M1–M6
+- [ ] Pipeline de geração de áudio TTS (edge-tts)
+- [ ] Service Worker para modo offline completo
+- [ ] Minijogo "descreva sem nomear" (circunlóquio)
+- [ ] Modo carro/shadowing com medição de lag
+- [ ] Trilhas restantes (Viaggio M7–M15, Vivere completa)
+- [ ] Preparatório CILS/CELI B1 cidadania
+- [ ] Wrapper Android (Capacitor)
+- [ ] Calibração do judge com votos "isso estava certo"
+
+## Licença
+
+Privado — uso pessoal.
diff --git a/SPEC.md b/SPEC.md
new file mode 100644
index 0000000..4626130
--- /dev/null
+++ b/SPEC.md
@@ -0,0 +1,90 @@
+# Italiano Vivo — Especificação do Produto (v1)
+
+App de italiano **situacional-first**, com voz livre, scoring de **inteligibilidade** ("errado mas entendido" conta como sucesso), treino de **estratégias de comunicação** (circunlóquio, pedir a palavra que falta) e **modo viagem offline**. Do extremo básico (A0/Pre-A1) ao avançado (C1), ancorado nos *can-do statements* do CEFR / ALTE Social & Tourist Can-Dos.
+
+## 1. Por que este app existe (lacunas comprovadas na pesquisa)
+
+| Dor | Estado do mercado | Nossa resposta |
+|---|---|---|
+| Avaliação binária ❌/✅ | Babbel/Duolingo/Speakly marcam fala correta como errada; nenhum separa precisão de sentido | Scoring em 3 eixos: **Sentido transmitido / Naturalidade / Precisão gramatical**. Saída padrão: *"Quase lá — o italiano diria: …"*, nunca "Errado" |
+| Vocabulário temático solto | Babbel começa com queijo de cabra; Duolingo prende o aluno no path | Dia 1: aluno escolhe as próprias situações-âncora ("quero pedir um lanche", "preciso reclamar do aquecedor") |
+| Falta de estratégia quando a palavra some | Zero apps de italiano treinam circunlóquio (evidência: Coluzzi 2022; Dörnyei 1995) | Módulo dedicado **"Salvavita"**: *Come si dice…?*, *Può ripetere più lentamente?*, descrever sem nomear. Penaliza **abandonar a mensagem**, não o erro |
+| Syllabus gramatical (presente→congiuntivo) | Todos os grandes | Unidades = **missões comunicativas** (aeroporto, farmácia, hotel…). Gramática entra como insumo da missão, nunca como capítulo |
+| Voz livre sem memória | Apps de IA conversacional: "no vocab list, no grammar log, no review schedule" | Conversa livre → extrai as frases que quebraram → rebuild → reaparecem espaçadas **dentro de uma situação nova** |
+| Offline confiável + áudio-only | Só Pimsleur é hands-free, e 9% das reviews negativas citam offline quebrado | PWA com Service Worker + catálogo 100% local; TTS/áudio empacotados; IA só quando online, com degradação suave |
+| ASR hostil ao aprendiz | Monossílabos ignorados, correções circulares | ASR via LLM multimodal (tolerante a sotaque) + botão **"isso estava certo"** que vira dado de re-treino; sensível a geminadas (*pala/balla*, *nono/nonna*) |
+| Billing predatório | Queixa nº1 (renovação silenciosa, expiração antecipada) | **Compra única por trilha** ou lifetime. Sem streak-guilt, sem conteúdo removido |
+
+## 2. Persona e escopo
+- **Henrique viajante**: foi à Itália, travou no balcão. Quer o básico de criança ("io voglio / io ho bisogno / mi scusi, non so come si dice") encaixado em contexto real, progredindo até conversa adulta.
+- Escopo **tudo**: viajante + expat/burocracia (permesso, ASL, banca, affitto), porque há exigência legal objetiva (B1 cidadania CILS/CELI) e disposição a pagar por resultado mensurável.
+
+## 3. Metodologia implementada (mecânica de app)
+
+| Princípio | Mecânica |
+|---|---|
+| Aquisição natural (como criança) | Fase 1 só áudio+imagem, sem escrita; texto desbloqueado após N exposições; produção cedo porém curta |
+| Comprehensible input i+1 | Diálogos com % de cognatos controlado pelo nível; missão seguinte libera com comprehension ≥ ~80% |
+| Chunks / lexical approach | Unidade mínima = bloco pronto (`Mi scusi, non ho capito — può ripetere più lentamente?`), nunca palavra isolada |
+| CLT / Task-Based Learning | Cada missão tem **critério de sucesso comunicativo** ("marque uma visita médica sem inglês"). O app avalia a meta atingida, depois expõe os gaps |
+| Output practice (Swain) | Após conversa livre: extrair 3 frases tentadas-e-quebradas → rebuild → re-gravar → reencontrar 3 dias depois |
+| SRS | Revisão de **frases-chunks** (SM-2 simplificado), reintroduzidas dentro de diálogo novo, não como flashcard morto |
+| Shadowing | Modo carro/corrida: áudio nativo em velocidade real, gravação sobreposta, medição de lag; protocolo Arguelles (blind → transcript → read+shadow) |
+| Estratégias de comunicação (CS) | Minijogo "descreva sem nomear" cronometrado; cartões de resgate; auto-repair pontuado positivamente |
+| CEFR can-do | Badge = **"Você agora consegue: reclamar de um item com defeito"**, com verificação por voz. Pre-A1 → B2/C1 |
+
+## 4. Trilhas e missões (espinha dorsal)
+
+**Trilha Bambino (A0–A1)** — núcleo da ideia: quero/não quero, tenho/preciso, gosto, dói, é grande/pequeno, mais/menos, isto/aquilo, números, sim/não/forse, e principalmente os **salvavidas**: *come si dice…? / non capisco / ripeta piano / come si scrive? / scusi, non conosco la parola*.
+
+**Trilha Viaggio (A1–B2)** — aeroporto e alfândega · check-in de voo · táxi/ônibus/trem · hotel (checar, reclamar do quarto, pedir extra) · bar e restaurante (pedir, alergia, conta, gorjeta, reserva) · supermercado/padaria/lanche ("vorrei un panino con…" + descrever o que não sei o nome) · farmácia e médico (sintomas, dor, dosagem) · banco e câmbio · aluguel de carro · compras e devolução · reclamar/negociar/recusar · fazer amizade/small talk · emergência e polícia · telefone/online burocrático.
+
+**Trilha Vivere (B1–C1)** — questura/permesso, ASL/sanità, SPID, comune/anagrafe, casa e landlord, scuola dei figli, lavoro, preparazione CILS/CELI B1 cittadinanza.
+
+Cada missão = `scenario` com: objetivos can-do, chunks essenciais, diálogos-âncora (áudio), armadilhas reais (tu vs Lei, cortesia italiana, "magari/dai/vabbè"), role-play de voz livre, e checklist de sobrevivência offline.
+
+## 5. Motor de feedback (o diferencial)
+
+Pipeline por tentativa de fala:
+1. **ASR** (qwen omni-flash, tolerante a sotaque) → transcrição + confusão provável.
+2. **Judge** (qwen3.8-max) produz JSON estruturado:
+ - `meaning_conveyed`: full | partial | failed
+ - `naturalness`: score + versão que um italiano diria
+ - `grammar`: erros classificados (grave=gera mal-entendido / leve / cosmético)
+ - `next_move`: continuação do diálogo como italiano faria
+ - `rescue_card`: se o aluno travou por falta de palavra → ensina o salvavita aplicável
+3. **Regra de ouro:** `meaning_conveyed=full` ⇒ missão avança mesmo com erros graves leves; feedback sempre termina com a frase reconstruída para repetir.
+4. Botão "isto estava certo" → registra dissidência (dataset futuro de calibração).
+
+## 6. Arquitetura técnica
+
+```
+cursoitaliano/
+ app/ # PWA React+Vite+TS (IndexedDB/Dexie, Workbox SW, MediaRecorder, Web Speech playback)
+ src/lib/ai.ts # cliente OpenAI-compatible -> LiteLLM (http://10.8.0.1:4000/v1)
+ src/engine/ # judge, scoring, srs, mission graph
+ src/content/ # JSONs gerados (missões, chunks, diálogos, salvavidas)
+ server/ # Node TS: /transcribe /judge /tts /generate-mission (cache + rate-limit + fallback chain)
+ content/ # pipeline de geração (Node) -> JSON versionado no repo
+ audio/ # assets TTS pré-gerados (por missão)
+ SPEC.md README.md .env.example
+```
+
+- **IA**: tudo via LiteLLM local (sem custo PAYG): chat/judge `qwen3.8-max`, drills rápidos `qwen3.8-flash`, voz/ASR `qwen3-omni-flash` (audio base64), TTS via Edge TTS vozes `it-IT-ElsaNeural`/`it-IT-DiegoNeural` (geração em lote, pacote offline; fallback Web Speech API).
+- **Offline**: catálogo, chunks, diálogos e áudio empacotados no build + IndexedDB; role-play de voz exige rede; **phrasebook de resgate e SRS funcionam 100% offline**; fila de tentativas para julgamento quando voltar a conexão.
+- **Segurança**: chave do proxy fora do bundle — `.env` no `server/`, app fala só com o próprio server; `.env` no .gitignore; `.env.example` sem segredo.
+
+## 7. MVP (o que está sendo construído agora)
+1. Repo inicializado, config, scripts.
+2. Conteúdo gerado: trilha Bambino completa (12 unidades) + Viaggio M1–M6 (aeroporto, táxi/trem, hotel, restaurante, bar/lanche, farmácia/médico), cada uma com can-dos, 12–20 chunks, 2 diálogos, role-play prompts, salvavidas.
+3. Server com `/judge` (3-eixos), `/transcribe` (omni), `/tts`.
+4. App PWA: home por missões, player de diálogo com áudio, **cabine de voz** (gravar → judge → feedback "quase lá"), minijogo "descreva sem nomear", SRS de chunks, modo resgate offline.
+5. README com rodar-local, arquitetura e roadmap (trilhas restantes, shadowing mode, CILS prep, Android nativo).
+
+## 8. Roadmap pós-MVP
+- Missões 7–15 da trilha Viaggio + trilha Vivere inteira (geração pelo mesmo pipeline).
+- Modo carro/shadowing com medição de lag, análise fonêmica de geminadas.
+- Export de erro como currículo (log de interlíngua + revisão adaptativa).
+- Trilha CILS/CELI B1 (cidadania) com simulado.
+- Wrapper Android (Capacitor) com áudio offline por pacote.
+- Calibração do judge com os votos "isto estava certo".
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 0000000..4139cf4
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+ Italiano Vivo
+
+
+
+
+
+
diff --git a/app/package.json b/app/package.json
new file mode 100644
index 0000000..89a9138
--- /dev/null
+++ b/app/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "italiano-vivo-app",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "dexie": "^4.0.11",
+ "dexie-react-hooks": "^1.1.7",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "zustand": "^5.0.2"
+ },
+ "devDependencies": {
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "^4.3.4",
+ "typescript": "^5.7.0",
+ "vite": "^6.0.0"
+ }
+}
diff --git a/app/public/manifest.webmanifest b/app/public/manifest.webmanifest
new file mode 100644
index 0000000..f51d622
--- /dev/null
+++ b/app/public/manifest.webmanifest
@@ -0,0 +1,22 @@
+{
+ "name": "Italiano Vivo",
+ "short_name": "ItalianoVivo",
+ "description": "Aprenda italiano com voz livre — situacional-first, tolerante a erros",
+ "start_url": "/",
+ "display": "standalone",
+ "background_color": "#ffffff",
+ "theme_color": "#009246",
+ "orientation": "portrait-primary",
+ "icons": [
+ {
+ "src": "/icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/app/public/sw.js b/app/public/sw.js
new file mode 100644
index 0000000..bd6fb3a
--- /dev/null
+++ b/app/public/sw.js
@@ -0,0 +1,91 @@
+/**
+ * Service Worker — Italiano Vivo
+ * Estratégia:
+ * - Shell (HTML/CSS/JS do app): cache-first
+ * - /content/* e /audio/*: cache-first com network fallback + runtime caching
+ * - /api/*: network-only (nunca cachear)
+ *
+ * Objetivo: modo resgate offline — frases + áudio já baixados funcionam no avião.
+ */
+
+const CACHE_NAME = "italiano-vivo-v1";
+const SHELL_ASSETS = ["/", "/index.html"];
+
+// ─── Install: pré-cache do shell ───
+self.addEventListener("install", (event) => {
+ event.waitUntil(
+ caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_ASSETS)),
+ );
+ self.skipWaiting();
+});
+
+// ─── Activate: limpar caches antigos ───
+self.addEventListener("activate", (event) => {
+ event.waitUntil(
+ caches.keys().then((keys) =>
+ Promise.all(
+ keys
+ .filter((k) => k !== CACHE_NAME)
+ .map((k) => caches.delete(k)),
+ ),
+ ),
+ );
+ self.clients.claim();
+});
+
+// ─── Fetch: roteamento por padrão de URL ───
+self.addEventListener("fetch", (event) => {
+ const url = new URL(event.request.url);
+ const pathname = url.pathname;
+
+ // API: sempre network-only
+ if (pathname.startsWith("/api/")) {
+ return; // deixa o browser fazer o fetch normal
+ }
+
+ // Content e Audio: cache-first com network fallback + runtime caching
+ if (pathname.startsWith("/content/") || pathname.startsWith("/audio/")) {
+ event.respondWith(cacheFirstWithRuntimeCache(event.request));
+ return;
+ }
+
+ // Shell e demais assets: cache-first
+ event.respondWith(cacheFirst(event.request));
+});
+
+/**
+ * Cache-first: tenta cache, se não achar vai na rede (sem salvar no cache).
+ * Usado para shell/assets que são pré-cachados no install.
+ */
+async function cacheFirst(request) {
+ const cached = await caches.match(request);
+ if (cached) return cached;
+ return fetch(request);
+}
+
+/**
+ * Cache-first com runtime caching: tenta cache, se não achar vai na rede
+ * e salva a resposta no cache para uso offline futuro.
+ * Usado para /content/* e /audio/* que são baixados sob demanda.
+ */
+async function cacheFirstWithRuntimeCache(request) {
+ const cached = await caches.match(request);
+ if (cached) return cached;
+
+ try {
+ const response = await fetch(request);
+ // Só cachear respostas OK (não cachear 404/500)
+ if (response.ok) {
+ const cache = await caches.open(CACHE_NAME);
+ cache.put(request, response.clone());
+ }
+ return response;
+ } catch (_err) {
+ // Offline e sem cache: retorna erro genérico
+ return new Response("Offline — Italiano Vivo", {
+ status: 503,
+ statusText: "Service Unavailable",
+ headers: { "Content-Type": "text/plain" },
+ });
+ }
+}
diff --git a/app/src/App.tsx b/app/src/App.tsx
new file mode 100644
index 0000000..a687b13
--- /dev/null
+++ b/app/src/App.tsx
@@ -0,0 +1,664 @@
+/**
+ * App principal do Italiano Vivo.
+ * Roteamento simples por estado (sem react-router).
+ * Componentes funcionais com CSS próprio.
+ */
+import { useState, useEffect, useRef, useCallback } from "react";
+import type { ContentIndex, Mission, MissionChunk, DialogueLine } from "./types/content.js";
+import { db, type AttemptRecord } from "./db/db.js";
+import { useLiveQuery } from "dexie-react-hooks";
+import { review as srsReview, verdictToQuality, createCard, getDueCards } from "./engine/srs.js";
+import type { SRSCard } from "./db/db.js";
+
+// ─── Tipos de veredito do judge ───
+interface JudgeVerdict {
+ meaning_conveyed: "full" | "partial" | "failed";
+ naturalness: number;
+ natural_version: string;
+ grammar: Array<{
+ it_said: string;
+ problem_pt: string;
+ fix_it: string;
+ severity: "blocks_understanding" | "minor" | "cosmetic";
+ }>;
+ rescue_card?: {
+ come_si_dice: string;
+ frase_it: string;
+ explicacao_pt: string;
+ };
+ next_move: {
+ italian: string;
+ portuguese: string;
+ };
+ praise_or_push: string;
+}
+
+type ViewState = "home" | "mission" | "review";
+
+// ─── Flag italiana ───
+function ItalianFlag() {
+ return (
+
+
+
+
+
+ );
+}
+
+// ─── Home: lista de missões por trilha ───
+function HomeView({ onSelectMission }: { onSelectMission: (id: string) => void }) {
+ const [index, setIndex] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ async function load() {
+ try {
+ const res = await fetch("/content/index.json");
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const data = (await res.json()) as ContentIndex;
+ if (!cancelled) setIndex(data);
+ } catch (e) {
+ if (!cancelled) {
+ console.warn("Falha ao carregar conteúdo, usando array vazio:", e);
+ setIndex({ schemaVersion: 1, missions: [] });
+ setError("Conteúdo ainda não gerado. As missões aparecerão após o pipeline de geração.");
+ }
+ } finally {
+ if (!cancelled) setLoading(false);
+ }
+ }
+ load();
+ return () => { cancelled = true; };
+ }, []);
+
+ if (loading) return 🇮🇹 Carregando missões...
;
+
+ const trails = ["bambino", "viaggio", "vivere"] as const;
+
+ return (
+
+ {error &&
{error}
}
+
+ {trails.map((trail) => {
+ const missions = index?.missions.filter((m) => m.trail === trail) ?? [];
+ if (missions.length === 0) return null;
+
+ return (
+
+
+ Trilha {trail.charAt(0).toUpperCase() + trail.slice(1)}
+
+ {missions.map((m) => (
+
onSelectMission(m.id)}>
+
+
{m.titleIt}
+ {trail}
+
+
{m.titlePt}
+
+ CEFR: {m.cefr} · {m.canDos.length} objetivos
+
+
+ ))}
+
+ );
+ })}
+
+ {(!index || index.missions.length === 0) && !error && (
+
+
Nenhuma missão disponível ainda.
+
+ )}
+
+ );
+}
+
+// ─── Player de áudio ───
+function AudioPlayer({ audioKey, label }: { audioKey: string; label?: string }) {
+ const [playing, setPlaying] = useState(false);
+ const audioRef = useRef(null);
+
+ const play = useCallback(() => {
+ if (audioRef.current) {
+ audioRef.current.currentTime = 0;
+ audioRef.current.play().catch(() => {});
+ }
+ }, []);
+
+ return (
+ <>
+
+ {playing ? "⏸" : "▶"}
+
+ setPlaying(true)}
+ onEnded={() => setPlaying(false)}
+ onError={() => setPlaying(false)}
+ />
+ >
+ );
+}
+
+// ─── Cabine de Voz ───
+function VoiceBooth({
+ missionId,
+ scenario,
+ goalPt,
+ expectedIt,
+ onComplete,
+}: {
+ missionId: string;
+ scenario: string;
+ goalPt: string;
+ expectedIt?: string;
+ onComplete: (verdict: JudgeVerdict) => void;
+}) {
+ const [recording, setRecording] = useState(false);
+ const [processing, setProcessing] = useState(false);
+ const [transcript, setTranscript] = useState("");
+ const [error, setError] = useState(null);
+ const mediaRecorderRef = useRef(null);
+ const chunksRef = useRef([]);
+
+ const startRecording = async () => {
+ try {
+ setError(null);
+ setTranscript("");
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ const recorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
+ chunksRef.current = [];
+
+ recorder.ondataavailable = (e) => {
+ if (e.data.size > 0) chunksRef.current.push(e.data);
+ };
+
+ recorder.onstop = async () => {
+ stream.getTracks().forEach((t) => t.stop());
+ const blob = new Blob(chunksRef.current, { type: "audio/webm" });
+ await processAudio(blob);
+ };
+
+ mediaRecorderRef.current = recorder;
+ recorder.start();
+ setRecording(true);
+ } catch (e) {
+ setError("Não foi possível acessar o microfone. Verifique as permissões.");
+ }
+ };
+
+ const stopRecording = () => {
+ if (mediaRecorderRef.current && recording) {
+ mediaRecorderRef.current.stop();
+ setRecording(false);
+ }
+ };
+
+ const processAudio = async (blob: Blob) => {
+ setProcessing(true);
+ setError(null);
+
+ try {
+ // Converter para base64
+ const buffer = await blob.arrayBuffer();
+ const bytes = new Uint8Array(buffer);
+ let binary = "";
+ for (let i = 0; i < bytes.length; i++) {
+ binary += String.fromCharCode(bytes[i]!);
+ }
+ const base64 = btoa(binary);
+
+ // Transcrever
+ const transcribeRes = await fetch("/api/transcribe", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ audioBase64: base64, mime: "audio/webm" }),
+ });
+
+ if (!transcribeRes.ok) throw new Error("Erro na transcrição");
+ const transcribeData = (await transcribeRes.json()) as { text: string };
+ setTranscript(transcribeData.text);
+
+ // Julgar
+ const judgeRes = await fetch("/api/judge", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ missionId,
+ scenario,
+ learnerGoalPt: goalPt,
+ expectedIt,
+ transcriptRaw: transcribeData.text,
+ }),
+ });
+
+ if (!judgeRes.ok) throw new Error("Erro no julgamento");
+ const verdict = (await judgeRes.json()) as JudgeVerdict;
+
+ // Salvar tentativa no DB
+ await db.attempts.add({
+ missionId,
+ ts: Date.now(),
+ text: transcribeData.text,
+ verdict: {
+ meaning_conveyed: verdict.meaning_conveyed,
+ naturalness: verdict.naturalness,
+ natural_version: verdict.natural_version,
+ grammar: verdict.grammar,
+ praise_or_push: verdict.praise_or_push,
+ },
+ });
+
+ onComplete(verdict);
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Erro desconhecido");
+ } finally {
+ setProcessing(false);
+ }
+ };
+
+ return (
+
+
🎙️ Fale em italiano:
+
"{goalPt}"
+
+ {error &&
{error}
}
+
+ {transcript && (
+
+
Transcrição:
+
"{transcript}"
+
+ )}
+
+
+ {!recording ? (
+
+ 🎤
+
+ ) : (
+
+ ⏹
+
+ )}
+
+
+ {processing &&
Analisando sua fala...
}
+
+ );
+}
+
+// ─── Feedback do Judge ───
+function FeedbackPanel({
+ verdict,
+ missionId,
+}: {
+ verdict: JudgeVerdict;
+ missionId: string;
+}) {
+ const [dissentRegistered, setDissentRegistered] = useState(false);
+
+ const handleDissent = async () => {
+ // Registrar dissidência ("isso estava certo")
+ const lastAttempt = await db.attempts
+ .where("missionId")
+ .equals(missionId)
+ .reverse()
+ .sortBy("ts");
+ const attempt = lastAttempt[0];
+ if (attempt) {
+ await db.attempts.update(attempt.id!, { dissent: true });
+ setDissentRegistered(true);
+ }
+ };
+
+ const meaningClass =
+ verdict.meaning_conveyed === "full"
+ ? "success"
+ : verdict.meaning_conveyed === "partial"
+ ? "warning"
+ : "error";
+
+ return (
+
+
+ Sentido transmitido: {" "}
+ {verdict.meaning_conveyed === "full"
+ ? "✅ Completo"
+ : verdict.meaning_conveyed === "partial"
+ ? "⚠️ Parcial"
+ : "❌ Não transmitido"}
+
+
+
+ {verdict.natural_version}
+
+
+
+ {verdict.praise_or_push}
+
+
+ {verdict.grammar.length > 0 && (
+
+
Gramática
+ {verdict.grammar.map((err, i) => (
+
+ "{err.it_said}" →{" "}
+ {err.fix_it}
+
+ {err.problem_pt}
+
+ ))}
+
+ )}
+
+ {verdict.rescue_card && (
+
+
💡 Salvavita
+
{verdict.rescue_card.come_si_dice}
+
{verdict.rescue_card.frase_it}
+
{verdict.rescue_card.explicacao_pt}
+
+ )}
+
+
+
Próximo passo do diálogo
+
{verdict.next_move.italian}
+
{verdict.next_move.portuguese}
+
+
+ {!dissentRegistered && (
+
+ 🤔 Isso estava certo (discordar da avaliação)
+
+ )}
+ {dissentRegistered && (
+
+ ✓ Dissidência registrada. Obrigado por ajudar a calibrar!
+
+ )}
+
+ );
+}
+
+// ─── Visão da Missão ───
+function MissionView({
+ missionId,
+ onBack,
+}: {
+ missionId: string;
+ onBack: () => void;
+}) {
+ const [mission, setMission] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [verdict, setVerdict] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ async function load() {
+ try {
+ const res = await fetch(`/content/missions/${missionId}.json`);
+ if (!res.ok) throw new Error(`Missão não encontrada (HTTP ${res.status})`);
+ const data = (await res.json()) as Mission;
+ if (!cancelled) setMission(data);
+ } catch (e) {
+ if (!cancelled) setError(e instanceof Error ? e.message : "Erro ao carregar missão");
+ } finally {
+ if (!cancelled) setLoading(false);
+ }
+ }
+ load();
+ return () => { cancelled = true; };
+ }, [missionId]);
+
+ if (loading) return Carregando missão...
;
+ if (error) return {error}
;
+ if (!mission) return Missão não encontrada
;
+
+ return (
+
+
+ ← Voltar
+
+
+
+
+
{mission.titleIt}
+ {mission.trail}
+
+
{mission.titlePt}
+
CEFR: {mission.cefr}
+
+
+ {/* Can-dos */}
+
+
🎯 Objetivos
+ {mission.canDos.map((cd) => (
+
+ ☐ {cd.text}
+
+ ))}
+
+
+ {/* Chunks */}
+
+
📦 Frases-chave
+ {mission.chunks.map((chunk: MissionChunk, i: number) => (
+
+
+
+
{chunk.it}
+
{chunk.pt}
+ {chunk.note &&
{chunk.note}
}
+
+
+ ))}
+
+
+ {/* Diálogos */}
+ {mission.dialogues.map((dialogue: DialogueLine[], dIdx: number) => (
+
+
💬 Diálogo {dIdx + 1}
+ {dialogue.map((line: DialogueLine, lIdx: number) => (
+
+
+
+
{line.it}
+
{line.pt}
+
+
+ ))}
+
+ ))}
+
+ {/* Cabine de voz */}
+
+
🎙️ Pratique falando
+ setVerdict(v)}
+ />
+
+
+ {/* Feedback */}
+ {verdict && (
+
+
📊 Feedback
+
+
+ )}
+
+ {/* Survival checklist */}
+ {mission.survivalChecklist.length > 0 && (
+
+
🆘 Checklist de Sobrevivência
+ {mission.survivalChecklist.map((item, i) => (
+
• {item}
+ ))}
+
+ )}
+
+ );
+}
+
+// ─── Revisão SRS ───
+function ReviewView() {
+ const cards = useLiveQuery(() => db.srsCards.toArray()) ?? [];
+ const dueCards = getDueCards(cards);
+ const [currentIdx, setCurrentIdx] = useState(0);
+ const [showAnswer, setShowAnswer] = useState(false);
+ const [selectedRating, setSelectedRating] = useState(null);
+
+ const currentCard = dueCards[currentIdx];
+
+ const handleRate = async (quality: number) => {
+ if (!currentCard) return;
+ const updated = srsReview(currentCard, quality);
+ await db.srsCards.put(updated);
+ setSelectedRating(null);
+ setShowAnswer(false);
+ setCurrentIdx((prev) => prev + 1);
+ };
+
+ if (dueCards.length === 0) {
+ return (
+
+
✨ Tudo em dia!
+
Nenhum cartão para revisar agora.
+
Volte mais tarde ou pratique novas missões.
+
+ );
+ }
+
+ if (currentIdx >= dueCards.length) {
+ return (
+
+
🎉 Revisão completa!
+
Você revisou {dueCards.length} cartões.
+
+ );
+ }
+
+ // Narrowing: após os guards acima, currentCard é garantido definido
+ if (!currentCard) return null;
+
+ return (
+
+
+ Cartão {currentIdx + 1} de {dueCards.length}
+
+
+
!showAnswer && setShowAnswer(true)}>
+
+
{currentCard.chunkIt}
+ {showAnswer && (
+ <>
+
{currentCard.chunkPt}
+ {currentCard.audioKey && (
+
e.stopPropagation()}>
+
+
+ )}
+ >
+ )}
+ {!showAnswer && (
+
+ Toque para revelar
+
+ )}
+
+
+
+ {showAnswer && (
+
+
+ Como foi?
+
+
+ {[1, 2, 3, 4, 5].map((q) => (
+ handleRate(q)}
+ >
+ {q}
+
+ ))}
+
+
+ Errei
+ Perfeito
+
+
+ )}
+
+ );
+}
+
+// ─── App Principal ───
+export function App() {
+ const [view, setView] = useState("home");
+ const [selectedMissionId, setSelectedMissionId] = useState(null);
+
+ const handleSelectMission = (id: string) => {
+ setSelectedMissionId(id);
+ setView("mission");
+ };
+
+ const handleBack = () => {
+ setSelectedMissionId(null);
+ setView("home");
+ };
+
+ return (
+
+
+
+
+ setView("home")}
+ >
+ 🏠 Missões
+
+ setView("review")}
+ >
+ 🔄 Revisão
+
+
+
+ {view === "home" && }
+ {view === "mission" && selectedMissionId && (
+
+ )}
+ {view === "review" && }
+
+ );
+}
diff --git a/app/src/db/db.ts b/app/src/db/db.ts
new file mode 100644
index 0000000..07c0cba
--- /dev/null
+++ b/app/src/db/db.ts
@@ -0,0 +1,83 @@
+/**
+ * Banco de dados IndexedDB via Dexie.
+ * Armazena progresso, tentativas, cartões SRS, log de resgate e configurações.
+ */
+import Dexie, { type EntityTable } from "dexie";
+
+export interface ProgressRecord {
+ missionId: string;
+ completedAt?: number;
+ bestMeaningScore?: "full" | "partial" | "failed";
+ attemptsCount: number;
+}
+
+export interface AttemptRecord {
+ id?: number;
+ missionId: string;
+ ts: number;
+ text: string;
+ verdict: {
+ meaning_conveyed: "full" | "partial" | "failed";
+ naturalness: number;
+ natural_version: string;
+ grammar: Array<{
+ it_said: string;
+ problem_pt: string;
+ fix_it: string;
+ severity: "blocks_understanding" | "minor" | "cosmetic";
+ }>;
+ praise_or_push: string;
+ };
+ /** Se o aluno marcou "isso estava certo" (dissidência) */
+ dissent?: boolean;
+}
+
+export interface SRSCard {
+ id: string;
+ chunkIt: string;
+ chunkPt: string;
+ audioKey?: string;
+ box: number;
+ interval: number;
+ due: number;
+ lastSeen: number;
+ easeFactor: number;
+}
+
+export interface RescueLogEntry {
+ id?: number;
+ missionId: string;
+ ts: number;
+ rescueCard: {
+ come_si_dice: string;
+ frase_it: string;
+ explicacao_pt: string;
+ };
+ used: boolean;
+}
+
+export interface AppSettings {
+ key: string;
+ value: string;
+}
+
+class ItalianoVivoDB extends Dexie {
+ progress!: EntityTable;
+ attempts!: EntityTable;
+ srsCards!: EntityTable;
+ rescueLog!: EntityTable;
+ settings!: EntityTable;
+
+ constructor() {
+ super("ItalianoVivo");
+ this.version(1).stores({
+ progress: "missionId",
+ attempts: "++id, missionId, ts",
+ srsCards: "id, due, box",
+ rescueLog: "++id, missionId, ts",
+ settings: "key",
+ });
+ }
+}
+
+export const db = new ItalianoVivoDB();
diff --git a/app/src/engine/srs.ts b/app/src/engine/srs.ts
new file mode 100644
index 0000000..d755b09
--- /dev/null
+++ b/app/src/engine/srs.ts
@@ -0,0 +1,112 @@
+/**
+ * SM-2 simplificado para revisão espaçada de chunks.
+ * Qualidade 0-5 derivada do veredito:
+ * full = 4, partial = 2, failed = 1
+ * + bônus se o aluno regravou (tentou de novo)
+ */
+
+import type { SRSCard } from "../db/db.js";
+
+/** Intervalos iniciais em dias por box (SM-2 simplificado) */
+const BASE_INTERVALS = [0, 1, 3, 7, 14, 30, 60];
+
+export interface ReviewInput {
+ meaningConveyed: "full" | "partial" | "failed";
+ reRecorded: boolean;
+}
+
+/** Converte veredito do judge em qualidade SM-2 (0-5) */
+export function verdictToQuality(input: ReviewInput): number {
+ let quality: number;
+
+ switch (input.meaningConveyed) {
+ case "full":
+ quality = 4;
+ break;
+ case "partial":
+ quality = 2;
+ break;
+ case "failed":
+ quality = 1;
+ break;
+ }
+
+ // Bônus por re-gravação: mostra esforço e engajamento
+ if (input.reRecorded && quality < 5) {
+ quality = Math.min(quality + 1, 5);
+ }
+
+ return quality;
+}
+
+/**
+ * Calcula o próximo estado do cartão SRS após uma revisão.
+ * Função pura — sem efeitos colaterais.
+ */
+export function review(card: SRSCard, quality: number): SRSCard {
+ const now = Date.now();
+ const DAY_MS = 86_400_000;
+
+ let { box, interval, easeFactor } = card;
+
+ if (quality < 3) {
+ // Falhou: resetar para box 0
+ box = 0;
+ interval = 0;
+ } else {
+ // Sucesso: avançar box
+ box = Math.min(box + 1, BASE_INTERVALS.length - 1);
+
+ // Calcular novo intervalo
+ if (box === 1) {
+ interval = 1;
+ } else if (box === 2) {
+ interval = 3;
+ } else {
+ // SM-2: I(n) = I(n-1) * EF
+ interval = Math.round(interval * easeFactor);
+ }
+
+ // Ajustar ease factor baseado na qualidade
+ // EF' = EF + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02))
+ easeFactor = easeFactor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));
+ easeFactor = Math.max(easeFactor, 1.3); // Mínimo 1.3
+ }
+
+ const due = now + interval * DAY_MS;
+
+ return {
+ ...card,
+ box,
+ interval,
+ easeFactor,
+ due,
+ lastSeen: now,
+ };
+}
+
+/** Cria um novo cartão SRS para um chunk */
+export function createCard(params: {
+ id: string;
+ chunkIt: string;
+ chunkPt: string;
+ audioKey?: string;
+}): SRSCard {
+ return {
+ id: params.id,
+ chunkIt: params.chunkIt,
+ chunkPt: params.chunkPt,
+ audioKey: params.audioKey,
+ box: 0,
+ interval: 0,
+ due: Date.now(), // Disponível imediatamente
+ lastSeen: 0,
+ easeFactor: 2.5, // Ease factor inicial padrão SM-2
+ };
+}
+
+/** Retorna cartões vencidos (due <= agora) */
+export function getDueCards(cards: SRSCard[]): SRSCard[] {
+ const now = Date.now();
+ return cards.filter((c) => c.due <= now).sort((a, b) => a.due - b.due);
+}
diff --git a/app/src/main.tsx b/app/src/main.tsx
new file mode 100644
index 0000000..3dfeab7
--- /dev/null
+++ b/app/src/main.tsx
@@ -0,0 +1,27 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import { App } from "./App.js";
+import "./styles.css";
+
+const rootEl = document.getElementById("root");
+if (!rootEl) throw new Error("Elemento #root não encontrado");
+
+createRoot(rootEl).render(
+
+
+ ,
+);
+
+// Service Worker: só em produção para modo resgate offline
+if ("serviceWorker" in navigator && import.meta.env.PROD) {
+ window.addEventListener("load", () => {
+ navigator.serviceWorker
+ .register("/sw.js")
+ .then((reg) => {
+ console.log("[SW] Registrado com sucesso, scope:", reg.scope);
+ })
+ .catch((err) => {
+ console.warn("[SW] Falha ao registrar:", err);
+ });
+ });
+}
diff --git a/app/src/styles.css b/app/src/styles.css
new file mode 100644
index 0000000..c5736fc
--- /dev/null
+++ b/app/src/styles.css
@@ -0,0 +1,434 @@
+/* Italiano Vivo — Design System */
+:root {
+ /* Cores da bandeira italiana + neutros */
+ --verde: #009246;
+ --bianco: #ffffff;
+ --rosso: #ce2b37;
+ --bg: #f8f9fa;
+ --surface: #ffffff;
+ --text: #1a1a2e;
+ --text-muted: #6c757d;
+ --border: #e2e8f0;
+ --shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ --radius: 12px;
+ --font: "Segoe UI", system-ui, -apple-system, sans-serif;
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: var(--font);
+ background: var(--bg);
+ color: var(--text);
+ line-height: 1.6;
+ -webkit-font-smoothing: antialiased;
+}
+
+#root {
+ max-width: 480px;
+ margin: 0 auto;
+ min-height: 100vh;
+ padding: 16px;
+}
+
+/* Tipografia */
+h1 {
+ font-size: 1.75rem;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+h2 {
+ font-size: 1.35rem;
+ font-weight: 600;
+ margin-bottom: 12px;
+}
+
+h3 {
+ font-size: 1.1rem;
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+p {
+ margin-bottom: 8px;
+}
+
+.text-muted {
+ color: var(--text-muted);
+ font-size: 0.9rem;
+}
+
+.text-it {
+ font-style: italic;
+ color: var(--verde);
+}
+
+/* Cards */
+.card {
+ background: var(--surface);
+ border-radius: var(--radius);
+ padding: 16px;
+ margin-bottom: 12px;
+ box-shadow: var(--shadow);
+ border: 1px solid var(--border);
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+/* Botões */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 12px 20px;
+ border: none;
+ border-radius: var(--radius);
+ font-size: 1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ text-decoration: none;
+}
+
+.btn:active {
+ transform: scale(0.97);
+}
+
+.btn-primary {
+ background: var(--verde);
+ color: var(--bianco);
+}
+
+.btn-primary:hover {
+ background: #007a3a;
+}
+
+.btn-secondary {
+ background: var(--border);
+ color: var(--text);
+}
+
+.btn-secondary:hover {
+ background: #d1d5db;
+}
+
+.btn-danger {
+ background: var(--rosso);
+ color: var(--bianco);
+}
+
+.btn-record {
+ width: 64px;
+ height: 64px;
+ border-radius: 50%;
+ background: var(--rosso);
+ color: white;
+ font-size: 1.5rem;
+ border: 3px solid #ff6b7a;
+}
+
+.btn-record.recording {
+ animation: pulse 1.2s infinite;
+ background: #ff4757;
+}
+
+@keyframes pulse {
+ 0%, 100% { transform: scale(1); }
+ 50% { transform: scale(1.08); }
+}
+
+.btn-play {
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
+ background: var(--verde);
+ color: white;
+ font-size: 1.2rem;
+ flex-shrink: 0;
+}
+
+/* Trail badges */
+.trail-badge {
+ display: inline-block;
+ padding: 4px 10px;
+ border-radius: 20px;
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.trail-bambino { background: #d4edda; color: #155724; }
+.trail-viaggio { background: #cce5ff; color: #004085; }
+.trail-vivere { background: #fff3cd; color: #856404; }
+
+/* Feedback cards */
+.feedback-axis {
+ padding: 12px;
+ border-left: 4px solid var(--border);
+ margin-bottom: 8px;
+ background: var(--bg);
+ border-radius: 0 var(--radius) var(--radius) 0;
+}
+
+.feedback-axis.success { border-left-color: var(--verde); }
+.feedback-axis.warning { border-left-color: #ffc107; }
+.feedback-axis.error { border-left-color: var(--rosso); }
+
+.natural-version {
+ background: #e8f5e9;
+ padding: 10px 14px;
+ border-radius: var(--radius);
+ margin: 8px 0;
+ font-size: 1.05rem;
+}
+
+.natural-version::before {
+ content: "🇮🇹 Um italiano diria: ";
+ font-weight: 700;
+ font-size: 0.85rem;
+ display: block;
+ margin-bottom: 4px;
+ color: var(--verde);
+}
+
+/* Voice booth */
+.voice-booth {
+ text-align: center;
+ padding: 24px 16px;
+ background: linear-gradient(135deg, #f8f9fa, #e8f5e9);
+ border-radius: var(--radius);
+ margin: 16px 0;
+}
+
+.voice-booth p {
+ margin-bottom: 16px;
+ font-size: 1.1rem;
+}
+
+/* Grammar errors list */
+.grammar-error {
+ padding: 8px 12px;
+ background: #fff3cd;
+ border-radius: 8px;
+ margin-bottom: 6px;
+ font-size: 0.9rem;
+}
+
+.grammar-error .fix {
+ color: var(--verde);
+ font-weight: 600;
+}
+
+.grammar-error.blocks_understanding {
+ background: #f8d7da;
+}
+
+/* SRS flashcard */
+.flashcard {
+ perspective: 1000px;
+ min-height: 200px;
+ cursor: pointer;
+}
+
+.flashcard-inner {
+ background: var(--surface);
+ border-radius: var(--radius);
+ padding: 32px 24px;
+ text-align: center;
+ box-shadow: var(--shadow);
+ border: 2px solid var(--border);
+ min-height: 200px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.flashcard-inner .it {
+ font-size: 1.4rem;
+ font-weight: 600;
+ color: var(--verde);
+ margin-bottom: 8px;
+}
+
+.flashcard-inner .pt {
+ font-size: 1rem;
+ color: var(--text-muted);
+}
+
+.rating-buttons {
+ display: flex;
+ gap: 8px;
+ justify-content: center;
+ margin-top: 16px;
+}
+
+.rating-btn {
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
+ border: 2px solid var(--border);
+ background: var(--surface);
+ font-weight: 700;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.rating-btn:hover { border-color: var(--verde); color: var(--verde); }
+.rating-btn.selected { background: var(--verde); color: white; border-color: var(--verde); }
+
+/* Loading & Error states */
+.loading {
+ text-align: center;
+ padding: 40px;
+ color: var(--text-muted);
+}
+
+.error-banner {
+ background: #f8d7da;
+ color: #721c24;
+ padding: 12px 16px;
+ border-radius: var(--radius);
+ margin-bottom: 16px;
+}
+
+/* Navigation */
+.nav-bar {
+ display: flex;
+ gap: 8px;
+ margin-bottom: 20px;
+ overflow-x: auto;
+ padding-bottom: 4px;
+}
+
+.nav-tab {
+ padding: 8px 16px;
+ border-radius: 20px;
+ border: 1px solid var(--border);
+ background: var(--surface);
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: all 0.2s;
+}
+
+.nav-tab.active {
+ background: var(--verde);
+ color: white;
+ border-color: var(--verde);
+}
+
+/* Chunk list */
+.chunk-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px;
+ border-bottom: 1px solid var(--border);
+}
+
+.chunk-item:last-child { border-bottom: none; }
+
+.chunk-text {
+ flex: 1;
+}
+
+.chunk-text .it {
+ font-weight: 600;
+ color: var(--verde);
+}
+
+.chunk-text .pt {
+ font-size: 0.85rem;
+ color: var(--text-muted);
+}
+
+/* Dialogue */
+.dialogue-line {
+ display: flex;
+ gap: 10px;
+ align-items: flex-start;
+ padding: 10px 0;
+}
+
+.dialogue-line.learner {
+ flex-direction: row-reverse;
+}
+
+.dialogue-bubble {
+ max-width: 80%;
+ padding: 10px 14px;
+ border-radius: 16px;
+ font-size: 0.95rem;
+}
+
+.dialogue-line.italian .dialogue-bubble {
+ background: #e8f5e9;
+ border-bottom-left-radius: 4px;
+}
+
+.dialogue-line.learner .dialogue-bubble {
+ background: #e3f2fd;
+ border-bottom-right-radius: 4px;
+}
+
+/* Dissent button */
+.dissent-btn {
+ margin-top: 12px;
+ padding: 8px 16px;
+ background: transparent;
+ border: 1px dashed var(--text-muted);
+ border-radius: 8px;
+ color: var(--text-muted);
+ font-size: 0.85rem;
+ cursor: pointer;
+}
+
+.dissent-btn:hover {
+ border-color: var(--verde);
+ color: var(--verde);
+}
+
+/* Header */
+.app-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 20px;
+ padding-bottom: 12px;
+ border-bottom: 2px solid var(--verde);
+}
+
+.app-header h1 {
+ margin: 0;
+ font-size: 1.5rem;
+}
+
+.flag {
+ display: inline-flex;
+ width: 32px;
+ height: 22px;
+ border-radius: 3px;
+ overflow: hidden;
+}
+
+.flag span {
+ flex: 1;
+}
+
+.flag .green { background: var(--verde); }
+.flag .white { background: var(--bianco); border: 1px solid #ddd; }
+.flag .red { background: var(--rosso); }
diff --git a/app/src/types/content.ts b/app/src/types/content.ts
new file mode 100644
index 0000000..c86e5de
--- /dev/null
+++ b/app/src/types/content.ts
@@ -0,0 +1,80 @@
+/**
+ * Tipos TypeScript para o conteúdo JSON versionado.
+ * Gerado pelo pipeline em content/, consumido pelo app.
+ */
+
+/** Um chunk lexical — unidade mínima de aprendizado */
+export interface MissionChunk {
+ /** Frase em italiano */
+ it: string;
+ /** Tradução em português */
+ pt: string;
+ /** Nota gramatical ou cultural opcional */
+ note?: string;
+ /** Chave do arquivo de áudio (sem extensão): audio/{audioKey}.mp3 */
+ audioKey: string;
+}
+
+/** Declaração can-do do CEFR */
+export interface CanDo {
+ id: string;
+ text: string;
+}
+
+/** Cartão de resgate (salvavita) */
+export interface RescueCard {
+ come_si_dice: string;
+ frase_it: string;
+ explicacao_pt: string;
+}
+
+/** Linha de diálogo */
+export interface DialogueLine {
+ speaker: "italian" | "learner";
+ it: string;
+ pt: string;
+ audioKey: string;
+}
+
+/** Trilhas do app */
+export type Trail = "bambino" | "viaggio" | "vivere";
+
+/** Uma missão completa */
+export interface Mission {
+ id: string;
+ trail: Trail;
+ cefr: string;
+ titleIt: string;
+ titlePt: string;
+ canDos: CanDo[];
+ chunks: MissionChunk[];
+ dialogues: DialogueLine[][];
+ roleplay: {
+ settingIt: string;
+ italianRole: string;
+ goalPt: string;
+ openerIt: string;
+ };
+ survivalChecklist: string[];
+}
+
+/** Bundle de conteúdo versionado */
+export interface ContentBundle {
+ schemaVersion: 1;
+ missions: Mission[];
+}
+
+/** Índice leve carregado na home (sem chunks/diálogos pesados) */
+export interface MissionSummary {
+ id: string;
+ trail: Trail;
+ cefr: string;
+ titleIt: string;
+ titlePt: string;
+ canDos: CanDo[];
+}
+
+export interface ContentIndex {
+ schemaVersion: 1;
+ missions: MissionSummary[];
+}
diff --git a/app/tsconfig.json b/app/tsconfig.json
new file mode 100644
index 0000000..f463a69
--- /dev/null
+++ b/app/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "noUncheckedIndexedAccess": true,
+ "types": ["vite/client"],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/app/tsconfig.tsbuildinfo b/app/tsconfig.tsbuildinfo
new file mode 100644
index 0000000..30af50b
--- /dev/null
+++ b/app/tsconfig.tsbuildinfo
@@ -0,0 +1 @@
+{"root":["./src/db/db.ts","./src/engine/srs.ts","./src/types/content.ts","./src/app.tsx","./src/main.tsx"],"version":"5.9.3"}
\ No newline at end of file
diff --git a/app/vite.config.ts b/app/vite.config.ts
new file mode 100644
index 0000000..8c8b227
--- /dev/null
+++ b/app/vite.config.ts
@@ -0,0 +1,27 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 5173,
+ proxy: {
+ "/api": {
+ target: "http://localhost:8787",
+ changeOrigin: true,
+ },
+ "/audio": {
+ target: "http://localhost:8787",
+ changeOrigin: true,
+ },
+ "/content": {
+ target: "http://localhost:8787",
+ changeOrigin: true,
+ },
+ },
+ },
+ build: {
+ outDir: "dist",
+ sourcemap: true,
+ },
+});
diff --git a/audio/.gitkeep b/audio/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/audio/b1-chunk-01.mp3 b/audio/b1-chunk-01.mp3
new file mode 100644
index 0000000..9a0561d
Binary files /dev/null and b/audio/b1-chunk-01.mp3 differ
diff --git a/audio/b1-chunk-02.mp3 b/audio/b1-chunk-02.mp3
new file mode 100644
index 0000000..9fa4e21
Binary files /dev/null and b/audio/b1-chunk-02.mp3 differ
diff --git a/audio/b1-chunk-03.mp3 b/audio/b1-chunk-03.mp3
new file mode 100644
index 0000000..d659322
Binary files /dev/null and b/audio/b1-chunk-03.mp3 differ
diff --git a/audio/b1-chunk-04.mp3 b/audio/b1-chunk-04.mp3
new file mode 100644
index 0000000..bcebad7
Binary files /dev/null and b/audio/b1-chunk-04.mp3 differ
diff --git a/audio/b1-chunk-05.mp3 b/audio/b1-chunk-05.mp3
new file mode 100644
index 0000000..848f4d8
Binary files /dev/null and b/audio/b1-chunk-05.mp3 differ
diff --git a/audio/b1-chunk-06.mp3 b/audio/b1-chunk-06.mp3
new file mode 100644
index 0000000..13cf2aa
Binary files /dev/null and b/audio/b1-chunk-06.mp3 differ
diff --git a/audio/b1-chunk-07.mp3 b/audio/b1-chunk-07.mp3
new file mode 100644
index 0000000..7a926f6
Binary files /dev/null and b/audio/b1-chunk-07.mp3 differ
diff --git a/audio/b1-chunk-08.mp3 b/audio/b1-chunk-08.mp3
new file mode 100644
index 0000000..5ce8f98
Binary files /dev/null and b/audio/b1-chunk-08.mp3 differ
diff --git a/audio/b1-chunk-09.mp3 b/audio/b1-chunk-09.mp3
new file mode 100644
index 0000000..88480fe
Binary files /dev/null and b/audio/b1-chunk-09.mp3 differ
diff --git a/audio/b1-chunk-10.mp3 b/audio/b1-chunk-10.mp3
new file mode 100644
index 0000000..f011901
Binary files /dev/null and b/audio/b1-chunk-10.mp3 differ
diff --git a/audio/b1-chunk-11.mp3 b/audio/b1-chunk-11.mp3
new file mode 100644
index 0000000..6b6e121
Binary files /dev/null and b/audio/b1-chunk-11.mp3 differ
diff --git a/audio/b1-chunk-12.mp3 b/audio/b1-chunk-12.mp3
new file mode 100644
index 0000000..315a255
Binary files /dev/null and b/audio/b1-chunk-12.mp3 differ
diff --git a/audio/b1-d1-l1.mp3 b/audio/b1-d1-l1.mp3
new file mode 100644
index 0000000..073ea52
Binary files /dev/null and b/audio/b1-d1-l1.mp3 differ
diff --git a/audio/b1-d1-l2.mp3 b/audio/b1-d1-l2.mp3
new file mode 100644
index 0000000..7dfd8d4
Binary files /dev/null and b/audio/b1-d1-l2.mp3 differ
diff --git a/audio/b1-d1-l3.mp3 b/audio/b1-d1-l3.mp3
new file mode 100644
index 0000000..3309c9d
Binary files /dev/null and b/audio/b1-d1-l3.mp3 differ
diff --git a/audio/b1-d1-l4.mp3 b/audio/b1-d1-l4.mp3
new file mode 100644
index 0000000..8d0c04f
Binary files /dev/null and b/audio/b1-d1-l4.mp3 differ
diff --git a/audio/b1-d1-l5.mp3 b/audio/b1-d1-l5.mp3
new file mode 100644
index 0000000..658ea1c
Binary files /dev/null and b/audio/b1-d1-l5.mp3 differ
diff --git a/audio/b1-d1-l6.mp3 b/audio/b1-d1-l6.mp3
new file mode 100644
index 0000000..a41cfbe
Binary files /dev/null and b/audio/b1-d1-l6.mp3 differ
diff --git a/audio/b1-d2-l1.mp3 b/audio/b1-d2-l1.mp3
new file mode 100644
index 0000000..7fe06dc
Binary files /dev/null and b/audio/b1-d2-l1.mp3 differ
diff --git a/audio/b1-d2-l2.mp3 b/audio/b1-d2-l2.mp3
new file mode 100644
index 0000000..5088996
Binary files /dev/null and b/audio/b1-d2-l2.mp3 differ
diff --git a/audio/b1-d2-l3.mp3 b/audio/b1-d2-l3.mp3
new file mode 100644
index 0000000..17de99f
Binary files /dev/null and b/audio/b1-d2-l3.mp3 differ
diff --git a/audio/b1-d2-l4.mp3 b/audio/b1-d2-l4.mp3
new file mode 100644
index 0000000..adc9500
Binary files /dev/null and b/audio/b1-d2-l4.mp3 differ
diff --git a/audio/b1-d2-l5.mp3 b/audio/b1-d2-l5.mp3
new file mode 100644
index 0000000..3c358c6
Binary files /dev/null and b/audio/b1-d2-l5.mp3 differ
diff --git a/audio/b1-d2-l6.mp3 b/audio/b1-d2-l6.mp3
new file mode 100644
index 0000000..5b31e6a
Binary files /dev/null and b/audio/b1-d2-l6.mp3 differ
diff --git a/audio/b10-chunk-01.mp3 b/audio/b10-chunk-01.mp3
new file mode 100644
index 0000000..6bb9e73
Binary files /dev/null and b/audio/b10-chunk-01.mp3 differ
diff --git a/audio/b10-chunk-02.mp3 b/audio/b10-chunk-02.mp3
new file mode 100644
index 0000000..aabd497
Binary files /dev/null and b/audio/b10-chunk-02.mp3 differ
diff --git a/audio/b10-chunk-03.mp3 b/audio/b10-chunk-03.mp3
new file mode 100644
index 0000000..cbfbaf0
Binary files /dev/null and b/audio/b10-chunk-03.mp3 differ
diff --git a/audio/b10-chunk-04.mp3 b/audio/b10-chunk-04.mp3
new file mode 100644
index 0000000..c2c9b8c
Binary files /dev/null and b/audio/b10-chunk-04.mp3 differ
diff --git a/audio/b10-chunk-05.mp3 b/audio/b10-chunk-05.mp3
new file mode 100644
index 0000000..c7912b8
Binary files /dev/null and b/audio/b10-chunk-05.mp3 differ
diff --git a/audio/b10-chunk-06.mp3 b/audio/b10-chunk-06.mp3
new file mode 100644
index 0000000..b981be0
Binary files /dev/null and b/audio/b10-chunk-06.mp3 differ
diff --git a/audio/b10-chunk-07.mp3 b/audio/b10-chunk-07.mp3
new file mode 100644
index 0000000..fb021af
Binary files /dev/null and b/audio/b10-chunk-07.mp3 differ
diff --git a/audio/b10-chunk-08.mp3 b/audio/b10-chunk-08.mp3
new file mode 100644
index 0000000..8e50b38
Binary files /dev/null and b/audio/b10-chunk-08.mp3 differ
diff --git a/audio/b10-chunk-09.mp3 b/audio/b10-chunk-09.mp3
new file mode 100644
index 0000000..2810fa7
Binary files /dev/null and b/audio/b10-chunk-09.mp3 differ
diff --git a/audio/b10-chunk-10.mp3 b/audio/b10-chunk-10.mp3
new file mode 100644
index 0000000..0418ee3
Binary files /dev/null and b/audio/b10-chunk-10.mp3 differ
diff --git a/audio/b10-chunk-11.mp3 b/audio/b10-chunk-11.mp3
new file mode 100644
index 0000000..92641ce
Binary files /dev/null and b/audio/b10-chunk-11.mp3 differ
diff --git a/audio/b10-chunk-12.mp3 b/audio/b10-chunk-12.mp3
new file mode 100644
index 0000000..1b76039
Binary files /dev/null and b/audio/b10-chunk-12.mp3 differ
diff --git a/audio/b10-chunk-13.mp3 b/audio/b10-chunk-13.mp3
new file mode 100644
index 0000000..c9f83e7
Binary files /dev/null and b/audio/b10-chunk-13.mp3 differ
diff --git a/audio/b10-d1-l1.mp3 b/audio/b10-d1-l1.mp3
new file mode 100644
index 0000000..0c12db1
Binary files /dev/null and b/audio/b10-d1-l1.mp3 differ
diff --git a/audio/b10-d1-l2.mp3 b/audio/b10-d1-l2.mp3
new file mode 100644
index 0000000..17ad045
Binary files /dev/null and b/audio/b10-d1-l2.mp3 differ
diff --git a/audio/b10-d1-l3.mp3 b/audio/b10-d1-l3.mp3
new file mode 100644
index 0000000..a1fc7ca
Binary files /dev/null and b/audio/b10-d1-l3.mp3 differ
diff --git a/audio/b10-d1-l4.mp3 b/audio/b10-d1-l4.mp3
new file mode 100644
index 0000000..9938bb1
Binary files /dev/null and b/audio/b10-d1-l4.mp3 differ
diff --git a/audio/b10-d1-l5.mp3 b/audio/b10-d1-l5.mp3
new file mode 100644
index 0000000..631ce4d
Binary files /dev/null and b/audio/b10-d1-l5.mp3 differ
diff --git a/audio/b10-d1-l6.mp3 b/audio/b10-d1-l6.mp3
new file mode 100644
index 0000000..0eeb39b
Binary files /dev/null and b/audio/b10-d1-l6.mp3 differ
diff --git a/audio/b10-d2-l1.mp3 b/audio/b10-d2-l1.mp3
new file mode 100644
index 0000000..bcf99db
Binary files /dev/null and b/audio/b10-d2-l1.mp3 differ
diff --git a/audio/b10-d2-l2.mp3 b/audio/b10-d2-l2.mp3
new file mode 100644
index 0000000..aa4b3e9
Binary files /dev/null and b/audio/b10-d2-l2.mp3 differ
diff --git a/audio/b10-d2-l3.mp3 b/audio/b10-d2-l3.mp3
new file mode 100644
index 0000000..26707d2
Binary files /dev/null and b/audio/b10-d2-l3.mp3 differ
diff --git a/audio/b10-d2-l4.mp3 b/audio/b10-d2-l4.mp3
new file mode 100644
index 0000000..a438267
Binary files /dev/null and b/audio/b10-d2-l4.mp3 differ
diff --git a/audio/b10-d2-l5.mp3 b/audio/b10-d2-l5.mp3
new file mode 100644
index 0000000..de86dad
Binary files /dev/null and b/audio/b10-d2-l5.mp3 differ
diff --git a/audio/b10-d2-l6.mp3 b/audio/b10-d2-l6.mp3
new file mode 100644
index 0000000..cd9d694
Binary files /dev/null and b/audio/b10-d2-l6.mp3 differ
diff --git a/audio/b10-d2-l7.mp3 b/audio/b10-d2-l7.mp3
new file mode 100644
index 0000000..f236d2f
Binary files /dev/null and b/audio/b10-d2-l7.mp3 differ
diff --git a/audio/b11-chunk-01.mp3 b/audio/b11-chunk-01.mp3
new file mode 100644
index 0000000..150287e
Binary files /dev/null and b/audio/b11-chunk-01.mp3 differ
diff --git a/audio/b11-chunk-02.mp3 b/audio/b11-chunk-02.mp3
new file mode 100644
index 0000000..ee2286f
Binary files /dev/null and b/audio/b11-chunk-02.mp3 differ
diff --git a/audio/b11-chunk-03.mp3 b/audio/b11-chunk-03.mp3
new file mode 100644
index 0000000..b6f1074
Binary files /dev/null and b/audio/b11-chunk-03.mp3 differ
diff --git a/audio/b11-chunk-04.mp3 b/audio/b11-chunk-04.mp3
new file mode 100644
index 0000000..a33635a
Binary files /dev/null and b/audio/b11-chunk-04.mp3 differ
diff --git a/audio/b11-chunk-05.mp3 b/audio/b11-chunk-05.mp3
new file mode 100644
index 0000000..6e74f90
Binary files /dev/null and b/audio/b11-chunk-05.mp3 differ
diff --git a/audio/b11-chunk-06.mp3 b/audio/b11-chunk-06.mp3
new file mode 100644
index 0000000..816065a
Binary files /dev/null and b/audio/b11-chunk-06.mp3 differ
diff --git a/audio/b11-chunk-07.mp3 b/audio/b11-chunk-07.mp3
new file mode 100644
index 0000000..f39aaa2
Binary files /dev/null and b/audio/b11-chunk-07.mp3 differ
diff --git a/audio/b11-chunk-08.mp3 b/audio/b11-chunk-08.mp3
new file mode 100644
index 0000000..5bb26b1
Binary files /dev/null and b/audio/b11-chunk-08.mp3 differ
diff --git a/audio/b11-chunk-09.mp3 b/audio/b11-chunk-09.mp3
new file mode 100644
index 0000000..ba55f66
Binary files /dev/null and b/audio/b11-chunk-09.mp3 differ
diff --git a/audio/b11-chunk-10.mp3 b/audio/b11-chunk-10.mp3
new file mode 100644
index 0000000..ccbb177
Binary files /dev/null and b/audio/b11-chunk-10.mp3 differ
diff --git a/audio/b11-chunk-11.mp3 b/audio/b11-chunk-11.mp3
new file mode 100644
index 0000000..b28becb
Binary files /dev/null and b/audio/b11-chunk-11.mp3 differ
diff --git a/audio/b11-chunk-12.mp3 b/audio/b11-chunk-12.mp3
new file mode 100644
index 0000000..36eaa4a
Binary files /dev/null and b/audio/b11-chunk-12.mp3 differ
diff --git a/audio/b11-chunk-13.mp3 b/audio/b11-chunk-13.mp3
new file mode 100644
index 0000000..f704595
Binary files /dev/null and b/audio/b11-chunk-13.mp3 differ
diff --git a/audio/b11-d1-l1.mp3 b/audio/b11-d1-l1.mp3
new file mode 100644
index 0000000..d0aa086
Binary files /dev/null and b/audio/b11-d1-l1.mp3 differ
diff --git a/audio/b11-d1-l2.mp3 b/audio/b11-d1-l2.mp3
new file mode 100644
index 0000000..ab09c06
Binary files /dev/null and b/audio/b11-d1-l2.mp3 differ
diff --git a/audio/b11-d1-l3.mp3 b/audio/b11-d1-l3.mp3
new file mode 100644
index 0000000..4f8c838
Binary files /dev/null and b/audio/b11-d1-l3.mp3 differ
diff --git a/audio/b11-d1-l4.mp3 b/audio/b11-d1-l4.mp3
new file mode 100644
index 0000000..a80205e
Binary files /dev/null and b/audio/b11-d1-l4.mp3 differ
diff --git a/audio/b11-d1-l5.mp3 b/audio/b11-d1-l5.mp3
new file mode 100644
index 0000000..c2602e8
Binary files /dev/null and b/audio/b11-d1-l5.mp3 differ
diff --git a/audio/b11-d1-l6.mp3 b/audio/b11-d1-l6.mp3
new file mode 100644
index 0000000..4f68c70
Binary files /dev/null and b/audio/b11-d1-l6.mp3 differ
diff --git a/audio/b11-d1-l7.mp3 b/audio/b11-d1-l7.mp3
new file mode 100644
index 0000000..13c9411
Binary files /dev/null and b/audio/b11-d1-l7.mp3 differ
diff --git a/audio/b11-d2-l1.mp3 b/audio/b11-d2-l1.mp3
new file mode 100644
index 0000000..28e6957
Binary files /dev/null and b/audio/b11-d2-l1.mp3 differ
diff --git a/audio/b11-d2-l2.mp3 b/audio/b11-d2-l2.mp3
new file mode 100644
index 0000000..d48175c
Binary files /dev/null and b/audio/b11-d2-l2.mp3 differ
diff --git a/audio/b11-d2-l3.mp3 b/audio/b11-d2-l3.mp3
new file mode 100644
index 0000000..1c1438d
Binary files /dev/null and b/audio/b11-d2-l3.mp3 differ
diff --git a/audio/b11-d2-l4.mp3 b/audio/b11-d2-l4.mp3
new file mode 100644
index 0000000..c247148
Binary files /dev/null and b/audio/b11-d2-l4.mp3 differ
diff --git a/audio/b11-d2-l5.mp3 b/audio/b11-d2-l5.mp3
new file mode 100644
index 0000000..58cceee
Binary files /dev/null and b/audio/b11-d2-l5.mp3 differ
diff --git a/audio/b11-d2-l6.mp3 b/audio/b11-d2-l6.mp3
new file mode 100644
index 0000000..8f8323e
Binary files /dev/null and b/audio/b11-d2-l6.mp3 differ
diff --git a/audio/b12-chunk-01.mp3 b/audio/b12-chunk-01.mp3
new file mode 100644
index 0000000..79055e1
Binary files /dev/null and b/audio/b12-chunk-01.mp3 differ
diff --git a/audio/b12-chunk-02.mp3 b/audio/b12-chunk-02.mp3
new file mode 100644
index 0000000..41c2874
Binary files /dev/null and b/audio/b12-chunk-02.mp3 differ
diff --git a/audio/b12-chunk-03.mp3 b/audio/b12-chunk-03.mp3
new file mode 100644
index 0000000..f8a8e0b
Binary files /dev/null and b/audio/b12-chunk-03.mp3 differ
diff --git a/audio/b12-chunk-04.mp3 b/audio/b12-chunk-04.mp3
new file mode 100644
index 0000000..9ca75a5
Binary files /dev/null and b/audio/b12-chunk-04.mp3 differ
diff --git a/audio/b12-chunk-05.mp3 b/audio/b12-chunk-05.mp3
new file mode 100644
index 0000000..e36ba79
Binary files /dev/null and b/audio/b12-chunk-05.mp3 differ
diff --git a/audio/b12-chunk-06.mp3 b/audio/b12-chunk-06.mp3
new file mode 100644
index 0000000..8876ccd
Binary files /dev/null and b/audio/b12-chunk-06.mp3 differ
diff --git a/audio/b12-chunk-07.mp3 b/audio/b12-chunk-07.mp3
new file mode 100644
index 0000000..cfa0ab4
Binary files /dev/null and b/audio/b12-chunk-07.mp3 differ
diff --git a/audio/b12-chunk-08.mp3 b/audio/b12-chunk-08.mp3
new file mode 100644
index 0000000..dd3153a
Binary files /dev/null and b/audio/b12-chunk-08.mp3 differ
diff --git a/audio/b12-chunk-09.mp3 b/audio/b12-chunk-09.mp3
new file mode 100644
index 0000000..8a201f5
Binary files /dev/null and b/audio/b12-chunk-09.mp3 differ
diff --git a/audio/b12-chunk-10.mp3 b/audio/b12-chunk-10.mp3
new file mode 100644
index 0000000..a631a28
Binary files /dev/null and b/audio/b12-chunk-10.mp3 differ
diff --git a/audio/b12-chunk-11.mp3 b/audio/b12-chunk-11.mp3
new file mode 100644
index 0000000..5972d7d
Binary files /dev/null and b/audio/b12-chunk-11.mp3 differ
diff --git a/audio/b12-chunk-12.mp3 b/audio/b12-chunk-12.mp3
new file mode 100644
index 0000000..5dff174
Binary files /dev/null and b/audio/b12-chunk-12.mp3 differ
diff --git a/audio/b12-chunk-13.mp3 b/audio/b12-chunk-13.mp3
new file mode 100644
index 0000000..9fd0986
Binary files /dev/null and b/audio/b12-chunk-13.mp3 differ
diff --git a/audio/b12-chunk-14.mp3 b/audio/b12-chunk-14.mp3
new file mode 100644
index 0000000..baadeb0
Binary files /dev/null and b/audio/b12-chunk-14.mp3 differ
diff --git a/audio/b12-d1-l1.mp3 b/audio/b12-d1-l1.mp3
new file mode 100644
index 0000000..421be0a
Binary files /dev/null and b/audio/b12-d1-l1.mp3 differ
diff --git a/audio/b12-d1-l10.mp3 b/audio/b12-d1-l10.mp3
new file mode 100644
index 0000000..5deac52
Binary files /dev/null and b/audio/b12-d1-l10.mp3 differ
diff --git a/audio/b12-d1-l2.mp3 b/audio/b12-d1-l2.mp3
new file mode 100644
index 0000000..24385b2
Binary files /dev/null and b/audio/b12-d1-l2.mp3 differ
diff --git a/audio/b12-d1-l3.mp3 b/audio/b12-d1-l3.mp3
new file mode 100644
index 0000000..9a3585e
Binary files /dev/null and b/audio/b12-d1-l3.mp3 differ
diff --git a/audio/b12-d1-l4.mp3 b/audio/b12-d1-l4.mp3
new file mode 100644
index 0000000..1c4d98c
Binary files /dev/null and b/audio/b12-d1-l4.mp3 differ
diff --git a/audio/b12-d1-l5.mp3 b/audio/b12-d1-l5.mp3
new file mode 100644
index 0000000..608362d
Binary files /dev/null and b/audio/b12-d1-l5.mp3 differ
diff --git a/audio/b12-d1-l6.mp3 b/audio/b12-d1-l6.mp3
new file mode 100644
index 0000000..93ba7db
Binary files /dev/null and b/audio/b12-d1-l6.mp3 differ
diff --git a/audio/b12-d1-l7.mp3 b/audio/b12-d1-l7.mp3
new file mode 100644
index 0000000..7e19bb6
Binary files /dev/null and b/audio/b12-d1-l7.mp3 differ
diff --git a/audio/b12-d1-l8.mp3 b/audio/b12-d1-l8.mp3
new file mode 100644
index 0000000..2f57d10
Binary files /dev/null and b/audio/b12-d1-l8.mp3 differ
diff --git a/audio/b12-d1-l9.mp3 b/audio/b12-d1-l9.mp3
new file mode 100644
index 0000000..262d3c1
Binary files /dev/null and b/audio/b12-d1-l9.mp3 differ
diff --git a/audio/b12-d2-l1.mp3 b/audio/b12-d2-l1.mp3
new file mode 100644
index 0000000..248906e
Binary files /dev/null and b/audio/b12-d2-l1.mp3 differ
diff --git a/audio/b12-d2-l2.mp3 b/audio/b12-d2-l2.mp3
new file mode 100644
index 0000000..74df0c4
Binary files /dev/null and b/audio/b12-d2-l2.mp3 differ
diff --git a/audio/b12-d2-l3.mp3 b/audio/b12-d2-l3.mp3
new file mode 100644
index 0000000..3371171
Binary files /dev/null and b/audio/b12-d2-l3.mp3 differ
diff --git a/audio/b12-d2-l4.mp3 b/audio/b12-d2-l4.mp3
new file mode 100644
index 0000000..768df77
Binary files /dev/null and b/audio/b12-d2-l4.mp3 differ
diff --git a/audio/b12-d2-l5.mp3 b/audio/b12-d2-l5.mp3
new file mode 100644
index 0000000..965f249
Binary files /dev/null and b/audio/b12-d2-l5.mp3 differ
diff --git a/audio/b12-d2-l6.mp3 b/audio/b12-d2-l6.mp3
new file mode 100644
index 0000000..f7bb50a
Binary files /dev/null and b/audio/b12-d2-l6.mp3 differ
diff --git a/audio/b12-d2-l7.mp3 b/audio/b12-d2-l7.mp3
new file mode 100644
index 0000000..be02852
Binary files /dev/null and b/audio/b12-d2-l7.mp3 differ
diff --git a/audio/b12-d2-l8.mp3 b/audio/b12-d2-l8.mp3
new file mode 100644
index 0000000..d00357d
Binary files /dev/null and b/audio/b12-d2-l8.mp3 differ
diff --git a/audio/b2-chunk-01.mp3 b/audio/b2-chunk-01.mp3
new file mode 100644
index 0000000..2b5b727
Binary files /dev/null and b/audio/b2-chunk-01.mp3 differ
diff --git a/audio/b2-chunk-02.mp3 b/audio/b2-chunk-02.mp3
new file mode 100644
index 0000000..7d6e208
Binary files /dev/null and b/audio/b2-chunk-02.mp3 differ
diff --git a/audio/b2-chunk-03.mp3 b/audio/b2-chunk-03.mp3
new file mode 100644
index 0000000..1b90a47
Binary files /dev/null and b/audio/b2-chunk-03.mp3 differ
diff --git a/audio/b2-chunk-04.mp3 b/audio/b2-chunk-04.mp3
new file mode 100644
index 0000000..e55e35f
Binary files /dev/null and b/audio/b2-chunk-04.mp3 differ
diff --git a/audio/b2-chunk-05.mp3 b/audio/b2-chunk-05.mp3
new file mode 100644
index 0000000..d505e2d
Binary files /dev/null and b/audio/b2-chunk-05.mp3 differ
diff --git a/audio/b2-chunk-06.mp3 b/audio/b2-chunk-06.mp3
new file mode 100644
index 0000000..27278f1
Binary files /dev/null and b/audio/b2-chunk-06.mp3 differ
diff --git a/audio/b2-chunk-07.mp3 b/audio/b2-chunk-07.mp3
new file mode 100644
index 0000000..b29e534
Binary files /dev/null and b/audio/b2-chunk-07.mp3 differ
diff --git a/audio/b2-chunk-08.mp3 b/audio/b2-chunk-08.mp3
new file mode 100644
index 0000000..767baec
Binary files /dev/null and b/audio/b2-chunk-08.mp3 differ
diff --git a/audio/b2-chunk-09.mp3 b/audio/b2-chunk-09.mp3
new file mode 100644
index 0000000..06ef530
Binary files /dev/null and b/audio/b2-chunk-09.mp3 differ
diff --git a/audio/b2-chunk-10.mp3 b/audio/b2-chunk-10.mp3
new file mode 100644
index 0000000..80dfac5
Binary files /dev/null and b/audio/b2-chunk-10.mp3 differ
diff --git a/audio/b2-chunk-11.mp3 b/audio/b2-chunk-11.mp3
new file mode 100644
index 0000000..dc02df2
Binary files /dev/null and b/audio/b2-chunk-11.mp3 differ
diff --git a/audio/b2-chunk-12.mp3 b/audio/b2-chunk-12.mp3
new file mode 100644
index 0000000..d11c39c
Binary files /dev/null and b/audio/b2-chunk-12.mp3 differ
diff --git a/audio/b2-d1-l1.mp3 b/audio/b2-d1-l1.mp3
new file mode 100644
index 0000000..480dea9
Binary files /dev/null and b/audio/b2-d1-l1.mp3 differ
diff --git a/audio/b2-d1-l2.mp3 b/audio/b2-d1-l2.mp3
new file mode 100644
index 0000000..74758d0
Binary files /dev/null and b/audio/b2-d1-l2.mp3 differ
diff --git a/audio/b2-d1-l3.mp3 b/audio/b2-d1-l3.mp3
new file mode 100644
index 0000000..9003ef8
Binary files /dev/null and b/audio/b2-d1-l3.mp3 differ
diff --git a/audio/b2-d1-l4.mp3 b/audio/b2-d1-l4.mp3
new file mode 100644
index 0000000..3afcfb6
Binary files /dev/null and b/audio/b2-d1-l4.mp3 differ
diff --git a/audio/b2-d1-l5.mp3 b/audio/b2-d1-l5.mp3
new file mode 100644
index 0000000..723dd7a
Binary files /dev/null and b/audio/b2-d1-l5.mp3 differ
diff --git a/audio/b2-d1-l6.mp3 b/audio/b2-d1-l6.mp3
new file mode 100644
index 0000000..9c25b7c
Binary files /dev/null and b/audio/b2-d1-l6.mp3 differ
diff --git a/audio/b2-d1-l7.mp3 b/audio/b2-d1-l7.mp3
new file mode 100644
index 0000000..eaaa965
Binary files /dev/null and b/audio/b2-d1-l7.mp3 differ
diff --git a/audio/b2-d2-l1.mp3 b/audio/b2-d2-l1.mp3
new file mode 100644
index 0000000..d85f3ec
Binary files /dev/null and b/audio/b2-d2-l1.mp3 differ
diff --git a/audio/b2-d2-l2.mp3 b/audio/b2-d2-l2.mp3
new file mode 100644
index 0000000..d884708
Binary files /dev/null and b/audio/b2-d2-l2.mp3 differ
diff --git a/audio/b2-d2-l3.mp3 b/audio/b2-d2-l3.mp3
new file mode 100644
index 0000000..ff48915
Binary files /dev/null and b/audio/b2-d2-l3.mp3 differ
diff --git a/audio/b2-d2-l4.mp3 b/audio/b2-d2-l4.mp3
new file mode 100644
index 0000000..eb8eacb
Binary files /dev/null and b/audio/b2-d2-l4.mp3 differ
diff --git a/audio/b2-d2-l5.mp3 b/audio/b2-d2-l5.mp3
new file mode 100644
index 0000000..69acc1f
Binary files /dev/null and b/audio/b2-d2-l5.mp3 differ
diff --git a/audio/b2-d2-l6.mp3 b/audio/b2-d2-l6.mp3
new file mode 100644
index 0000000..22ea5fe
Binary files /dev/null and b/audio/b2-d2-l6.mp3 differ
diff --git a/audio/b3-chunk-01.mp3 b/audio/b3-chunk-01.mp3
new file mode 100644
index 0000000..ce00167
Binary files /dev/null and b/audio/b3-chunk-01.mp3 differ
diff --git a/audio/b3-chunk-02.mp3 b/audio/b3-chunk-02.mp3
new file mode 100644
index 0000000..6bc1b0f
Binary files /dev/null and b/audio/b3-chunk-02.mp3 differ
diff --git a/audio/b3-chunk-03.mp3 b/audio/b3-chunk-03.mp3
new file mode 100644
index 0000000..f396654
Binary files /dev/null and b/audio/b3-chunk-03.mp3 differ
diff --git a/audio/b3-chunk-04.mp3 b/audio/b3-chunk-04.mp3
new file mode 100644
index 0000000..57ece26
Binary files /dev/null and b/audio/b3-chunk-04.mp3 differ
diff --git a/audio/b3-chunk-05.mp3 b/audio/b3-chunk-05.mp3
new file mode 100644
index 0000000..2cc8f32
Binary files /dev/null and b/audio/b3-chunk-05.mp3 differ
diff --git a/audio/b3-chunk-06.mp3 b/audio/b3-chunk-06.mp3
new file mode 100644
index 0000000..3dce6c3
Binary files /dev/null and b/audio/b3-chunk-06.mp3 differ
diff --git a/audio/b3-chunk-07.mp3 b/audio/b3-chunk-07.mp3
new file mode 100644
index 0000000..65f5247
Binary files /dev/null and b/audio/b3-chunk-07.mp3 differ
diff --git a/audio/b3-chunk-08.mp3 b/audio/b3-chunk-08.mp3
new file mode 100644
index 0000000..5961112
Binary files /dev/null and b/audio/b3-chunk-08.mp3 differ
diff --git a/audio/b3-chunk-09.mp3 b/audio/b3-chunk-09.mp3
new file mode 100644
index 0000000..2fe2c41
Binary files /dev/null and b/audio/b3-chunk-09.mp3 differ
diff --git a/audio/b3-chunk-10.mp3 b/audio/b3-chunk-10.mp3
new file mode 100644
index 0000000..ee2bca7
Binary files /dev/null and b/audio/b3-chunk-10.mp3 differ
diff --git a/audio/b3-chunk-11.mp3 b/audio/b3-chunk-11.mp3
new file mode 100644
index 0000000..374ed89
Binary files /dev/null and b/audio/b3-chunk-11.mp3 differ
diff --git a/audio/b3-chunk-12.mp3 b/audio/b3-chunk-12.mp3
new file mode 100644
index 0000000..7b9280c
Binary files /dev/null and b/audio/b3-chunk-12.mp3 differ
diff --git a/audio/b3-chunk-13.mp3 b/audio/b3-chunk-13.mp3
new file mode 100644
index 0000000..13d4b2b
Binary files /dev/null and b/audio/b3-chunk-13.mp3 differ
diff --git a/audio/b3-d1-l1.mp3 b/audio/b3-d1-l1.mp3
new file mode 100644
index 0000000..ea9ac42
Binary files /dev/null and b/audio/b3-d1-l1.mp3 differ
diff --git a/audio/b3-d1-l2.mp3 b/audio/b3-d1-l2.mp3
new file mode 100644
index 0000000..1ac3021
Binary files /dev/null and b/audio/b3-d1-l2.mp3 differ
diff --git a/audio/b3-d1-l3.mp3 b/audio/b3-d1-l3.mp3
new file mode 100644
index 0000000..9a48165
Binary files /dev/null and b/audio/b3-d1-l3.mp3 differ
diff --git a/audio/b3-d1-l4.mp3 b/audio/b3-d1-l4.mp3
new file mode 100644
index 0000000..18a0500
Binary files /dev/null and b/audio/b3-d1-l4.mp3 differ
diff --git a/audio/b3-d1-l5.mp3 b/audio/b3-d1-l5.mp3
new file mode 100644
index 0000000..50144a1
Binary files /dev/null and b/audio/b3-d1-l5.mp3 differ
diff --git a/audio/b3-d1-l6.mp3 b/audio/b3-d1-l6.mp3
new file mode 100644
index 0000000..93301c5
Binary files /dev/null and b/audio/b3-d1-l6.mp3 differ
diff --git a/audio/b3-d1-l7.mp3 b/audio/b3-d1-l7.mp3
new file mode 100644
index 0000000..f9547aa
Binary files /dev/null and b/audio/b3-d1-l7.mp3 differ
diff --git a/audio/b3-d2-l1.mp3 b/audio/b3-d2-l1.mp3
new file mode 100644
index 0000000..7623035
Binary files /dev/null and b/audio/b3-d2-l1.mp3 differ
diff --git a/audio/b3-d2-l2.mp3 b/audio/b3-d2-l2.mp3
new file mode 100644
index 0000000..3e159bd
Binary files /dev/null and b/audio/b3-d2-l2.mp3 differ
diff --git a/audio/b3-d2-l3.mp3 b/audio/b3-d2-l3.mp3
new file mode 100644
index 0000000..8cc16ce
Binary files /dev/null and b/audio/b3-d2-l3.mp3 differ
diff --git a/audio/b3-d2-l4.mp3 b/audio/b3-d2-l4.mp3
new file mode 100644
index 0000000..e8eceff
Binary files /dev/null and b/audio/b3-d2-l4.mp3 differ
diff --git a/audio/b3-d2-l5.mp3 b/audio/b3-d2-l5.mp3
new file mode 100644
index 0000000..ffd7b81
Binary files /dev/null and b/audio/b3-d2-l5.mp3 differ
diff --git a/audio/b3-d2-l6.mp3 b/audio/b3-d2-l6.mp3
new file mode 100644
index 0000000..b7721e2
Binary files /dev/null and b/audio/b3-d2-l6.mp3 differ
diff --git a/audio/b4-chunk-01.mp3 b/audio/b4-chunk-01.mp3
new file mode 100644
index 0000000..a90015c
Binary files /dev/null and b/audio/b4-chunk-01.mp3 differ
diff --git a/audio/b4-chunk-02.mp3 b/audio/b4-chunk-02.mp3
new file mode 100644
index 0000000..1f742a6
Binary files /dev/null and b/audio/b4-chunk-02.mp3 differ
diff --git a/audio/b4-chunk-03.mp3 b/audio/b4-chunk-03.mp3
new file mode 100644
index 0000000..2c7cc06
Binary files /dev/null and b/audio/b4-chunk-03.mp3 differ
diff --git a/audio/b4-chunk-04.mp3 b/audio/b4-chunk-04.mp3
new file mode 100644
index 0000000..260b4aa
Binary files /dev/null and b/audio/b4-chunk-04.mp3 differ
diff --git a/audio/b4-chunk-05.mp3 b/audio/b4-chunk-05.mp3
new file mode 100644
index 0000000..8c31e66
Binary files /dev/null and b/audio/b4-chunk-05.mp3 differ
diff --git a/audio/b4-chunk-06.mp3 b/audio/b4-chunk-06.mp3
new file mode 100644
index 0000000..48b7d02
Binary files /dev/null and b/audio/b4-chunk-06.mp3 differ
diff --git a/audio/b4-chunk-07.mp3 b/audio/b4-chunk-07.mp3
new file mode 100644
index 0000000..7423ef0
Binary files /dev/null and b/audio/b4-chunk-07.mp3 differ
diff --git a/audio/b4-chunk-08.mp3 b/audio/b4-chunk-08.mp3
new file mode 100644
index 0000000..e0faaeb
Binary files /dev/null and b/audio/b4-chunk-08.mp3 differ
diff --git a/audio/b4-chunk-09.mp3 b/audio/b4-chunk-09.mp3
new file mode 100644
index 0000000..ff03dea
Binary files /dev/null and b/audio/b4-chunk-09.mp3 differ
diff --git a/audio/b4-chunk-10.mp3 b/audio/b4-chunk-10.mp3
new file mode 100644
index 0000000..c3e0c94
Binary files /dev/null and b/audio/b4-chunk-10.mp3 differ
diff --git a/audio/b4-chunk-11.mp3 b/audio/b4-chunk-11.mp3
new file mode 100644
index 0000000..23c78b0
Binary files /dev/null and b/audio/b4-chunk-11.mp3 differ
diff --git a/audio/b4-chunk-12.mp3 b/audio/b4-chunk-12.mp3
new file mode 100644
index 0000000..0fb5407
Binary files /dev/null and b/audio/b4-chunk-12.mp3 differ
diff --git a/audio/b4-d1-l1.mp3 b/audio/b4-d1-l1.mp3
new file mode 100644
index 0000000..0e3c20d
Binary files /dev/null and b/audio/b4-d1-l1.mp3 differ
diff --git a/audio/b4-d1-l2.mp3 b/audio/b4-d1-l2.mp3
new file mode 100644
index 0000000..1a894d1
Binary files /dev/null and b/audio/b4-d1-l2.mp3 differ
diff --git a/audio/b4-d1-l3.mp3 b/audio/b4-d1-l3.mp3
new file mode 100644
index 0000000..5e10565
Binary files /dev/null and b/audio/b4-d1-l3.mp3 differ
diff --git a/audio/b4-d1-l4.mp3 b/audio/b4-d1-l4.mp3
new file mode 100644
index 0000000..e2cc241
Binary files /dev/null and b/audio/b4-d1-l4.mp3 differ
diff --git a/audio/b4-d1-l5.mp3 b/audio/b4-d1-l5.mp3
new file mode 100644
index 0000000..84b8664
Binary files /dev/null and b/audio/b4-d1-l5.mp3 differ
diff --git a/audio/b4-d1-l6.mp3 b/audio/b4-d1-l6.mp3
new file mode 100644
index 0000000..28c4016
Binary files /dev/null and b/audio/b4-d1-l6.mp3 differ
diff --git a/audio/b4-d2-l1.mp3 b/audio/b4-d2-l1.mp3
new file mode 100644
index 0000000..ab7f6b7
Binary files /dev/null and b/audio/b4-d2-l1.mp3 differ
diff --git a/audio/b4-d2-l2.mp3 b/audio/b4-d2-l2.mp3
new file mode 100644
index 0000000..d8ce943
Binary files /dev/null and b/audio/b4-d2-l2.mp3 differ
diff --git a/audio/b4-d2-l3.mp3 b/audio/b4-d2-l3.mp3
new file mode 100644
index 0000000..b0585a0
Binary files /dev/null and b/audio/b4-d2-l3.mp3 differ
diff --git a/audio/b4-d2-l4.mp3 b/audio/b4-d2-l4.mp3
new file mode 100644
index 0000000..9e141b7
Binary files /dev/null and b/audio/b4-d2-l4.mp3 differ
diff --git a/audio/b4-d2-l5.mp3 b/audio/b4-d2-l5.mp3
new file mode 100644
index 0000000..bddd0f8
Binary files /dev/null and b/audio/b4-d2-l5.mp3 differ
diff --git a/audio/b4-d2-l6.mp3 b/audio/b4-d2-l6.mp3
new file mode 100644
index 0000000..97e042b
Binary files /dev/null and b/audio/b4-d2-l6.mp3 differ
diff --git a/audio/b5-chunk-01.mp3 b/audio/b5-chunk-01.mp3
new file mode 100644
index 0000000..b33ceeb
Binary files /dev/null and b/audio/b5-chunk-01.mp3 differ
diff --git a/audio/b5-chunk-02.mp3 b/audio/b5-chunk-02.mp3
new file mode 100644
index 0000000..ba861f4
Binary files /dev/null and b/audio/b5-chunk-02.mp3 differ
diff --git a/audio/b5-chunk-03.mp3 b/audio/b5-chunk-03.mp3
new file mode 100644
index 0000000..df6a6a7
Binary files /dev/null and b/audio/b5-chunk-03.mp3 differ
diff --git a/audio/b5-chunk-04.mp3 b/audio/b5-chunk-04.mp3
new file mode 100644
index 0000000..a4b1a30
Binary files /dev/null and b/audio/b5-chunk-04.mp3 differ
diff --git a/audio/b5-chunk-05.mp3 b/audio/b5-chunk-05.mp3
new file mode 100644
index 0000000..29d503e
Binary files /dev/null and b/audio/b5-chunk-05.mp3 differ
diff --git a/audio/b5-chunk-06.mp3 b/audio/b5-chunk-06.mp3
new file mode 100644
index 0000000..bb55523
Binary files /dev/null and b/audio/b5-chunk-06.mp3 differ
diff --git a/audio/b5-chunk-07.mp3 b/audio/b5-chunk-07.mp3
new file mode 100644
index 0000000..57bea51
Binary files /dev/null and b/audio/b5-chunk-07.mp3 differ
diff --git a/audio/b5-chunk-08.mp3 b/audio/b5-chunk-08.mp3
new file mode 100644
index 0000000..bcd0d31
Binary files /dev/null and b/audio/b5-chunk-08.mp3 differ
diff --git a/audio/b5-chunk-09.mp3 b/audio/b5-chunk-09.mp3
new file mode 100644
index 0000000..97acfab
Binary files /dev/null and b/audio/b5-chunk-09.mp3 differ
diff --git a/audio/b5-chunk-10.mp3 b/audio/b5-chunk-10.mp3
new file mode 100644
index 0000000..38ec3da
Binary files /dev/null and b/audio/b5-chunk-10.mp3 differ
diff --git a/audio/b5-chunk-11.mp3 b/audio/b5-chunk-11.mp3
new file mode 100644
index 0000000..f79b8ad
Binary files /dev/null and b/audio/b5-chunk-11.mp3 differ
diff --git a/audio/b5-chunk-12.mp3 b/audio/b5-chunk-12.mp3
new file mode 100644
index 0000000..4ae799e
Binary files /dev/null and b/audio/b5-chunk-12.mp3 differ
diff --git a/audio/b5-chunk-13.mp3 b/audio/b5-chunk-13.mp3
new file mode 100644
index 0000000..4c02b4b
Binary files /dev/null and b/audio/b5-chunk-13.mp3 differ
diff --git a/audio/b5-d1-l1.mp3 b/audio/b5-d1-l1.mp3
new file mode 100644
index 0000000..f83d314
Binary files /dev/null and b/audio/b5-d1-l1.mp3 differ
diff --git a/audio/b5-d1-l2.mp3 b/audio/b5-d1-l2.mp3
new file mode 100644
index 0000000..4eb999b
Binary files /dev/null and b/audio/b5-d1-l2.mp3 differ
diff --git a/audio/b5-d1-l3.mp3 b/audio/b5-d1-l3.mp3
new file mode 100644
index 0000000..ab77962
Binary files /dev/null and b/audio/b5-d1-l3.mp3 differ
diff --git a/audio/b5-d1-l4.mp3 b/audio/b5-d1-l4.mp3
new file mode 100644
index 0000000..4829a7d
Binary files /dev/null and b/audio/b5-d1-l4.mp3 differ
diff --git a/audio/b5-d1-l5.mp3 b/audio/b5-d1-l5.mp3
new file mode 100644
index 0000000..99fd74d
Binary files /dev/null and b/audio/b5-d1-l5.mp3 differ
diff --git a/audio/b5-d1-l6.mp3 b/audio/b5-d1-l6.mp3
new file mode 100644
index 0000000..fc6aa4d
Binary files /dev/null and b/audio/b5-d1-l6.mp3 differ
diff --git a/audio/b5-d1-l7.mp3 b/audio/b5-d1-l7.mp3
new file mode 100644
index 0000000..e178dd1
Binary files /dev/null and b/audio/b5-d1-l7.mp3 differ
diff --git a/audio/b5-d2-l1.mp3 b/audio/b5-d2-l1.mp3
new file mode 100644
index 0000000..93101a7
Binary files /dev/null and b/audio/b5-d2-l1.mp3 differ
diff --git a/audio/b5-d2-l2.mp3 b/audio/b5-d2-l2.mp3
new file mode 100644
index 0000000..3f6e50d
Binary files /dev/null and b/audio/b5-d2-l2.mp3 differ
diff --git a/audio/b5-d2-l3.mp3 b/audio/b5-d2-l3.mp3
new file mode 100644
index 0000000..189779e
Binary files /dev/null and b/audio/b5-d2-l3.mp3 differ
diff --git a/audio/b5-d2-l4.mp3 b/audio/b5-d2-l4.mp3
new file mode 100644
index 0000000..f8788f5
Binary files /dev/null and b/audio/b5-d2-l4.mp3 differ
diff --git a/audio/b5-d2-l5.mp3 b/audio/b5-d2-l5.mp3
new file mode 100644
index 0000000..484a72e
Binary files /dev/null and b/audio/b5-d2-l5.mp3 differ
diff --git a/audio/b5-d2-l6.mp3 b/audio/b5-d2-l6.mp3
new file mode 100644
index 0000000..91952eb
Binary files /dev/null and b/audio/b5-d2-l6.mp3 differ
diff --git a/audio/b6-chunk-01.mp3 b/audio/b6-chunk-01.mp3
new file mode 100644
index 0000000..ed6d8cc
Binary files /dev/null and b/audio/b6-chunk-01.mp3 differ
diff --git a/audio/b6-chunk-02.mp3 b/audio/b6-chunk-02.mp3
new file mode 100644
index 0000000..36d0971
Binary files /dev/null and b/audio/b6-chunk-02.mp3 differ
diff --git a/audio/b6-chunk-03.mp3 b/audio/b6-chunk-03.mp3
new file mode 100644
index 0000000..6cc6626
Binary files /dev/null and b/audio/b6-chunk-03.mp3 differ
diff --git a/audio/b6-chunk-04.mp3 b/audio/b6-chunk-04.mp3
new file mode 100644
index 0000000..5873b9e
Binary files /dev/null and b/audio/b6-chunk-04.mp3 differ
diff --git a/audio/b6-chunk-05.mp3 b/audio/b6-chunk-05.mp3
new file mode 100644
index 0000000..e36826b
Binary files /dev/null and b/audio/b6-chunk-05.mp3 differ
diff --git a/audio/b6-chunk-06.mp3 b/audio/b6-chunk-06.mp3
new file mode 100644
index 0000000..a68575a
Binary files /dev/null and b/audio/b6-chunk-06.mp3 differ
diff --git a/audio/b6-chunk-07.mp3 b/audio/b6-chunk-07.mp3
new file mode 100644
index 0000000..2254cf2
Binary files /dev/null and b/audio/b6-chunk-07.mp3 differ
diff --git a/audio/b6-chunk-08.mp3 b/audio/b6-chunk-08.mp3
new file mode 100644
index 0000000..57405a6
Binary files /dev/null and b/audio/b6-chunk-08.mp3 differ
diff --git a/audio/b6-chunk-09.mp3 b/audio/b6-chunk-09.mp3
new file mode 100644
index 0000000..55eb521
Binary files /dev/null and b/audio/b6-chunk-09.mp3 differ
diff --git a/audio/b6-chunk-10.mp3 b/audio/b6-chunk-10.mp3
new file mode 100644
index 0000000..ecf23a5
Binary files /dev/null and b/audio/b6-chunk-10.mp3 differ
diff --git a/audio/b6-chunk-11.mp3 b/audio/b6-chunk-11.mp3
new file mode 100644
index 0000000..7325f01
Binary files /dev/null and b/audio/b6-chunk-11.mp3 differ
diff --git a/audio/b6-chunk-12.mp3 b/audio/b6-chunk-12.mp3
new file mode 100644
index 0000000..4b79e0a
Binary files /dev/null and b/audio/b6-chunk-12.mp3 differ
diff --git a/audio/b6-d1-l1.mp3 b/audio/b6-d1-l1.mp3
new file mode 100644
index 0000000..72b6102
Binary files /dev/null and b/audio/b6-d1-l1.mp3 differ
diff --git a/audio/b6-d1-l2.mp3 b/audio/b6-d1-l2.mp3
new file mode 100644
index 0000000..2801534
Binary files /dev/null and b/audio/b6-d1-l2.mp3 differ
diff --git a/audio/b6-d1-l3.mp3 b/audio/b6-d1-l3.mp3
new file mode 100644
index 0000000..76674bb
Binary files /dev/null and b/audio/b6-d1-l3.mp3 differ
diff --git a/audio/b6-d1-l4.mp3 b/audio/b6-d1-l4.mp3
new file mode 100644
index 0000000..7d9e998
Binary files /dev/null and b/audio/b6-d1-l4.mp3 differ
diff --git a/audio/b6-d1-l5.mp3 b/audio/b6-d1-l5.mp3
new file mode 100644
index 0000000..659a3ad
Binary files /dev/null and b/audio/b6-d1-l5.mp3 differ
diff --git a/audio/b6-d1-l6.mp3 b/audio/b6-d1-l6.mp3
new file mode 100644
index 0000000..eee2f20
Binary files /dev/null and b/audio/b6-d1-l6.mp3 differ
diff --git a/audio/b6-d2-l1.mp3 b/audio/b6-d2-l1.mp3
new file mode 100644
index 0000000..5c6486d
Binary files /dev/null and b/audio/b6-d2-l1.mp3 differ
diff --git a/audio/b6-d2-l2.mp3 b/audio/b6-d2-l2.mp3
new file mode 100644
index 0000000..439c5b4
Binary files /dev/null and b/audio/b6-d2-l2.mp3 differ
diff --git a/audio/b6-d2-l3.mp3 b/audio/b6-d2-l3.mp3
new file mode 100644
index 0000000..63a8363
Binary files /dev/null and b/audio/b6-d2-l3.mp3 differ
diff --git a/audio/b6-d2-l4.mp3 b/audio/b6-d2-l4.mp3
new file mode 100644
index 0000000..7039369
Binary files /dev/null and b/audio/b6-d2-l4.mp3 differ
diff --git a/audio/b6-d2-l5.mp3 b/audio/b6-d2-l5.mp3
new file mode 100644
index 0000000..0c5be72
Binary files /dev/null and b/audio/b6-d2-l5.mp3 differ
diff --git a/audio/b6-d2-l6.mp3 b/audio/b6-d2-l6.mp3
new file mode 100644
index 0000000..b181518
Binary files /dev/null and b/audio/b6-d2-l6.mp3 differ
diff --git a/audio/b6-d2-l7.mp3 b/audio/b6-d2-l7.mp3
new file mode 100644
index 0000000..e17e3d3
Binary files /dev/null and b/audio/b6-d2-l7.mp3 differ
diff --git a/audio/b7-chunk-01.mp3 b/audio/b7-chunk-01.mp3
new file mode 100644
index 0000000..2d6ac6c
Binary files /dev/null and b/audio/b7-chunk-01.mp3 differ
diff --git a/audio/b7-chunk-02.mp3 b/audio/b7-chunk-02.mp3
new file mode 100644
index 0000000..8faab4b
Binary files /dev/null and b/audio/b7-chunk-02.mp3 differ
diff --git a/audio/b7-chunk-03.mp3 b/audio/b7-chunk-03.mp3
new file mode 100644
index 0000000..459d26a
Binary files /dev/null and b/audio/b7-chunk-03.mp3 differ
diff --git a/audio/b7-chunk-04.mp3 b/audio/b7-chunk-04.mp3
new file mode 100644
index 0000000..fe2dbe0
Binary files /dev/null and b/audio/b7-chunk-04.mp3 differ
diff --git a/audio/b7-chunk-05.mp3 b/audio/b7-chunk-05.mp3
new file mode 100644
index 0000000..edbd69a
Binary files /dev/null and b/audio/b7-chunk-05.mp3 differ
diff --git a/audio/b7-chunk-06.mp3 b/audio/b7-chunk-06.mp3
new file mode 100644
index 0000000..5eecf9e
Binary files /dev/null and b/audio/b7-chunk-06.mp3 differ
diff --git a/audio/b7-chunk-07.mp3 b/audio/b7-chunk-07.mp3
new file mode 100644
index 0000000..9b8a6b9
Binary files /dev/null and b/audio/b7-chunk-07.mp3 differ
diff --git a/audio/b7-chunk-08.mp3 b/audio/b7-chunk-08.mp3
new file mode 100644
index 0000000..3fcd600
Binary files /dev/null and b/audio/b7-chunk-08.mp3 differ
diff --git a/audio/b7-chunk-09.mp3 b/audio/b7-chunk-09.mp3
new file mode 100644
index 0000000..6b1bb03
Binary files /dev/null and b/audio/b7-chunk-09.mp3 differ
diff --git a/audio/b7-chunk-10.mp3 b/audio/b7-chunk-10.mp3
new file mode 100644
index 0000000..dcdc0e5
Binary files /dev/null and b/audio/b7-chunk-10.mp3 differ
diff --git a/audio/b7-chunk-11.mp3 b/audio/b7-chunk-11.mp3
new file mode 100644
index 0000000..e85273d
Binary files /dev/null and b/audio/b7-chunk-11.mp3 differ
diff --git a/audio/b7-chunk-12.mp3 b/audio/b7-chunk-12.mp3
new file mode 100644
index 0000000..6356390
Binary files /dev/null and b/audio/b7-chunk-12.mp3 differ
diff --git a/audio/b7-chunk-13.mp3 b/audio/b7-chunk-13.mp3
new file mode 100644
index 0000000..c6a4507
Binary files /dev/null and b/audio/b7-chunk-13.mp3 differ
diff --git a/audio/b7-chunk-14.mp3 b/audio/b7-chunk-14.mp3
new file mode 100644
index 0000000..4f05ddc
Binary files /dev/null and b/audio/b7-chunk-14.mp3 differ
diff --git a/audio/b7-d1-l1.mp3 b/audio/b7-d1-l1.mp3
new file mode 100644
index 0000000..e8ae560
Binary files /dev/null and b/audio/b7-d1-l1.mp3 differ
diff --git a/audio/b7-d1-l2.mp3 b/audio/b7-d1-l2.mp3
new file mode 100644
index 0000000..54f3c38
Binary files /dev/null and b/audio/b7-d1-l2.mp3 differ
diff --git a/audio/b7-d1-l3.mp3 b/audio/b7-d1-l3.mp3
new file mode 100644
index 0000000..34168a4
Binary files /dev/null and b/audio/b7-d1-l3.mp3 differ
diff --git a/audio/b7-d1-l4.mp3 b/audio/b7-d1-l4.mp3
new file mode 100644
index 0000000..1e0956f
Binary files /dev/null and b/audio/b7-d1-l4.mp3 differ
diff --git a/audio/b7-d1-l5.mp3 b/audio/b7-d1-l5.mp3
new file mode 100644
index 0000000..67d550f
Binary files /dev/null and b/audio/b7-d1-l5.mp3 differ
diff --git a/audio/b7-d1-l6.mp3 b/audio/b7-d1-l6.mp3
new file mode 100644
index 0000000..afe273f
Binary files /dev/null and b/audio/b7-d1-l6.mp3 differ
diff --git a/audio/b7-d2-l1.mp3 b/audio/b7-d2-l1.mp3
new file mode 100644
index 0000000..3da7d95
Binary files /dev/null and b/audio/b7-d2-l1.mp3 differ
diff --git a/audio/b7-d2-l2.mp3 b/audio/b7-d2-l2.mp3
new file mode 100644
index 0000000..18b2ef4
Binary files /dev/null and b/audio/b7-d2-l2.mp3 differ
diff --git a/audio/b7-d2-l3.mp3 b/audio/b7-d2-l3.mp3
new file mode 100644
index 0000000..d86bb37
Binary files /dev/null and b/audio/b7-d2-l3.mp3 differ
diff --git a/audio/b7-d2-l4.mp3 b/audio/b7-d2-l4.mp3
new file mode 100644
index 0000000..4012bbf
Binary files /dev/null and b/audio/b7-d2-l4.mp3 differ
diff --git a/audio/b7-d2-l5.mp3 b/audio/b7-d2-l5.mp3
new file mode 100644
index 0000000..7420e64
Binary files /dev/null and b/audio/b7-d2-l5.mp3 differ
diff --git a/audio/b7-d2-l6.mp3 b/audio/b7-d2-l6.mp3
new file mode 100644
index 0000000..a2eae43
Binary files /dev/null and b/audio/b7-d2-l6.mp3 differ
diff --git a/audio/b7-d2-l7.mp3 b/audio/b7-d2-l7.mp3
new file mode 100644
index 0000000..4b28ee0
Binary files /dev/null and b/audio/b7-d2-l7.mp3 differ
diff --git a/audio/b8-chunk-01.mp3 b/audio/b8-chunk-01.mp3
new file mode 100644
index 0000000..31a1bf9
Binary files /dev/null and b/audio/b8-chunk-01.mp3 differ
diff --git a/audio/b8-chunk-02.mp3 b/audio/b8-chunk-02.mp3
new file mode 100644
index 0000000..45a783e
Binary files /dev/null and b/audio/b8-chunk-02.mp3 differ
diff --git a/audio/b8-chunk-03.mp3 b/audio/b8-chunk-03.mp3
new file mode 100644
index 0000000..5a4592f
Binary files /dev/null and b/audio/b8-chunk-03.mp3 differ
diff --git a/audio/b8-chunk-04.mp3 b/audio/b8-chunk-04.mp3
new file mode 100644
index 0000000..8da2847
Binary files /dev/null and b/audio/b8-chunk-04.mp3 differ
diff --git a/audio/b8-chunk-05.mp3 b/audio/b8-chunk-05.mp3
new file mode 100644
index 0000000..f3ec1d5
Binary files /dev/null and b/audio/b8-chunk-05.mp3 differ
diff --git a/audio/b8-chunk-06.mp3 b/audio/b8-chunk-06.mp3
new file mode 100644
index 0000000..73d5ea4
Binary files /dev/null and b/audio/b8-chunk-06.mp3 differ
diff --git a/audio/b8-chunk-07.mp3 b/audio/b8-chunk-07.mp3
new file mode 100644
index 0000000..fd35788
Binary files /dev/null and b/audio/b8-chunk-07.mp3 differ
diff --git a/audio/b8-chunk-08.mp3 b/audio/b8-chunk-08.mp3
new file mode 100644
index 0000000..5f3c23c
Binary files /dev/null and b/audio/b8-chunk-08.mp3 differ
diff --git a/audio/b8-chunk-09.mp3 b/audio/b8-chunk-09.mp3
new file mode 100644
index 0000000..6ad0a0c
Binary files /dev/null and b/audio/b8-chunk-09.mp3 differ
diff --git a/audio/b8-chunk-10.mp3 b/audio/b8-chunk-10.mp3
new file mode 100644
index 0000000..9a3c507
Binary files /dev/null and b/audio/b8-chunk-10.mp3 differ
diff --git a/audio/b8-chunk-11.mp3 b/audio/b8-chunk-11.mp3
new file mode 100644
index 0000000..201fe83
Binary files /dev/null and b/audio/b8-chunk-11.mp3 differ
diff --git a/audio/b8-chunk-12.mp3 b/audio/b8-chunk-12.mp3
new file mode 100644
index 0000000..eca53d3
Binary files /dev/null and b/audio/b8-chunk-12.mp3 differ
diff --git a/audio/b8-d1-l1.mp3 b/audio/b8-d1-l1.mp3
new file mode 100644
index 0000000..de77fad
Binary files /dev/null and b/audio/b8-d1-l1.mp3 differ
diff --git a/audio/b8-d1-l2.mp3 b/audio/b8-d1-l2.mp3
new file mode 100644
index 0000000..0c40e16
Binary files /dev/null and b/audio/b8-d1-l2.mp3 differ
diff --git a/audio/b8-d1-l3.mp3 b/audio/b8-d1-l3.mp3
new file mode 100644
index 0000000..5f1efbf
Binary files /dev/null and b/audio/b8-d1-l3.mp3 differ
diff --git a/audio/b8-d1-l4.mp3 b/audio/b8-d1-l4.mp3
new file mode 100644
index 0000000..4c1d3bf
Binary files /dev/null and b/audio/b8-d1-l4.mp3 differ
diff --git a/audio/b8-d1-l5.mp3 b/audio/b8-d1-l5.mp3
new file mode 100644
index 0000000..91e7f70
Binary files /dev/null and b/audio/b8-d1-l5.mp3 differ
diff --git a/audio/b8-d1-l6.mp3 b/audio/b8-d1-l6.mp3
new file mode 100644
index 0000000..68799b0
Binary files /dev/null and b/audio/b8-d1-l6.mp3 differ
diff --git a/audio/b8-d1-l7.mp3 b/audio/b8-d1-l7.mp3
new file mode 100644
index 0000000..fcbc106
Binary files /dev/null and b/audio/b8-d1-l7.mp3 differ
diff --git a/audio/b8-d2-l1.mp3 b/audio/b8-d2-l1.mp3
new file mode 100644
index 0000000..f4dd792
Binary files /dev/null and b/audio/b8-d2-l1.mp3 differ
diff --git a/audio/b8-d2-l2.mp3 b/audio/b8-d2-l2.mp3
new file mode 100644
index 0000000..3c5f66a
Binary files /dev/null and b/audio/b8-d2-l2.mp3 differ
diff --git a/audio/b8-d2-l3.mp3 b/audio/b8-d2-l3.mp3
new file mode 100644
index 0000000..5a9eb37
Binary files /dev/null and b/audio/b8-d2-l3.mp3 differ
diff --git a/audio/b8-d2-l4.mp3 b/audio/b8-d2-l4.mp3
new file mode 100644
index 0000000..f8fbf3a
Binary files /dev/null and b/audio/b8-d2-l4.mp3 differ
diff --git a/audio/b8-d2-l5.mp3 b/audio/b8-d2-l5.mp3
new file mode 100644
index 0000000..a172eae
Binary files /dev/null and b/audio/b8-d2-l5.mp3 differ
diff --git a/audio/b8-d2-l6.mp3 b/audio/b8-d2-l6.mp3
new file mode 100644
index 0000000..b5ecd4c
Binary files /dev/null and b/audio/b8-d2-l6.mp3 differ
diff --git a/audio/b9-chunk-01.mp3 b/audio/b9-chunk-01.mp3
new file mode 100644
index 0000000..47f4465
Binary files /dev/null and b/audio/b9-chunk-01.mp3 differ
diff --git a/audio/b9-chunk-02.mp3 b/audio/b9-chunk-02.mp3
new file mode 100644
index 0000000..f7628ba
Binary files /dev/null and b/audio/b9-chunk-02.mp3 differ
diff --git a/audio/b9-chunk-03.mp3 b/audio/b9-chunk-03.mp3
new file mode 100644
index 0000000..0cc9733
Binary files /dev/null and b/audio/b9-chunk-03.mp3 differ
diff --git a/audio/b9-chunk-04.mp3 b/audio/b9-chunk-04.mp3
new file mode 100644
index 0000000..0853240
Binary files /dev/null and b/audio/b9-chunk-04.mp3 differ
diff --git a/audio/b9-chunk-05.mp3 b/audio/b9-chunk-05.mp3
new file mode 100644
index 0000000..008d0bd
Binary files /dev/null and b/audio/b9-chunk-05.mp3 differ
diff --git a/audio/b9-chunk-06.mp3 b/audio/b9-chunk-06.mp3
new file mode 100644
index 0000000..e657d03
Binary files /dev/null and b/audio/b9-chunk-06.mp3 differ
diff --git a/audio/b9-chunk-07.mp3 b/audio/b9-chunk-07.mp3
new file mode 100644
index 0000000..f76c41f
Binary files /dev/null and b/audio/b9-chunk-07.mp3 differ
diff --git a/audio/b9-chunk-08.mp3 b/audio/b9-chunk-08.mp3
new file mode 100644
index 0000000..9452d78
Binary files /dev/null and b/audio/b9-chunk-08.mp3 differ
diff --git a/audio/b9-chunk-09.mp3 b/audio/b9-chunk-09.mp3
new file mode 100644
index 0000000..421c8fe
Binary files /dev/null and b/audio/b9-chunk-09.mp3 differ
diff --git a/audio/b9-chunk-10.mp3 b/audio/b9-chunk-10.mp3
new file mode 100644
index 0000000..6627aad
Binary files /dev/null and b/audio/b9-chunk-10.mp3 differ
diff --git a/audio/b9-chunk-11.mp3 b/audio/b9-chunk-11.mp3
new file mode 100644
index 0000000..95eec00
Binary files /dev/null and b/audio/b9-chunk-11.mp3 differ
diff --git a/audio/b9-chunk-12.mp3 b/audio/b9-chunk-12.mp3
new file mode 100644
index 0000000..77ca756
Binary files /dev/null and b/audio/b9-chunk-12.mp3 differ
diff --git a/audio/b9-chunk-13.mp3 b/audio/b9-chunk-13.mp3
new file mode 100644
index 0000000..594ebcd
Binary files /dev/null and b/audio/b9-chunk-13.mp3 differ
diff --git a/audio/b9-d1-l1.mp3 b/audio/b9-d1-l1.mp3
new file mode 100644
index 0000000..0f8014f
Binary files /dev/null and b/audio/b9-d1-l1.mp3 differ
diff --git a/audio/b9-d1-l2.mp3 b/audio/b9-d1-l2.mp3
new file mode 100644
index 0000000..ffab68c
Binary files /dev/null and b/audio/b9-d1-l2.mp3 differ
diff --git a/audio/b9-d1-l3.mp3 b/audio/b9-d1-l3.mp3
new file mode 100644
index 0000000..0c7b93c
Binary files /dev/null and b/audio/b9-d1-l3.mp3 differ
diff --git a/audio/b9-d1-l4.mp3 b/audio/b9-d1-l4.mp3
new file mode 100644
index 0000000..e5ccc65
Binary files /dev/null and b/audio/b9-d1-l4.mp3 differ
diff --git a/audio/b9-d1-l5.mp3 b/audio/b9-d1-l5.mp3
new file mode 100644
index 0000000..e9a06ec
Binary files /dev/null and b/audio/b9-d1-l5.mp3 differ
diff --git a/audio/b9-d1-l6.mp3 b/audio/b9-d1-l6.mp3
new file mode 100644
index 0000000..1df12b6
Binary files /dev/null and b/audio/b9-d1-l6.mp3 differ
diff --git a/audio/b9-d2-l1.mp3 b/audio/b9-d2-l1.mp3
new file mode 100644
index 0000000..20c45ef
Binary files /dev/null and b/audio/b9-d2-l1.mp3 differ
diff --git a/audio/b9-d2-l2.mp3 b/audio/b9-d2-l2.mp3
new file mode 100644
index 0000000..a319ddb
Binary files /dev/null and b/audio/b9-d2-l2.mp3 differ
diff --git a/audio/b9-d2-l3.mp3 b/audio/b9-d2-l3.mp3
new file mode 100644
index 0000000..0e8ad19
Binary files /dev/null and b/audio/b9-d2-l3.mp3 differ
diff --git a/audio/b9-d2-l4.mp3 b/audio/b9-d2-l4.mp3
new file mode 100644
index 0000000..5c418b8
Binary files /dev/null and b/audio/b9-d2-l4.mp3 differ
diff --git a/audio/b9-d2-l5.mp3 b/audio/b9-d2-l5.mp3
new file mode 100644
index 0000000..782d2fa
Binary files /dev/null and b/audio/b9-d2-l5.mp3 differ
diff --git a/audio/b9-d2-l6.mp3 b/audio/b9-d2-l6.mp3
new file mode 100644
index 0000000..7da51db
Binary files /dev/null and b/audio/b9-d2-l6.mp3 differ
diff --git a/audio/v1-chunk-01.mp3 b/audio/v1-chunk-01.mp3
new file mode 100644
index 0000000..b3c8d22
Binary files /dev/null and b/audio/v1-chunk-01.mp3 differ
diff --git a/audio/v1-chunk-02.mp3 b/audio/v1-chunk-02.mp3
new file mode 100644
index 0000000..8bc4d63
Binary files /dev/null and b/audio/v1-chunk-02.mp3 differ
diff --git a/audio/v1-chunk-03.mp3 b/audio/v1-chunk-03.mp3
new file mode 100644
index 0000000..7f6c8f4
Binary files /dev/null and b/audio/v1-chunk-03.mp3 differ
diff --git a/audio/v1-chunk-04.mp3 b/audio/v1-chunk-04.mp3
new file mode 100644
index 0000000..b789907
Binary files /dev/null and b/audio/v1-chunk-04.mp3 differ
diff --git a/audio/v1-chunk-05.mp3 b/audio/v1-chunk-05.mp3
new file mode 100644
index 0000000..cde8ea3
Binary files /dev/null and b/audio/v1-chunk-05.mp3 differ
diff --git a/audio/v1-chunk-06.mp3 b/audio/v1-chunk-06.mp3
new file mode 100644
index 0000000..4f241bd
Binary files /dev/null and b/audio/v1-chunk-06.mp3 differ
diff --git a/audio/v1-chunk-07.mp3 b/audio/v1-chunk-07.mp3
new file mode 100644
index 0000000..03717a6
Binary files /dev/null and b/audio/v1-chunk-07.mp3 differ
diff --git a/audio/v1-chunk-08.mp3 b/audio/v1-chunk-08.mp3
new file mode 100644
index 0000000..50f89ed
Binary files /dev/null and b/audio/v1-chunk-08.mp3 differ
diff --git a/audio/v1-chunk-09.mp3 b/audio/v1-chunk-09.mp3
new file mode 100644
index 0000000..d9ea572
Binary files /dev/null and b/audio/v1-chunk-09.mp3 differ
diff --git a/audio/v1-chunk-10.mp3 b/audio/v1-chunk-10.mp3
new file mode 100644
index 0000000..96ffd6a
Binary files /dev/null and b/audio/v1-chunk-10.mp3 differ
diff --git a/audio/v1-chunk-11.mp3 b/audio/v1-chunk-11.mp3
new file mode 100644
index 0000000..1e36ac9
Binary files /dev/null and b/audio/v1-chunk-11.mp3 differ
diff --git a/audio/v1-chunk-12.mp3 b/audio/v1-chunk-12.mp3
new file mode 100644
index 0000000..a192b17
Binary files /dev/null and b/audio/v1-chunk-12.mp3 differ
diff --git a/audio/v1-chunk-13.mp3 b/audio/v1-chunk-13.mp3
new file mode 100644
index 0000000..e14d979
Binary files /dev/null and b/audio/v1-chunk-13.mp3 differ
diff --git a/audio/v1-d1-l1.mp3 b/audio/v1-d1-l1.mp3
new file mode 100644
index 0000000..2d2e30c
Binary files /dev/null and b/audio/v1-d1-l1.mp3 differ
diff --git a/audio/v1-d1-l10.mp3 b/audio/v1-d1-l10.mp3
new file mode 100644
index 0000000..67f21f5
Binary files /dev/null and b/audio/v1-d1-l10.mp3 differ
diff --git a/audio/v1-d1-l2.mp3 b/audio/v1-d1-l2.mp3
new file mode 100644
index 0000000..2c2521b
Binary files /dev/null and b/audio/v1-d1-l2.mp3 differ
diff --git a/audio/v1-d1-l3.mp3 b/audio/v1-d1-l3.mp3
new file mode 100644
index 0000000..2bd5cdc
Binary files /dev/null and b/audio/v1-d1-l3.mp3 differ
diff --git a/audio/v1-d1-l4.mp3 b/audio/v1-d1-l4.mp3
new file mode 100644
index 0000000..f50f9e4
Binary files /dev/null and b/audio/v1-d1-l4.mp3 differ
diff --git a/audio/v1-d1-l5.mp3 b/audio/v1-d1-l5.mp3
new file mode 100644
index 0000000..90f36dd
Binary files /dev/null and b/audio/v1-d1-l5.mp3 differ
diff --git a/audio/v1-d1-l6.mp3 b/audio/v1-d1-l6.mp3
new file mode 100644
index 0000000..8e3efee
Binary files /dev/null and b/audio/v1-d1-l6.mp3 differ
diff --git a/audio/v1-d1-l7.mp3 b/audio/v1-d1-l7.mp3
new file mode 100644
index 0000000..8337f6e
Binary files /dev/null and b/audio/v1-d1-l7.mp3 differ
diff --git a/audio/v1-d1-l8.mp3 b/audio/v1-d1-l8.mp3
new file mode 100644
index 0000000..5e8acaa
Binary files /dev/null and b/audio/v1-d1-l8.mp3 differ
diff --git a/audio/v1-d1-l9.mp3 b/audio/v1-d1-l9.mp3
new file mode 100644
index 0000000..f5ef6f4
Binary files /dev/null and b/audio/v1-d1-l9.mp3 differ
diff --git a/audio/v1-d2-l1.mp3 b/audio/v1-d2-l1.mp3
new file mode 100644
index 0000000..b5f6d4f
Binary files /dev/null and b/audio/v1-d2-l1.mp3 differ
diff --git a/audio/v1-d2-l2.mp3 b/audio/v1-d2-l2.mp3
new file mode 100644
index 0000000..d646370
Binary files /dev/null and b/audio/v1-d2-l2.mp3 differ
diff --git a/audio/v1-d2-l3.mp3 b/audio/v1-d2-l3.mp3
new file mode 100644
index 0000000..5d08f6e
Binary files /dev/null and b/audio/v1-d2-l3.mp3 differ
diff --git a/audio/v1-d2-l4.mp3 b/audio/v1-d2-l4.mp3
new file mode 100644
index 0000000..6ca93ae
Binary files /dev/null and b/audio/v1-d2-l4.mp3 differ
diff --git a/audio/v1-d2-l5.mp3 b/audio/v1-d2-l5.mp3
new file mode 100644
index 0000000..ff3d660
Binary files /dev/null and b/audio/v1-d2-l5.mp3 differ
diff --git a/audio/v1-d2-l6.mp3 b/audio/v1-d2-l6.mp3
new file mode 100644
index 0000000..bbd5a01
Binary files /dev/null and b/audio/v1-d2-l6.mp3 differ
diff --git a/audio/v1-d2-l7.mp3 b/audio/v1-d2-l7.mp3
new file mode 100644
index 0000000..c189cfb
Binary files /dev/null and b/audio/v1-d2-l7.mp3 differ
diff --git a/audio/v2-chunk-01.mp3 b/audio/v2-chunk-01.mp3
new file mode 100644
index 0000000..adec712
Binary files /dev/null and b/audio/v2-chunk-01.mp3 differ
diff --git a/audio/v2-chunk-02.mp3 b/audio/v2-chunk-02.mp3
new file mode 100644
index 0000000..4f5ce9e
Binary files /dev/null and b/audio/v2-chunk-02.mp3 differ
diff --git a/audio/v2-chunk-03.mp3 b/audio/v2-chunk-03.mp3
new file mode 100644
index 0000000..9243516
Binary files /dev/null and b/audio/v2-chunk-03.mp3 differ
diff --git a/audio/v2-chunk-04.mp3 b/audio/v2-chunk-04.mp3
new file mode 100644
index 0000000..bddc205
Binary files /dev/null and b/audio/v2-chunk-04.mp3 differ
diff --git a/audio/v2-chunk-05.mp3 b/audio/v2-chunk-05.mp3
new file mode 100644
index 0000000..110b8da
Binary files /dev/null and b/audio/v2-chunk-05.mp3 differ
diff --git a/audio/v2-chunk-06.mp3 b/audio/v2-chunk-06.mp3
new file mode 100644
index 0000000..645f8ab
Binary files /dev/null and b/audio/v2-chunk-06.mp3 differ
diff --git a/audio/v2-chunk-07.mp3 b/audio/v2-chunk-07.mp3
new file mode 100644
index 0000000..64f482e
Binary files /dev/null and b/audio/v2-chunk-07.mp3 differ
diff --git a/audio/v2-chunk-08.mp3 b/audio/v2-chunk-08.mp3
new file mode 100644
index 0000000..947c284
Binary files /dev/null and b/audio/v2-chunk-08.mp3 differ
diff --git a/audio/v2-chunk-09.mp3 b/audio/v2-chunk-09.mp3
new file mode 100644
index 0000000..fa19f41
Binary files /dev/null and b/audio/v2-chunk-09.mp3 differ
diff --git a/audio/v2-chunk-10.mp3 b/audio/v2-chunk-10.mp3
new file mode 100644
index 0000000..5ab069f
Binary files /dev/null and b/audio/v2-chunk-10.mp3 differ
diff --git a/audio/v2-chunk-11.mp3 b/audio/v2-chunk-11.mp3
new file mode 100644
index 0000000..c2c5f54
Binary files /dev/null and b/audio/v2-chunk-11.mp3 differ
diff --git a/audio/v2-chunk-12.mp3 b/audio/v2-chunk-12.mp3
new file mode 100644
index 0000000..eb21544
Binary files /dev/null and b/audio/v2-chunk-12.mp3 differ
diff --git a/audio/v2-chunk-13.mp3 b/audio/v2-chunk-13.mp3
new file mode 100644
index 0000000..c10d853
Binary files /dev/null and b/audio/v2-chunk-13.mp3 differ
diff --git a/audio/v2-d1-l1.mp3 b/audio/v2-d1-l1.mp3
new file mode 100644
index 0000000..b3609b7
Binary files /dev/null and b/audio/v2-d1-l1.mp3 differ
diff --git a/audio/v2-d1-l2.mp3 b/audio/v2-d1-l2.mp3
new file mode 100644
index 0000000..800483d
Binary files /dev/null and b/audio/v2-d1-l2.mp3 differ
diff --git a/audio/v2-d1-l3.mp3 b/audio/v2-d1-l3.mp3
new file mode 100644
index 0000000..8caf2ce
Binary files /dev/null and b/audio/v2-d1-l3.mp3 differ
diff --git a/audio/v2-d1-l4.mp3 b/audio/v2-d1-l4.mp3
new file mode 100644
index 0000000..e81b2f3
Binary files /dev/null and b/audio/v2-d1-l4.mp3 differ
diff --git a/audio/v2-d1-l5.mp3 b/audio/v2-d1-l5.mp3
new file mode 100644
index 0000000..37a8fb2
Binary files /dev/null and b/audio/v2-d1-l5.mp3 differ
diff --git a/audio/v2-d1-l6.mp3 b/audio/v2-d1-l6.mp3
new file mode 100644
index 0000000..9824964
Binary files /dev/null and b/audio/v2-d1-l6.mp3 differ
diff --git a/audio/v2-d1-l7.mp3 b/audio/v2-d1-l7.mp3
new file mode 100644
index 0000000..592344f
Binary files /dev/null and b/audio/v2-d1-l7.mp3 differ
diff --git a/audio/v2-d2-l1.mp3 b/audio/v2-d2-l1.mp3
new file mode 100644
index 0000000..070c6a4
Binary files /dev/null and b/audio/v2-d2-l1.mp3 differ
diff --git a/audio/v2-d2-l2.mp3 b/audio/v2-d2-l2.mp3
new file mode 100644
index 0000000..119e951
Binary files /dev/null and b/audio/v2-d2-l2.mp3 differ
diff --git a/audio/v2-d2-l3.mp3 b/audio/v2-d2-l3.mp3
new file mode 100644
index 0000000..1d7c100
Binary files /dev/null and b/audio/v2-d2-l3.mp3 differ
diff --git a/audio/v2-d2-l4.mp3 b/audio/v2-d2-l4.mp3
new file mode 100644
index 0000000..5d834f0
Binary files /dev/null and b/audio/v2-d2-l4.mp3 differ
diff --git a/audio/v2-d2-l5.mp3 b/audio/v2-d2-l5.mp3
new file mode 100644
index 0000000..30e52ee
Binary files /dev/null and b/audio/v2-d2-l5.mp3 differ
diff --git a/audio/v2-d2-l6.mp3 b/audio/v2-d2-l6.mp3
new file mode 100644
index 0000000..0e5776c
Binary files /dev/null and b/audio/v2-d2-l6.mp3 differ
diff --git a/audio/v2-d2-l7.mp3 b/audio/v2-d2-l7.mp3
new file mode 100644
index 0000000..722fa53
Binary files /dev/null and b/audio/v2-d2-l7.mp3 differ
diff --git a/audio/v3-chunk-01.mp3 b/audio/v3-chunk-01.mp3
new file mode 100644
index 0000000..95b77b5
Binary files /dev/null and b/audio/v3-chunk-01.mp3 differ
diff --git a/audio/v3-chunk-02.mp3 b/audio/v3-chunk-02.mp3
new file mode 100644
index 0000000..1f656c0
Binary files /dev/null and b/audio/v3-chunk-02.mp3 differ
diff --git a/audio/v3-chunk-03.mp3 b/audio/v3-chunk-03.mp3
new file mode 100644
index 0000000..ceda5a8
Binary files /dev/null and b/audio/v3-chunk-03.mp3 differ
diff --git a/audio/v3-chunk-04.mp3 b/audio/v3-chunk-04.mp3
new file mode 100644
index 0000000..ef60ff7
Binary files /dev/null and b/audio/v3-chunk-04.mp3 differ
diff --git a/audio/v3-chunk-05.mp3 b/audio/v3-chunk-05.mp3
new file mode 100644
index 0000000..98f9d2d
Binary files /dev/null and b/audio/v3-chunk-05.mp3 differ
diff --git a/audio/v3-chunk-06.mp3 b/audio/v3-chunk-06.mp3
new file mode 100644
index 0000000..e7333ff
Binary files /dev/null and b/audio/v3-chunk-06.mp3 differ
diff --git a/audio/v3-chunk-07.mp3 b/audio/v3-chunk-07.mp3
new file mode 100644
index 0000000..3bbcd5a
Binary files /dev/null and b/audio/v3-chunk-07.mp3 differ
diff --git a/audio/v3-chunk-08.mp3 b/audio/v3-chunk-08.mp3
new file mode 100644
index 0000000..87e7a25
Binary files /dev/null and b/audio/v3-chunk-08.mp3 differ
diff --git a/audio/v3-chunk-09.mp3 b/audio/v3-chunk-09.mp3
new file mode 100644
index 0000000..b6f9da1
Binary files /dev/null and b/audio/v3-chunk-09.mp3 differ
diff --git a/audio/v3-chunk-10.mp3 b/audio/v3-chunk-10.mp3
new file mode 100644
index 0000000..664513f
Binary files /dev/null and b/audio/v3-chunk-10.mp3 differ
diff --git a/audio/v3-chunk-11.mp3 b/audio/v3-chunk-11.mp3
new file mode 100644
index 0000000..8ac5105
Binary files /dev/null and b/audio/v3-chunk-11.mp3 differ
diff --git a/audio/v3-chunk-12.mp3 b/audio/v3-chunk-12.mp3
new file mode 100644
index 0000000..f3e8c3c
Binary files /dev/null and b/audio/v3-chunk-12.mp3 differ
diff --git a/audio/v3-chunk-13.mp3 b/audio/v3-chunk-13.mp3
new file mode 100644
index 0000000..ea39da0
Binary files /dev/null and b/audio/v3-chunk-13.mp3 differ
diff --git a/audio/v3-d1-l1.mp3 b/audio/v3-d1-l1.mp3
new file mode 100644
index 0000000..e4b4140
Binary files /dev/null and b/audio/v3-d1-l1.mp3 differ
diff --git a/audio/v3-d1-l2.mp3 b/audio/v3-d1-l2.mp3
new file mode 100644
index 0000000..903e963
Binary files /dev/null and b/audio/v3-d1-l2.mp3 differ
diff --git a/audio/v3-d1-l3.mp3 b/audio/v3-d1-l3.mp3
new file mode 100644
index 0000000..444a844
Binary files /dev/null and b/audio/v3-d1-l3.mp3 differ
diff --git a/audio/v3-d1-l4.mp3 b/audio/v3-d1-l4.mp3
new file mode 100644
index 0000000..6af7fbf
Binary files /dev/null and b/audio/v3-d1-l4.mp3 differ
diff --git a/audio/v3-d1-l5.mp3 b/audio/v3-d1-l5.mp3
new file mode 100644
index 0000000..82b89f8
Binary files /dev/null and b/audio/v3-d1-l5.mp3 differ
diff --git a/audio/v3-d1-l6.mp3 b/audio/v3-d1-l6.mp3
new file mode 100644
index 0000000..17b2c69
Binary files /dev/null and b/audio/v3-d1-l6.mp3 differ
diff --git a/audio/v3-d1-l7.mp3 b/audio/v3-d1-l7.mp3
new file mode 100644
index 0000000..4ce23d5
Binary files /dev/null and b/audio/v3-d1-l7.mp3 differ
diff --git a/audio/v3-d1-l8.mp3 b/audio/v3-d1-l8.mp3
new file mode 100644
index 0000000..9ae5699
Binary files /dev/null and b/audio/v3-d1-l8.mp3 differ
diff --git a/audio/v3-d2-l1.mp3 b/audio/v3-d2-l1.mp3
new file mode 100644
index 0000000..a594700
Binary files /dev/null and b/audio/v3-d2-l1.mp3 differ
diff --git a/audio/v3-d2-l2.mp3 b/audio/v3-d2-l2.mp3
new file mode 100644
index 0000000..e4709db
Binary files /dev/null and b/audio/v3-d2-l2.mp3 differ
diff --git a/audio/v3-d2-l3.mp3 b/audio/v3-d2-l3.mp3
new file mode 100644
index 0000000..1e81aac
Binary files /dev/null and b/audio/v3-d2-l3.mp3 differ
diff --git a/audio/v3-d2-l4.mp3 b/audio/v3-d2-l4.mp3
new file mode 100644
index 0000000..32a8c70
Binary files /dev/null and b/audio/v3-d2-l4.mp3 differ
diff --git a/audio/v3-d2-l5.mp3 b/audio/v3-d2-l5.mp3
new file mode 100644
index 0000000..0268a8b
Binary files /dev/null and b/audio/v3-d2-l5.mp3 differ
diff --git a/audio/v3-d2-l6.mp3 b/audio/v3-d2-l6.mp3
new file mode 100644
index 0000000..f51dddd
Binary files /dev/null and b/audio/v3-d2-l6.mp3 differ
diff --git a/audio/v3-d2-l7.mp3 b/audio/v3-d2-l7.mp3
new file mode 100644
index 0000000..fe25a59
Binary files /dev/null and b/audio/v3-d2-l7.mp3 differ
diff --git a/audio/v4-chunk-01.mp3 b/audio/v4-chunk-01.mp3
new file mode 100644
index 0000000..3bd53a5
Binary files /dev/null and b/audio/v4-chunk-01.mp3 differ
diff --git a/audio/v4-chunk-02.mp3 b/audio/v4-chunk-02.mp3
new file mode 100644
index 0000000..b547b20
Binary files /dev/null and b/audio/v4-chunk-02.mp3 differ
diff --git a/audio/v4-chunk-03.mp3 b/audio/v4-chunk-03.mp3
new file mode 100644
index 0000000..b81597b
Binary files /dev/null and b/audio/v4-chunk-03.mp3 differ
diff --git a/audio/v4-chunk-04.mp3 b/audio/v4-chunk-04.mp3
new file mode 100644
index 0000000..a83d4b7
Binary files /dev/null and b/audio/v4-chunk-04.mp3 differ
diff --git a/audio/v4-chunk-05.mp3 b/audio/v4-chunk-05.mp3
new file mode 100644
index 0000000..35b7a0d
Binary files /dev/null and b/audio/v4-chunk-05.mp3 differ
diff --git a/audio/v4-chunk-06.mp3 b/audio/v4-chunk-06.mp3
new file mode 100644
index 0000000..ec980fb
Binary files /dev/null and b/audio/v4-chunk-06.mp3 differ
diff --git a/audio/v4-chunk-07.mp3 b/audio/v4-chunk-07.mp3
new file mode 100644
index 0000000..cb399c9
Binary files /dev/null and b/audio/v4-chunk-07.mp3 differ
diff --git a/audio/v4-chunk-08.mp3 b/audio/v4-chunk-08.mp3
new file mode 100644
index 0000000..41f5e57
Binary files /dev/null and b/audio/v4-chunk-08.mp3 differ
diff --git a/audio/v4-chunk-09.mp3 b/audio/v4-chunk-09.mp3
new file mode 100644
index 0000000..fc0cf67
Binary files /dev/null and b/audio/v4-chunk-09.mp3 differ
diff --git a/audio/v4-chunk-10.mp3 b/audio/v4-chunk-10.mp3
new file mode 100644
index 0000000..b4d50f1
Binary files /dev/null and b/audio/v4-chunk-10.mp3 differ
diff --git a/audio/v4-chunk-11.mp3 b/audio/v4-chunk-11.mp3
new file mode 100644
index 0000000..7211dfe
Binary files /dev/null and b/audio/v4-chunk-11.mp3 differ
diff --git a/audio/v4-chunk-12.mp3 b/audio/v4-chunk-12.mp3
new file mode 100644
index 0000000..d75c901
Binary files /dev/null and b/audio/v4-chunk-12.mp3 differ
diff --git a/audio/v4-chunk-13.mp3 b/audio/v4-chunk-13.mp3
new file mode 100644
index 0000000..7f4e1df
Binary files /dev/null and b/audio/v4-chunk-13.mp3 differ
diff --git a/audio/v4-chunk-14.mp3 b/audio/v4-chunk-14.mp3
new file mode 100644
index 0000000..d248a24
Binary files /dev/null and b/audio/v4-chunk-14.mp3 differ
diff --git a/audio/v4-d1-l1.mp3 b/audio/v4-d1-l1.mp3
new file mode 100644
index 0000000..eef4707
Binary files /dev/null and b/audio/v4-d1-l1.mp3 differ
diff --git a/audio/v4-d1-l2.mp3 b/audio/v4-d1-l2.mp3
new file mode 100644
index 0000000..77791e9
Binary files /dev/null and b/audio/v4-d1-l2.mp3 differ
diff --git a/audio/v4-d1-l3.mp3 b/audio/v4-d1-l3.mp3
new file mode 100644
index 0000000..d51d518
Binary files /dev/null and b/audio/v4-d1-l3.mp3 differ
diff --git a/audio/v4-d1-l4.mp3 b/audio/v4-d1-l4.mp3
new file mode 100644
index 0000000..aad1745
Binary files /dev/null and b/audio/v4-d1-l4.mp3 differ
diff --git a/audio/v4-d1-l5.mp3 b/audio/v4-d1-l5.mp3
new file mode 100644
index 0000000..a359e9b
Binary files /dev/null and b/audio/v4-d1-l5.mp3 differ
diff --git a/audio/v4-d1-l6.mp3 b/audio/v4-d1-l6.mp3
new file mode 100644
index 0000000..79aed9c
Binary files /dev/null and b/audio/v4-d1-l6.mp3 differ
diff --git a/audio/v4-d1-l7.mp3 b/audio/v4-d1-l7.mp3
new file mode 100644
index 0000000..79c081c
Binary files /dev/null and b/audio/v4-d1-l7.mp3 differ
diff --git a/audio/v4-d1-l8.mp3 b/audio/v4-d1-l8.mp3
new file mode 100644
index 0000000..61d3204
Binary files /dev/null and b/audio/v4-d1-l8.mp3 differ
diff --git a/audio/v4-d1-l9.mp3 b/audio/v4-d1-l9.mp3
new file mode 100644
index 0000000..b8f26ea
Binary files /dev/null and b/audio/v4-d1-l9.mp3 differ
diff --git a/audio/v4-d2-l1.mp3 b/audio/v4-d2-l1.mp3
new file mode 100644
index 0000000..8f735f3
Binary files /dev/null and b/audio/v4-d2-l1.mp3 differ
diff --git a/audio/v4-d2-l2.mp3 b/audio/v4-d2-l2.mp3
new file mode 100644
index 0000000..1ea76d9
Binary files /dev/null and b/audio/v4-d2-l2.mp3 differ
diff --git a/audio/v4-d2-l3.mp3 b/audio/v4-d2-l3.mp3
new file mode 100644
index 0000000..4c8a660
Binary files /dev/null and b/audio/v4-d2-l3.mp3 differ
diff --git a/audio/v4-d2-l4.mp3 b/audio/v4-d2-l4.mp3
new file mode 100644
index 0000000..01125c2
Binary files /dev/null and b/audio/v4-d2-l4.mp3 differ
diff --git a/audio/v4-d2-l5.mp3 b/audio/v4-d2-l5.mp3
new file mode 100644
index 0000000..ce4ea74
Binary files /dev/null and b/audio/v4-d2-l5.mp3 differ
diff --git a/audio/v4-d2-l6.mp3 b/audio/v4-d2-l6.mp3
new file mode 100644
index 0000000..a03eebb
Binary files /dev/null and b/audio/v4-d2-l6.mp3 differ
diff --git a/audio/v4-d2-l7.mp3 b/audio/v4-d2-l7.mp3
new file mode 100644
index 0000000..1c97569
Binary files /dev/null and b/audio/v4-d2-l7.mp3 differ
diff --git a/audio/v5-chunk-01.mp3 b/audio/v5-chunk-01.mp3
new file mode 100644
index 0000000..a9ccfb9
Binary files /dev/null and b/audio/v5-chunk-01.mp3 differ
diff --git a/audio/v5-chunk-02.mp3 b/audio/v5-chunk-02.mp3
new file mode 100644
index 0000000..df7fa57
Binary files /dev/null and b/audio/v5-chunk-02.mp3 differ
diff --git a/audio/v5-chunk-03.mp3 b/audio/v5-chunk-03.mp3
new file mode 100644
index 0000000..d4c43c2
Binary files /dev/null and b/audio/v5-chunk-03.mp3 differ
diff --git a/audio/v5-chunk-04.mp3 b/audio/v5-chunk-04.mp3
new file mode 100644
index 0000000..fc672c6
Binary files /dev/null and b/audio/v5-chunk-04.mp3 differ
diff --git a/audio/v5-chunk-05.mp3 b/audio/v5-chunk-05.mp3
new file mode 100644
index 0000000..a039e41
Binary files /dev/null and b/audio/v5-chunk-05.mp3 differ
diff --git a/audio/v5-chunk-06.mp3 b/audio/v5-chunk-06.mp3
new file mode 100644
index 0000000..f3f310b
Binary files /dev/null and b/audio/v5-chunk-06.mp3 differ
diff --git a/audio/v5-chunk-07.mp3 b/audio/v5-chunk-07.mp3
new file mode 100644
index 0000000..736b35d
Binary files /dev/null and b/audio/v5-chunk-07.mp3 differ
diff --git a/audio/v5-chunk-08.mp3 b/audio/v5-chunk-08.mp3
new file mode 100644
index 0000000..05bd255
Binary files /dev/null and b/audio/v5-chunk-08.mp3 differ
diff --git a/audio/v5-chunk-09.mp3 b/audio/v5-chunk-09.mp3
new file mode 100644
index 0000000..ade13d0
Binary files /dev/null and b/audio/v5-chunk-09.mp3 differ
diff --git a/audio/v5-chunk-10.mp3 b/audio/v5-chunk-10.mp3
new file mode 100644
index 0000000..de7badb
Binary files /dev/null and b/audio/v5-chunk-10.mp3 differ
diff --git a/audio/v5-chunk-11.mp3 b/audio/v5-chunk-11.mp3
new file mode 100644
index 0000000..9fe2fa0
Binary files /dev/null and b/audio/v5-chunk-11.mp3 differ
diff --git a/audio/v5-chunk-12.mp3 b/audio/v5-chunk-12.mp3
new file mode 100644
index 0000000..7bcd506
Binary files /dev/null and b/audio/v5-chunk-12.mp3 differ
diff --git a/audio/v5-chunk-13.mp3 b/audio/v5-chunk-13.mp3
new file mode 100644
index 0000000..442842d
Binary files /dev/null and b/audio/v5-chunk-13.mp3 differ
diff --git a/audio/v5-d1-l1.mp3 b/audio/v5-d1-l1.mp3
new file mode 100644
index 0000000..6b78a04
Binary files /dev/null and b/audio/v5-d1-l1.mp3 differ
diff --git a/audio/v5-d1-l2.mp3 b/audio/v5-d1-l2.mp3
new file mode 100644
index 0000000..9e58b47
Binary files /dev/null and b/audio/v5-d1-l2.mp3 differ
diff --git a/audio/v5-d1-l3.mp3 b/audio/v5-d1-l3.mp3
new file mode 100644
index 0000000..b8567b1
Binary files /dev/null and b/audio/v5-d1-l3.mp3 differ
diff --git a/audio/v5-d1-l4.mp3 b/audio/v5-d1-l4.mp3
new file mode 100644
index 0000000..117a4e8
Binary files /dev/null and b/audio/v5-d1-l4.mp3 differ
diff --git a/audio/v5-d1-l5.mp3 b/audio/v5-d1-l5.mp3
new file mode 100644
index 0000000..3915e88
Binary files /dev/null and b/audio/v5-d1-l5.mp3 differ
diff --git a/audio/v5-d1-l6.mp3 b/audio/v5-d1-l6.mp3
new file mode 100644
index 0000000..b645734
Binary files /dev/null and b/audio/v5-d1-l6.mp3 differ
diff --git a/audio/v5-d1-l7.mp3 b/audio/v5-d1-l7.mp3
new file mode 100644
index 0000000..7b0c52b
Binary files /dev/null and b/audio/v5-d1-l7.mp3 differ
diff --git a/audio/v5-d2-l1.mp3 b/audio/v5-d2-l1.mp3
new file mode 100644
index 0000000..04d1c3a
Binary files /dev/null and b/audio/v5-d2-l1.mp3 differ
diff --git a/audio/v5-d2-l2.mp3 b/audio/v5-d2-l2.mp3
new file mode 100644
index 0000000..891ab18
Binary files /dev/null and b/audio/v5-d2-l2.mp3 differ
diff --git a/audio/v5-d2-l3.mp3 b/audio/v5-d2-l3.mp3
new file mode 100644
index 0000000..8a5f199
Binary files /dev/null and b/audio/v5-d2-l3.mp3 differ
diff --git a/audio/v5-d2-l4.mp3 b/audio/v5-d2-l4.mp3
new file mode 100644
index 0000000..5671632
Binary files /dev/null and b/audio/v5-d2-l4.mp3 differ
diff --git a/audio/v5-d2-l5.mp3 b/audio/v5-d2-l5.mp3
new file mode 100644
index 0000000..5a15a21
Binary files /dev/null and b/audio/v5-d2-l5.mp3 differ
diff --git a/audio/v5-d2-l6.mp3 b/audio/v5-d2-l6.mp3
new file mode 100644
index 0000000..a10be35
Binary files /dev/null and b/audio/v5-d2-l6.mp3 differ
diff --git a/audio/v5-d2-l7.mp3 b/audio/v5-d2-l7.mp3
new file mode 100644
index 0000000..25e837d
Binary files /dev/null and b/audio/v5-d2-l7.mp3 differ
diff --git a/audio/v5-d2-l8.mp3 b/audio/v5-d2-l8.mp3
new file mode 100644
index 0000000..0018736
Binary files /dev/null and b/audio/v5-d2-l8.mp3 differ
diff --git a/audio/v5-d2-l9.mp3 b/audio/v5-d2-l9.mp3
new file mode 100644
index 0000000..9adae60
Binary files /dev/null and b/audio/v5-d2-l9.mp3 differ
diff --git a/audio/v6-chunk-01.mp3 b/audio/v6-chunk-01.mp3
new file mode 100644
index 0000000..fef5586
Binary files /dev/null and b/audio/v6-chunk-01.mp3 differ
diff --git a/audio/v6-chunk-02.mp3 b/audio/v6-chunk-02.mp3
new file mode 100644
index 0000000..df977c5
Binary files /dev/null and b/audio/v6-chunk-02.mp3 differ
diff --git a/audio/v6-chunk-03.mp3 b/audio/v6-chunk-03.mp3
new file mode 100644
index 0000000..7d0ce9d
Binary files /dev/null and b/audio/v6-chunk-03.mp3 differ
diff --git a/audio/v6-chunk-04.mp3 b/audio/v6-chunk-04.mp3
new file mode 100644
index 0000000..4ccf6fe
Binary files /dev/null and b/audio/v6-chunk-04.mp3 differ
diff --git a/audio/v6-chunk-05.mp3 b/audio/v6-chunk-05.mp3
new file mode 100644
index 0000000..7bcbb05
Binary files /dev/null and b/audio/v6-chunk-05.mp3 differ
diff --git a/audio/v6-chunk-06.mp3 b/audio/v6-chunk-06.mp3
new file mode 100644
index 0000000..d0322f9
Binary files /dev/null and b/audio/v6-chunk-06.mp3 differ
diff --git a/audio/v6-chunk-07.mp3 b/audio/v6-chunk-07.mp3
new file mode 100644
index 0000000..44b805e
Binary files /dev/null and b/audio/v6-chunk-07.mp3 differ
diff --git a/audio/v6-chunk-08.mp3 b/audio/v6-chunk-08.mp3
new file mode 100644
index 0000000..b09f798
Binary files /dev/null and b/audio/v6-chunk-08.mp3 differ
diff --git a/audio/v6-chunk-09.mp3 b/audio/v6-chunk-09.mp3
new file mode 100644
index 0000000..9f50016
Binary files /dev/null and b/audio/v6-chunk-09.mp3 differ
diff --git a/audio/v6-chunk-10.mp3 b/audio/v6-chunk-10.mp3
new file mode 100644
index 0000000..f06aeeb
Binary files /dev/null and b/audio/v6-chunk-10.mp3 differ
diff --git a/audio/v6-chunk-11.mp3 b/audio/v6-chunk-11.mp3
new file mode 100644
index 0000000..b0e1e96
Binary files /dev/null and b/audio/v6-chunk-11.mp3 differ
diff --git a/audio/v6-chunk-12.mp3 b/audio/v6-chunk-12.mp3
new file mode 100644
index 0000000..027d6aa
Binary files /dev/null and b/audio/v6-chunk-12.mp3 differ
diff --git a/audio/v6-chunk-13.mp3 b/audio/v6-chunk-13.mp3
new file mode 100644
index 0000000..b1dbcf5
Binary files /dev/null and b/audio/v6-chunk-13.mp3 differ
diff --git a/audio/v6-chunk-14.mp3 b/audio/v6-chunk-14.mp3
new file mode 100644
index 0000000..1affe7f
Binary files /dev/null and b/audio/v6-chunk-14.mp3 differ
diff --git a/audio/v6-chunk-15.mp3 b/audio/v6-chunk-15.mp3
new file mode 100644
index 0000000..34f24d0
Binary files /dev/null and b/audio/v6-chunk-15.mp3 differ
diff --git a/audio/v6-d1-l1.mp3 b/audio/v6-d1-l1.mp3
new file mode 100644
index 0000000..543e69e
Binary files /dev/null and b/audio/v6-d1-l1.mp3 differ
diff --git a/audio/v6-d1-l10.mp3 b/audio/v6-d1-l10.mp3
new file mode 100644
index 0000000..2c70cbe
Binary files /dev/null and b/audio/v6-d1-l10.mp3 differ
diff --git a/audio/v6-d1-l2.mp3 b/audio/v6-d1-l2.mp3
new file mode 100644
index 0000000..5586439
Binary files /dev/null and b/audio/v6-d1-l2.mp3 differ
diff --git a/audio/v6-d1-l3.mp3 b/audio/v6-d1-l3.mp3
new file mode 100644
index 0000000..e03cb9c
Binary files /dev/null and b/audio/v6-d1-l3.mp3 differ
diff --git a/audio/v6-d1-l4.mp3 b/audio/v6-d1-l4.mp3
new file mode 100644
index 0000000..38f8ca0
Binary files /dev/null and b/audio/v6-d1-l4.mp3 differ
diff --git a/audio/v6-d1-l5.mp3 b/audio/v6-d1-l5.mp3
new file mode 100644
index 0000000..c0ac6d0
Binary files /dev/null and b/audio/v6-d1-l5.mp3 differ
diff --git a/audio/v6-d1-l6.mp3 b/audio/v6-d1-l6.mp3
new file mode 100644
index 0000000..337be34
Binary files /dev/null and b/audio/v6-d1-l6.mp3 differ
diff --git a/audio/v6-d1-l7.mp3 b/audio/v6-d1-l7.mp3
new file mode 100644
index 0000000..fd23756
Binary files /dev/null and b/audio/v6-d1-l7.mp3 differ
diff --git a/audio/v6-d1-l8.mp3 b/audio/v6-d1-l8.mp3
new file mode 100644
index 0000000..0d6bcd1
Binary files /dev/null and b/audio/v6-d1-l8.mp3 differ
diff --git a/audio/v6-d1-l9.mp3 b/audio/v6-d1-l9.mp3
new file mode 100644
index 0000000..50ea9d2
Binary files /dev/null and b/audio/v6-d1-l9.mp3 differ
diff --git a/audio/v6-d2-l1.mp3 b/audio/v6-d2-l1.mp3
new file mode 100644
index 0000000..55727a5
Binary files /dev/null and b/audio/v6-d2-l1.mp3 differ
diff --git a/audio/v6-d2-l2.mp3 b/audio/v6-d2-l2.mp3
new file mode 100644
index 0000000..622b21c
Binary files /dev/null and b/audio/v6-d2-l2.mp3 differ
diff --git a/audio/v6-d2-l3.mp3 b/audio/v6-d2-l3.mp3
new file mode 100644
index 0000000..fb56eba
Binary files /dev/null and b/audio/v6-d2-l3.mp3 differ
diff --git a/audio/v6-d2-l4.mp3 b/audio/v6-d2-l4.mp3
new file mode 100644
index 0000000..d5f56a4
Binary files /dev/null and b/audio/v6-d2-l4.mp3 differ
diff --git a/audio/v6-d2-l5.mp3 b/audio/v6-d2-l5.mp3
new file mode 100644
index 0000000..d2cca41
Binary files /dev/null and b/audio/v6-d2-l5.mp3 differ
diff --git a/audio/v6-d2-l6.mp3 b/audio/v6-d2-l6.mp3
new file mode 100644
index 0000000..18492c3
Binary files /dev/null and b/audio/v6-d2-l6.mp3 differ
diff --git a/audio/v6-d2-l7.mp3 b/audio/v6-d2-l7.mp3
new file mode 100644
index 0000000..c9f6095
Binary files /dev/null and b/audio/v6-d2-l7.mp3 differ
diff --git a/content/.gitkeep b/content/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/content/build-index.mjs b/content/build-index.mjs
new file mode 100644
index 0000000..b0aa30c
--- /dev/null
+++ b/content/build-index.mjs
@@ -0,0 +1,87 @@
+#!/usr/bin/env node
+/**
+ * Build content/index.json from individual mission files.
+ * Usage: node content/build-index.mjs
+ */
+
+import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { join, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const missionsDir = join(__dirname, 'missions');
+const outputPath = join(__dirname, 'index.json');
+
+// Read all mission JSON files
+const files = readdirSync(missionsDir)
+ .filter(f => f.endsWith('.json'))
+ .sort(); // B1..B12, V1..V6 (lexicographic works for our IDs)
+
+console.log(`Found ${files.length} mission files.`);
+
+const missions = [];
+for (const file of files) {
+ const filePath = join(missionsDir, file);
+ const raw = readFileSync(filePath, 'utf-8');
+ const mission = JSON.parse(raw);
+ missions.push(mission);
+ console.log(` ✓ ${mission.id}: ${mission.titleIt} (${mission.chunks.length} chunks, ${mission.dialogues.length} dialogues)`);
+}
+
+// Validate basic structure
+for (const m of missions) {
+ if (!m.id || !m.trail || !m.cefr || !m.titleIt || !m.titlePt) {
+ throw new Error(`Mission ${m.id || '?'} missing required fields`);
+ }
+ if (!Array.isArray(m.canDos) || m.canDos.length < 2) {
+ throw new Error(`Mission ${m.id}: needs >=2 canDos`);
+ }
+ if (!Array.isArray(m.chunks) || m.chunks.length < 12) {
+ throw new Error(`Mission ${m.id}: needs >=12 chunks, got ${m.chunks?.length}`);
+ }
+ if (!Array.isArray(m.dialogues) || m.dialogues.length !== 2) {
+ throw new Error(`Mission ${m.id}: needs exactly 2 dialogues`);
+ }
+ for (let i = 0; i < m.dialogues.length; i++) {
+ const d = m.dialogues[i];
+ if (d.length < 6 || d.length > 10) {
+ throw new Error(`Mission ${m.id} dialogue ${i+1}: needs 6-10 lines, got ${d.length}`);
+ }
+ }
+ if (!m.roleplay || !m.roleplay.settingIt || !m.roleplay.openerIt) {
+ throw new Error(`Mission ${m.id}: incomplete roleplay`);
+ }
+ if (!Array.isArray(m.survivalChecklist) || m.survivalChecklist.length < 3) {
+ throw new Error(`Mission ${m.id}: needs >=3 survivalChecklist items`);
+ }
+ // Check each chunk has audioKey
+ for (const c of m.chunks) {
+ if (!c.it || !c.pt || !c.audioKey) {
+ throw new Error(`Mission ${m.id}: chunk missing it/pt/audioKey`);
+ }
+ }
+ // Check each dialogue line has audioKey
+ for (const d of m.dialogues) {
+ for (const line of d) {
+ if (!line.speaker || !line.it || !line.pt || !line.audioKey) {
+ throw new Error(`Mission ${m.id}: dialogue line missing fields`);
+ }
+ }
+ }
+}
+
+const bundle = {
+ schemaVersion: 1,
+ missions
+};
+
+writeFileSync(outputPath, JSON.stringify(bundle, null, 2), 'utf-8');
+console.log(`\n✅ Wrote ${outputPath} with ${missions.length} missions.`);
+
+// Summary stats
+const totalChunks = missions.reduce((s, m) => s + m.chunks.length, 0);
+const totalDialogues = missions.reduce((s, m) => s + m.dialogues.length, 0);
+const totalLines = missions.reduce((s, m) => s + m.dialogues.reduce((a, d) => a + d.length, 0), 0);
+console.log(` Total chunks: ${totalChunks}`);
+console.log(` Total dialogues: ${totalDialogues}`);
+console.log(` Total dialogue lines: ${totalLines}`);
diff --git a/content/index.json b/content/index.json
new file mode 100644
index 0000000..9b53b6e
--- /dev/null
+++ b/content/index.json
@@ -0,0 +1,3558 @@
+{
+ "schemaVersion": 1,
+ "missions": [
+ {
+ "id": "B1",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Voglio / Non voglio",
+ "titlePt": "Quero / Não quero",
+ "canDos": [
+ {
+ "id": "b1-cd1",
+ "text": "Consigo expressar desejo básico com 'voglio' e apontar para objetos"
+ },
+ {
+ "id": "b1-cd2",
+ "text": "Consigo recusar algo educadamente dizendo 'non voglio'"
+ },
+ {
+ "id": "b1-cd3",
+ "text": "Consigo combinar 'voglio questo/quello' com gestos para me fazer entender"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Voglio questo.",
+ "pt": "Quero isto.",
+ "audioKey": "b1-chunk-01"
+ },
+ {
+ "it": "Voglio quello.",
+ "pt": "Quero aquilo.",
+ "audioKey": "b1-chunk-02"
+ },
+ {
+ "it": "Non voglio questo.",
+ "pt": "Não quero isto.",
+ "audioKey": "b1-chunk-03"
+ },
+ {
+ "it": "Non voglio, grazie.",
+ "pt": "Não quero, obrigado.",
+ "note": "Sempre acrescente 'grazie' ao recusar",
+ "audioKey": "b1-chunk-04"
+ },
+ {
+ "it": "Voglio un gelato.",
+ "pt": "Quero um sorvete.",
+ "audioKey": "b1-chunk-05"
+ },
+ {
+ "it": "Voglio l'acqua.",
+ "pt": "Quero a água.",
+ "note": "Artigo definido quando o objeto é específico",
+ "audioKey": "b1-chunk-06"
+ },
+ {
+ "it": "Non lo voglio.",
+ "pt": "Não o quero.",
+ "note": "'lo' substitui o objeto masculino já mencionado",
+ "audioKey": "b1-chunk-07"
+ },
+ {
+ "it": "Sì, lo voglio!",
+ "pt": "Sim, eu quero!",
+ "audioKey": "b1-chunk-08"
+ },
+ {
+ "it": "No, non voglio.",
+ "pt": "Não, não quero.",
+ "audioKey": "b1-chunk-09"
+ },
+ {
+ "it": "Scusi, come si dice 'quero' in italiano?",
+ "pt": "Desculpe, como se diz 'quero' em italiano?",
+ "note": "SALVAVITA: peça a palavra que falta",
+ "audioKey": "b1-chunk-10"
+ },
+ {
+ "it": "Vorrei questo, per favore.",
+ "pt": "Gostaria disto, por favor.",
+ "note": "'Vorrei' é mais cortês que 'voglio'",
+ "audioKey": "b1-chunk-11"
+ },
+ {
+ "it": "Voglio anche quello.",
+ "pt": "Quero também aquilo.",
+ "audioKey": "b1-chunk-12"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "italian",
+ "it": "Vuoi un biscotto?",
+ "pt": "Quer um biscoito?",
+ "audioKey": "b1-d1-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, voglio un biscotto!",
+ "pt": "Sim, quero um biscoito!",
+ "audioKey": "b1-d1-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "E vuoi anche il latte?",
+ "pt": "E quer também o leite?",
+ "audioKey": "b1-d1-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, non voglio il latte. Voglio l'acqua.",
+ "pt": "Não, não quero leite. Quero água.",
+ "audioKey": "b1-d1-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Va bene! Ecco l'acqua.",
+ "pt": "Tudo bem! Aqui está a água.",
+ "audioKey": "b1-d1-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie!",
+ "pt": "Obrigado!",
+ "audioKey": "b1-d1-l6"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Cosa vuoi? Questo o quello?",
+ "pt": "O que você quer? Isto ou aquilo?",
+ "audioKey": "b1-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Voglio quello, per favore.",
+ "pt": "Quero aquilo, por favor.",
+ "audioKey": "b1-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Sei sicuro? Questo è più buono.",
+ "pt": "Tem certeza? Este é mais gostoso.",
+ "audioKey": "b1-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, non voglio questo. Voglio quello!",
+ "pt": "Não, não quero este. Quero aquele!",
+ "audioKey": "b1-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Dai, va bene! Prendi quello.",
+ "pt": "Vai, tudo bem! Pegue aquele.",
+ "audioKey": "b1-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie mille!",
+ "pt": "Muito obrigado!",
+ "audioKey": "b1-d2-l6"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In cucina con la mamma",
+ "italianRole": "Mamma che offre snack al bambino",
+ "goalPt": "Expressar o que quer e o que não quer entre duas opções de lanche",
+ "openerIt": "Tesoro, cosa vuoi mangiare?"
+ },
+ "survivalChecklist": [
+ "Voglio questo / quello",
+ "Non voglio, grazie",
+ "Sì, lo voglio",
+ "Scusi, come si dice…?"
+ ]
+ },
+ {
+ "id": "B10",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Cortesia Italiana: Tu e Lei",
+ "titlePt": "Cortesia italiana: obrigado, desculpe, bom dia — tu vs Lei",
+ "canDos": [
+ {
+ "id": "b10-cd1",
+ "text": "Consigo usar saudações adequadas ao período do dia"
+ },
+ {
+ "id": "b10-cd2",
+ "text": "Consigo agradecer, pedir licença e desculpas de forma natural"
+ },
+ {
+ "id": "b10-cd3",
+ "text": "Consigo distinguir quando usar 'tu' e quando usar 'Lei'"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Buongiorno!",
+ "pt": "Bom dia! (manhã/tarde cedo)",
+ "note": "Use até ~17h; depois 'buonasera'",
+ "audioKey": "b10-chunk-01"
+ },
+ {
+ "it": "Buonasera!",
+ "pt": "Boa noite! (tarde/noite)",
+ "audioKey": "b10-chunk-02"
+ },
+ {
+ "it": "Buonanotte!",
+ "pt": "Boa noite! (ao se despedir para dormir)",
+ "note": "Só na hora de ir dormir",
+ "audioKey": "b10-chunk-03"
+ },
+ {
+ "it": "Grazie! / Grazie mille!",
+ "pt": "Obrigado! / Muito obrigado!",
+ "audioKey": "b10-chunk-04"
+ },
+ {
+ "it": "Prego!",
+ "pt": "De nada! / Pois não!",
+ "note": "'prego' também = 'pois não' ao oferecer algo",
+ "audioKey": "b10-chunk-05"
+ },
+ {
+ "it": "Scusa! (informale)",
+ "pt": "Desculpe! (com amigos/família)",
+ "note": "Tu: scusa; Lei: scusi",
+ "audioKey": "b10-chunk-06"
+ },
+ {
+ "it": "Mi scusi! (formale)",
+ "pt": "Desculpe-me! (com estranhos/mais velhos)",
+ "note": "Sempre use com desconhecidos",
+ "audioKey": "b10-chunk-07"
+ },
+ {
+ "it": "Permesso!",
+ "pt": "Com licença! (para passar)",
+ "audioKey": "b10-chunk-08"
+ },
+ {
+ "it": "Per favore.",
+ "pt": "Por favor.",
+ "audioKey": "b10-chunk-09"
+ },
+ {
+ "it": "Arrivederci!",
+ "pt": "Até logo! (formal/neutro)",
+ "audioKey": "b10-chunk-10"
+ },
+ {
+ "it": "Ciao!",
+ "pt": "Oi/Tchau! (informal)",
+ "note": "Serve tanto para chegar quanto para sair",
+ "audioKey": "b10-chunk-11"
+ },
+ {
+ "it": "Come si dice 'com licença' in italiano?",
+ "pt": "Como se diz 'com licença' em italiano?",
+ "note": "SALVAVITA de cortesia",
+ "audioKey": "b10-chunk-12"
+ },
+ {
+ "it": "È stato un piacere!",
+ "pt": "Foi um prazer!",
+ "audioKey": "b10-chunk-13"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Buongiorno! Mi scusi, posso passare?",
+ "pt": "Bom dia! Desculpe, posso passar?",
+ "audioKey": "b10-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certamente! Prego, passi pure!",
+ "pt": "Certamente! Pois não, passe!",
+ "audioKey": "b10-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie mille! Permesso!",
+ "pt": "Muito obrigado! Com licença!",
+ "audioKey": "b10-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Di niente! Buongiorno!",
+ "pt": "De nada! Bom dia!",
+ "audioKey": "b10-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Arrivederci e buona giornata!",
+ "pt": "Até logo e bom dia!",
+ "audioKey": "b10-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Altrettanto! Arrivederci!",
+ "pt": "Igualmente! Até logo!",
+ "audioKey": "b10-d1-l6"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Ciao! Come stai?",
+ "pt": "Oi! Como está?",
+ "audioKey": "b10-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Ciao! Sto bene, grazie! E tu?",
+ "pt": "Oi! Estou bem, obrigado! E você?",
+ "note": "'tu' porque é amigo",
+ "audioKey": "b10-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Benissimo! Vuoi un caffè?",
+ "pt": "Muito bem! Quer um café?",
+ "audioKey": "b10-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, grazie! Per favore.",
+ "pt": "Sim, obrigado! Por favor.",
+ "audioKey": "b10-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ecco il caffè! Prego!",
+ "pt": "Aqui o café! Pois não!",
+ "audioKey": "b10-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie! Sei molto gentile!",
+ "pt": "Obrigado! Você é muito gentil!",
+ "audioKey": "b10-d2-l6"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ma figurati! Ciao!",
+ "pt": "Imagina! Tchau!",
+ "audioKey": "b10-d2-l7"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In un negozio del centro",
+ "italianRole": "Commesso/a del negozio (formale) e poi amico incontrato fuori (informale)",
+ "goalPt": "Praticar a diferença entre tratamento formal (Lei) e informal (tu) na mesma situação",
+ "openerIt": "Buongiorno, signore! Posso aiutarla?"
+ },
+ "survivalChecklist": [
+ "Buongiorno / Buonasera / Buonanotte",
+ "Grazie! / Prego!",
+ "Scusa (tu) / Mi scusi (Lei)",
+ "Permesso! / Per favore",
+ "Ciao! / Arrivederci!"
+ ]
+ },
+ {
+ "id": "B11",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Questo è… / Quello è… + Circunlóquio",
+ "titlePt": "Isto é… / Aquilo é… + descrever sem saber o nome",
+ "canDos": [
+ {
+ "id": "b11-cd1",
+ "text": "Consigo apresentar objetos com 'questo è' e 'quello è'"
+ },
+ {
+ "id": "b11-cd2",
+ "text": "Consigo descrever algo que não sei nomear com 'è una cosa per…' e 'serve a…'"
+ },
+ {
+ "id": "b11-cd3",
+ "text": "Consigo manter comunicação eficaz mesmo sem vocabulário específico"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Questo è un libro.",
+ "pt": "Isto é um livro.",
+ "audioKey": "b11-chunk-01"
+ },
+ {
+ "it": "Quello è un albero.",
+ "pt": "Aquilo é uma árvore.",
+ "note": "'quello' = aquele (longe); 'questo' = este (perto)",
+ "audioKey": "b11-chunk-02"
+ },
+ {
+ "it": "Questa è una mela.",
+ "pt": "Esta é uma maçã.",
+ "note": "'questa' feminino; 'quella' feminino longe",
+ "audioKey": "b11-chunk-03"
+ },
+ {
+ "it": "È una cosa per cucinare.",
+ "pt": "É uma coisa para cozinhar.",
+ "note": "Circunlóquio: função = 'per' + infinitivo",
+ "audioKey": "b11-chunk-04"
+ },
+ {
+ "it": "Serve a scrivere sulla lavagna.",
+ "pt": "Serve para escrever no quadro.",
+ "note": "'servire a' + infinitivo",
+ "audioKey": "b11-chunk-05"
+ },
+ {
+ "it": "È quella cosa lunga e nera.",
+ "pt": "É aquela coisa comprida e preta.",
+ "note": "Adjetivos ajudam muito no circunlóquio",
+ "audioKey": "b11-chunk-06"
+ },
+ {
+ "it": "È fatto di legno.",
+ "pt": "É feito de madeira.",
+ "note": "'fatto di' + material",
+ "audioKey": "b11-chunk-07"
+ },
+ {
+ "it": "Si usa in cucina.",
+ "pt": "Se usa na cozinha.",
+ "note": "'si usa' = se usa (impessoal)",
+ "audioKey": "b11-chunk-08"
+ },
+ {
+ "it": "Assomiglia a un cucchiaio ma è più grande.",
+ "pt": "Parece uma colher mas é maior.",
+ "note": "'assomigliare a' = parecer com",
+ "audioKey": "b11-chunk-09"
+ },
+ {
+ "it": "Non so il nome, ma è una cosa che serve per…",
+ "pt": "Não sei o nome, mas é uma coisa que serve para…",
+ "note": "Fórmula completa de circunlóquio",
+ "audioKey": "b11-chunk-10"
+ },
+ {
+ "it": "Come si chiama questa cosa?",
+ "pt": "Como se chama esta coisa?",
+ "note": "SALVAVITA combinado com descrição",
+ "audioKey": "b11-chunk-11"
+ },
+ {
+ "it": "Ah, è un mestolo! Grazie!",
+ "pt": "Ah, é uma concha! Obrigado!",
+ "audioKey": "b11-chunk-12"
+ },
+ {
+ "it": "È piccolo, rotondo e rosso.",
+ "pt": "É pequeno, redondo e vermelho.",
+ "note": "Encadeie adjetivos para precisão",
+ "audioKey": "b11-chunk-13"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Guarda, questo è… non so il nome. È una cosa per aprire le bottiglie.",
+ "pt": "Olha, isto é… não sei o nome. É uma coisa para abrir garrafas.",
+ "audioKey": "b11-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ah, intendi il cavatappi?",
+ "pt": "Ah, quer dizer o saca-rolhas?",
+ "audioKey": "b11-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, il cavatappi! E quello là, cos'è?",
+ "pt": "Sim, o saca-rolhas! E aquilo ali, o que é?",
+ "audioKey": "b11-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Quello è un tagliere. Serve a tagliare il formaggio.",
+ "pt": "Aquilo é uma tábua de corte. Serve para cortar queijo.",
+ "audioKey": "b11-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Un tagliere! Ho capito. E questa cosa piccola e rotonda?",
+ "pt": "Uma tábua de corte! Entendi. E esta coisa pequena e redonda?",
+ "audioKey": "b11-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "È un tappo! Si usa per chiudere la bottiglia.",
+ "pt": "É uma rolha/tampa! Se usa para fechar a garrafa.",
+ "audioKey": "b11-d1-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto! Ora so tre parole nuove!",
+ "pt": "Perfeito! Agora sei três palavras novas!",
+ "audioKey": "b11-d1-l7"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Cos'hai nella borsa?",
+ "pt": "O que tem na bolsa?",
+ "audioKey": "b11-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Ho questa cosa… è lunga, nera e serve a scrivere.",
+ "pt": "Tenho esta coisa… é comprida, preta e serve para escrever.",
+ "audioKey": "b11-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Una penna?",
+ "pt": "Uma caneta?",
+ "audioKey": "b11-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, una penna! E poi ho quell'altra cosa, è fatta di stoffa e serve a pulire.",
+ "pt": "Sim, uma caneta! E depois tenho aquela outra coisa, é feita de tecido e serve para limpar.",
+ "audioKey": "b11-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Un fazzoletto? O un tovagliolo?",
+ "pt": "Um lenço? Ou um guardanapo?",
+ "audioKey": "b11-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Un fazzoletto! Esatto! Grazie, non mi veniva la parola.",
+ "pt": "Um lenço! Exato! Obrigado, não me vinha a palavra.",
+ "audioKey": "b11-d2-l6"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In cucina con un amico italiano",
+ "italianRole": "Amico che mostra utensili da cucina",
+ "goalPt": "Descrever utensílios cujo nome não sabe usando circunlóquio e aprender os nomes corretos",
+ "openerIt": "Vieni, ti mostro gli utensili della mia cucina!"
+ },
+ "survivalChecklist": [
+ "Questo è… / Quello è…",
+ "È una cosa per… / Serve a…",
+ "È fatto di… / Si usa in…",
+ "Non so il nome, ma…",
+ "Come si chiama questa cosa?"
+ ]
+ },
+ {
+ "id": "B12",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Mini-Conversazione al Bar",
+ "titlePt": "Mini-conversa: pedindo um lanche no bar",
+ "canDos": [
+ {
+ "id": "b12-cd1",
+ "text": "Consigo pedir um lanche combinando todas as estruturas da trilha bambino"
+ },
+ {
+ "id": "b12-cd2",
+ "text": "Consigo usar circunlóquio quando não sei o nome de um ingrediente"
+ },
+ {
+ "id": "b12-cd3",
+ "text": "Consigo manter uma mini-conversa completa num bar italiano"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Buongiorno! Vorrei un panino.",
+ "pt": "Bom dia! Gostaria de um sanduíche.",
+ "note": "'Vorrei' = condizionale, mais cortês que 'voglio'",
+ "audioKey": "b12-chunk-01"
+ },
+ {
+ "it": "Un panino con prosciutto e formaggio.",
+ "pt": "Um sanduíche com presunto e queijo.",
+ "audioKey": "b12-chunk-02"
+ },
+ {
+ "it": "Scusi, come si chiama quello lì?",
+ "pt": "Desculpe, como se chama aquilo ali?",
+ "note": "Circunlóquio + apontar = estratégia vencedora",
+ "audioKey": "b12-chunk-03"
+ },
+ {
+ "it": "È una cosa verde, per il panino.",
+ "pt": "É uma coisa verde, para o sanduíche.",
+ "note": "Descreva cor + função",
+ "audioKey": "b12-chunk-04"
+ },
+ {
+ "it": "Ah, la rucola! Sì, la voglio!",
+ "pt": "Ah, a rúcula! Sim, eu quero!",
+ "audioKey": "b12-chunk-05"
+ },
+ {
+ "it": "Quanto costa?",
+ "pt": "Quanto custa?",
+ "audioKey": "b12-chunk-06"
+ },
+ {
+ "it": "Posso pagare con la carta?",
+ "pt": "Posso pagar com cartão?",
+ "audioKey": "b12-chunk-07"
+ },
+ {
+ "it": "Mi piace tantissimo!",
+ "pt": "Gosto muitíssimo!",
+ "audioKey": "b12-chunk-08"
+ },
+ {
+ "it": "È buonissimo, grazie!",
+ "pt": "Está muito gostoso, obrigado!",
+ "audioKey": "b12-chunk-09"
+ },
+ {
+ "it": "Non capisco bene, può ripetere?",
+ "pt": "Não entendo bem, pode repetir?",
+ "audioKey": "b12-chunk-10"
+ },
+ {
+ "it": "Ho anche sete. Un'acqua naturale, per favore.",
+ "pt": "Também estou com sede. Uma água sem gás, por favor.",
+ "note": "'naturale' = sem gás; 'frizzante/gassata' = com gás",
+ "audioKey": "b12-chunk-11"
+ },
+ {
+ "it": "Grazie mille! Arrivederci!",
+ "pt": "Muito obrigado! Até logo!",
+ "audioKey": "b12-chunk-12"
+ },
+ {
+ "it": "È una cosa rossa e dolce, dentro il panino.",
+ "pt": "É uma coisa vermelha e doce, dentro do sanduíche.",
+ "note": "Circunlóquio para tomate/pimentão/etc.",
+ "audioKey": "b12-chunk-13"
+ },
+ {
+ "it": "Il pomodoro! Certo, lo metto!",
+ "pt": "O tomate! Claro, eu coloco!",
+ "audioKey": "b12-chunk-14"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Buongiorno! Vorrei un panino, per favore.",
+ "pt": "Bom dia! Gostaria de um sanduíche, por favor.",
+ "audioKey": "b12-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certamente! Con cosa lo vuoi? Abbiamo prosciutto, salame, formaggio…",
+ "pt": "Claro! Com que quer? Temos presunto, salame, queijo…",
+ "audioKey": "b12-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Con prosciutto e… scusi, come si chiama quella cosa verde?",
+ "pt": "Com presunto e… desculpe, como se chama aquela coisa verde?",
+ "audioKey": "b12-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "La rucola? È un'insalata, un po' amara ma buonissima!",
+ "pt": "A rúcula? É uma salada, um pouco amarga mas muito gostosa!",
+ "audioKey": "b12-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, la rucola! Mi piace! E anche quel formaggio bianco.",
+ "pt": "Sim, a rúcula! Gosto! E também aquele queijo branco.",
+ "audioKey": "b12-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "La mozzarella? Ottima scelta!",
+ "pt": "A mussarela? Ótima escolha!",
+ "audioKey": "b12-d1-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto! Quanto costa?",
+ "pt": "Perfeito! Quanto custa?",
+ "audioKey": "b12-d1-l7"
+ },
+ {
+ "speaker": "italian",
+ "it": "Sette euro. Vuoi anche da bere?",
+ "pt": "Sete euros. Quer também algo para beber?",
+ "audioKey": "b12-d1-l8"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, un'acqua naturale, per favore. Posso pagare con la carta?",
+ "pt": "Sim, uma água sem gás, por favor. Posso pagar com cartão?",
+ "audioKey": "b12-d1-l9"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certo! Ecco il tuo panino. Buon appetito!",
+ "pt": "Claro! Aqui está seu sanduíche. Bom apetite!",
+ "audioKey": "b12-d1-l10"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Ciao! Cosa prendi oggi?",
+ "pt": "Oi! O que vai pegar hoje?",
+ "audioKey": "b12-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Vorrei qualcosa di caldo. Ho fame e ho freddo!",
+ "pt": "Gostaria de algo quente. Estou com fome e com frio!",
+ "audioKey": "b12-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Allora ti consiglio una zuppa! È buonissima!",
+ "pt": "Então te recomendo uma sopa! É muito gostosa!",
+ "audioKey": "b12-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Una zuppa? Di cosa è fatta?",
+ "pt": "Uma sopa? De que é feita?",
+ "audioKey": "b12-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Di verdure, pomodoro e un po' di pasta.",
+ "pt": "De legumes, tomate e um pouco de massa.",
+ "audioKey": "b12-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Mi piace! La prendo! E un caffè dopo, per favore.",
+ "pt": "Gosto! Eu pego! E um café depois, por favor.",
+ "audioKey": "b12-d2-l6"
+ },
+ {
+ "speaker": "italian",
+ "it": "Volentieri! Ti porto subito la zuppa!",
+ "pt": "Com prazer! Te trago a sopa agora!",
+ "audioKey": "b12-d2-l7"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie! Non vedo l'ora!",
+ "pt": "Obrigado! Mal posso esperar!",
+ "audioKey": "b12-d2-l8"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Al bar della piazza",
+ "italianRole": "Barista simpatico e paziente",
+ "goalPt": "Pedir um lanche completo usando tutte le strutture apprese: vorrei, circunlóquio, quanto costa, cortesia",
+ "openerIt": "Buongiorno! Cosa desidera?"
+ },
+ "survivalChecklist": [
+ "Vorrei un panino con…",
+ "Scusi, come si chiama quello lì?",
+ "È una cosa [colore] per…",
+ "Quanto costa? / Posso pagare con la carta?",
+ "Grazie mille! Buon appetito!"
+ ]
+ },
+ {
+ "id": "B2",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Ho bisogno / Mi serve",
+ "titlePt": "Preciso de / Me faz falta",
+ "canDos": [
+ {
+ "id": "b2-cd1",
+ "text": "Consigo dizer que preciso de algo com 'ho bisogno di'"
+ },
+ {
+ "id": "b2-cd2",
+ "text": "Consigo usar 'mi serve' para pedir objetos do dia a dia"
+ },
+ {
+ "id": "b2-cd3",
+ "text": "Consigo explicar por que preciso usando gestos e palavras simples"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Ho bisogno di aiuto.",
+ "pt": "Preciso de ajuda.",
+ "audioKey": "b2-chunk-01"
+ },
+ {
+ "it": "Ho bisogno dell'acqua.",
+ "pt": "Preciso de água.",
+ "note": "'di' + artigo = 'dell'' antes de vogal",
+ "audioKey": "b2-chunk-02"
+ },
+ {
+ "it": "Mi serve una penna.",
+ "pt": "Me faz falta uma caneta / Preciso de uma caneta.",
+ "audioKey": "b2-chunk-03"
+ },
+ {
+ "it": "Mi serve il bagno.",
+ "pt": "Preciso do banheiro.",
+ "note": "Frase essencial em qualquer situação",
+ "audioKey": "b2-chunk-04"
+ },
+ {
+ "it": "Non ho bisogno di niente.",
+ "pt": "Não preciso de nada.",
+ "audioKey": "b2-chunk-05"
+ },
+ {
+ "it": "Ho bisogno di mangiare.",
+ "pt": "Preciso comer.",
+ "note": "'di' + infinitivo para ações",
+ "audioKey": "b2-chunk-06"
+ },
+ {
+ "it": "Mi servono le scarpe.",
+ "pt": "Preciso dos sapatos.",
+ "note": "'servono' plural quando o objeto é plural",
+ "audioKey": "b2-chunk-07"
+ },
+ {
+ "it": "Scusi, mi serve un telefono.",
+ "pt": "Desculpe, preciso de um telefone.",
+ "audioKey": "b2-chunk-08"
+ },
+ {
+ "it": "Ho bisogno di dormire.",
+ "pt": "Preciso dormir.",
+ "audioKey": "b2-chunk-09"
+ },
+ {
+ "it": "Come si dice 'preciso' in italiano?",
+ "pt": "Como se diz 'preciso' em italiano?",
+ "note": "SALVAVITA: peça a palavra",
+ "audioKey": "b2-chunk-10"
+ },
+ {
+ "it": "Mi serve anche questo.",
+ "pt": "Também preciso disto.",
+ "audioKey": "b2-chunk-11"
+ },
+ {
+ "it": "Ho proprio bisogno di te.",
+ "pt": "Realmente preciso de você.",
+ "note": "'proprio' intensifica",
+ "audioKey": "b2-chunk-12"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Mamma, ho bisogno di aiuto!",
+ "pt": "Mamãe, preciso de ajuda!",
+ "audioKey": "b2-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Cosa ti serve, tesoro?",
+ "pt": "O que você precisa, querido?",
+ "audioKey": "b2-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Mi serve una matita.",
+ "pt": "Preciso de um lápis.",
+ "audioKey": "b2-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ecco! Ti serve altro?",
+ "pt": "Aqui! Precisa de mais alguma coisa?",
+ "audioKey": "b2-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, ho bisogno anche della gomma.",
+ "pt": "Sim, também preciso da borracha.",
+ "audioKey": "b2-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Dai, prendi. Hai bisogno di nient'altro?",
+ "pt": "Vai, pegue. Precisa de mais nada?",
+ "audioKey": "b2-d1-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, grazie! Non ho bisogno di niente.",
+ "pt": "Não, obrigado! Não preciso de nada.",
+ "audioKey": "b2-d1-l7"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Stai bene? Hai bisogno di qualcosa?",
+ "pt": "Está bem? Precisa de alguma coisa?",
+ "audioKey": "b2-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, mi serve il bagno, per favore.",
+ "pt": "Sim, preciso do banheiro, por favor.",
+ "audioKey": "b2-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certo! È laggiù, a destra.",
+ "pt": "Claro! É ali embaixo, à direita.",
+ "audioKey": "b2-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie! Ho anche bisogno di lavarmi le mani.",
+ "pt": "Obrigado! Também preciso lavar as mãos.",
+ "audioKey": "b2-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Va bene, c'è il sapone lì.",
+ "pt": "Tudo bem, há sabonete ali.",
+ "audioKey": "b2-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto, grazie mille!",
+ "pt": "Perfeito, muito obrigado!",
+ "audioKey": "b2-d2-l6"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "A scuola con l'insegnante",
+ "italianRole": "Maestra che chiede cosa serve all'alunno",
+ "goalPt": "Pedir materiais escolares usando 'ho bisogno di' e 'mi serve'",
+ "openerIt": "Bambini, cosa vi serve per la lezione?"
+ },
+ "survivalChecklist": [
+ "Ho bisogno di…",
+ "Mi serve…",
+ "Mi serve il bagno",
+ "Non ho bisogno di niente",
+ "Come si dice 'preciso'?"
+ ]
+ },
+ {
+ "id": "B3",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Come si dice…?",
+ "titlePt": "Como se diz…? (O Salvavidas)",
+ "canDos": [
+ {
+ "id": "b3-cd1",
+ "text": "Consigo pedir a palavra que não sei com 'come si dice'"
+ },
+ {
+ "id": "b3-cd2",
+ "text": "Consigo descrever algo que não sei nomear usando gestos e 'è una cosa per…'"
+ },
+ {
+ "id": "b3-cd3",
+ "text": "Consigo manter a conversa mesmo sem saber todas as palavras"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Scusi, come si dice questo in italiano?",
+ "pt": "Desculpe, como se diz isto em italiano?",
+ "note": "SALVAVITA PRINCIPAL — use sempre!",
+ "audioKey": "b3-chunk-01"
+ },
+ {
+ "it": "Come si chiama questa cosa?",
+ "pt": "Como se chama esta coisa?",
+ "audioKey": "b3-chunk-02"
+ },
+ {
+ "it": "Non so la parola.",
+ "pt": "Não sei a palavra.",
+ "audioKey": "b3-chunk-03"
+ },
+ {
+ "it": "È una cosa per scrivere.",
+ "pt": "É uma coisa para escrever.",
+ "note": "Circunlóquio: 'per' + infinitivo descreve função",
+ "audioKey": "b3-chunk-04"
+ },
+ {
+ "it": "Serve a tagliare.",
+ "pt": "Serve para cortar.",
+ "note": "'servire a' + infinitivo = serve para",
+ "audioKey": "b3-chunk-05"
+ },
+ {
+ "it": "Quella cosa lì, come si dice?",
+ "pt": "Aquela coisa ali, como se diz?",
+ "audioKey": "b3-chunk-06"
+ },
+ {
+ "it": "Non mi viene la parola.",
+ "pt": "Não me vem a palavra.",
+ "note": "Expressão idiomática natural",
+ "audioKey": "b3-chunk-07"
+ },
+ {
+ "it": "Ah, sì! È un cucchiaio!",
+ "pt": "Ah, sim! É uma colher!",
+ "audioKey": "b3-chunk-08"
+ },
+ {
+ "it": "Grazie, non lo sapevo.",
+ "pt": "Obrigado, não sabia.",
+ "audioKey": "b3-chunk-09"
+ },
+ {
+ "it": "Come si dice 'garfo' in italiano?",
+ "pt": "Como se diz 'garfo' em italiano?",
+ "audioKey": "b3-chunk-10"
+ },
+ {
+ "it": "È quella cosa rotonda per bere.",
+ "pt": "É aquela coisa redonda para beber.",
+ "note": "Combine adjetivo + 'per' para circunlóquio rico",
+ "audioKey": "b3-chunk-11"
+ },
+ {
+ "it": "Mi può aiutare con la parola?",
+ "pt": "Pode me ajudar com a palavra?",
+ "note": "Use Lei (formal) com desconhecidos",
+ "audioKey": "b3-chunk-12"
+ },
+ {
+ "it": "Ho dimenticato come si dice.",
+ "pt": "Esqueci como se diz.",
+ "audioKey": "b3-chunk-13"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Scusi, come si dice questa cosa?",
+ "pt": "Desculpe, como se diz esta coisa?",
+ "audioKey": "b3-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Quale cosa? Questa qui?",
+ "pt": "Qual coisa? Esta aqui?",
+ "audioKey": "b3-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì! È una cosa per aprire la bottiglia.",
+ "pt": "Sim! É uma coisa para abrir a garrafa.",
+ "audioKey": "b3-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ah, è un cavatappi!",
+ "pt": "Ah, é um saca-rolhas!",
+ "audioKey": "b3-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Un cavatappi! Grazie, non lo sapevo.",
+ "pt": "Um saca-rolhas! Obrigado, não sabia.",
+ "audioKey": "b3-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Di niente! Chiedi pure se non sai una parola.",
+ "pt": "De nada! Pergunte à vontade se não souber uma palavra.",
+ "audioKey": "b3-d1-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto, grazie mille!",
+ "pt": "Perfeito, muito obrigado!",
+ "audioKey": "b3-d1-l7"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Cosa stai cercando?",
+ "pt": "O que está procurando?",
+ "audioKey": "b3-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Non mi viene la parola… è quella cosa per asciugare i piatti.",
+ "pt": "Não me vem a palavra… é aquela coisa para secar os pratos.",
+ "audioKey": "b3-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Intendi lo strofinaccio?",
+ "pt": "Quer dizer o pano de prato?",
+ "audioKey": "b3-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, sì! Lo strofinaccio! Come si dice in portoghese?",
+ "pt": "Sim, sim! O pano de prato! Como se diz em português?",
+ "audioKey": "b3-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Magari non lo so, ma ora hai la parola italiana!",
+ "pt": "Talvez eu não saiba, mas agora você tem a palavra italiana!",
+ "audioKey": "b3-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Dai, grazie! Ora me la ricordo.",
+ "pt": "Vai, obrigado! Agora vou lembrar.",
+ "audioKey": "b3-d2-l6"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Al mercato rionale",
+ "italianRole": "Venditore del banco della frutta",
+ "goalPt": "Pedir frutas cujo nome você não sabe, descrevendo-as ou perguntando 'come si dice'",
+ "openerIt": "Buongiorno! Cosa desidera oggi?"
+ },
+ "survivalChecklist": [
+ "Scusi, come si dice…?",
+ "Come si chiama questa cosa?",
+ "È una cosa per… / Serve a…",
+ "Non so la parola",
+ "Non mi viene la parola"
+ ]
+ },
+ {
+ "id": "B4",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Non capisco / Può ripetere?",
+ "titlePt": "Não entendo / Pode repetir?",
+ "canDos": [
+ {
+ "id": "b4-cd1",
+ "text": "Consigo sinalizar que não entendi sem travar a conversa"
+ },
+ {
+ "id": "b4-cd2",
+ "text": "Consigo pedir para falar mais devagar com 'più lentamente'"
+ },
+ {
+ "id": "b4-cd3",
+ "text": "Consigo confirmar o que entendi repetindo com minhas palavras"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Non capisco.",
+ "pt": "Não entendo.",
+ "audioKey": "b4-chunk-01"
+ },
+ {
+ "it": "Non ho capito.",
+ "pt": "Não entendi.",
+ "note": "Passado: quando já foi dito e você perdeu",
+ "audioKey": "b4-chunk-02"
+ },
+ {
+ "it": "Può ripetere, per favore?",
+ "pt": "Pode repetir, por favor?",
+ "note": "Use Lei (formal) com estranhos",
+ "audioKey": "b4-chunk-03"
+ },
+ {
+ "it": "Può parlare più lentamente?",
+ "pt": "Pode falar mais devagar?",
+ "audioKey": "b4-chunk-04"
+ },
+ {
+ "it": "Un po' più piano, per favore.",
+ "pt": "Um pouco mais devagar, por favor.",
+ "note": "'piano' = devagar/baixo; 'forte' = alto/rápido",
+ "audioKey": "b4-chunk-05"
+ },
+ {
+ "it": "Cosa significa?",
+ "pt": "O que significa?",
+ "audioKey": "b4-chunk-06"
+ },
+ {
+ "it": "Non capisco bene.",
+ "pt": "Não entendo bem.",
+ "audioKey": "b4-chunk-07"
+ },
+ {
+ "it": "Ah, ora capisco!",
+ "pt": "Ah, agora entendo!",
+ "audioKey": "b4-chunk-08"
+ },
+ {
+ "it": "Ho capito! Vuol dire…",
+ "pt": "Entendi! Quer dizer…",
+ "note": "Confirme reformulando",
+ "audioKey": "b4-chunk-09"
+ },
+ {
+ "it": "Scusi, non ho sentito.",
+ "pt": "Desculpe, não ouvi.",
+ "audioKey": "b4-chunk-10"
+ },
+ {
+ "it": "Può spiegare in modo semplice?",
+ "pt": "Pode explicar de forma simples?",
+ "audioKey": "b4-chunk-11"
+ },
+ {
+ "it": "Come si dice 'non entendo' in italiano?",
+ "pt": "Como se diz 'não entendo' em italiano?",
+ "note": "SALVAVITA: até para dizer que não entende!",
+ "audioKey": "b4-chunk-12"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "italian",
+ "it": "Allora, domani andiamo alla stazione alle otto e prendiamo il treno per Firenze.",
+ "pt": "Então, amanhã vamos à estação às oito e pegamos o trem para Florença.",
+ "audioKey": "b4-d1-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Scusi, non ho capito. Può ripetere più lentamente?",
+ "pt": "Desculpe, não entendi. Pode repetir mais devagar?",
+ "audioKey": "b4-d1-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certo! Domani… alle otto… andiamo alla stazione.",
+ "pt": "Claro! Amanhã… às oito… vamos à estação.",
+ "audioKey": "b4-d1-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Ah, ora capisco! Alle otto, giusto?",
+ "pt": "Ah, agora entendo! Às oito, certo?",
+ "audioKey": "b4-d1-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Esatto! Bravo!",
+ "pt": "Exato! Muito bem!",
+ "audioKey": "b4-d1-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie! Quando parli piano, capisco meglio.",
+ "pt": "Obrigado! Quando fala devagar, entendo melhor.",
+ "audioKey": "b4-d1-l6"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Il museo chiude alle diciassette, ma sabato resta aperto fino alle venti.",
+ "pt": "O museu fecha às dezessete, mas sábado fica aberto até as vinte.",
+ "audioKey": "b4-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Cosa significa 'resta aperto'?",
+ "pt": "O que significa 'resta aperto'?",
+ "audioKey": "b4-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Vuol dire che rimane aperto, non chiude.",
+ "pt": "Quer dizer que permanece aberto, não fecha.",
+ "audioKey": "b4-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Ho capito! Quindi sabato posso andare anche la sera?",
+ "pt": "Entendi! Então sábado posso ir também à noite?",
+ "audioKey": "b4-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Proprio così! Magari è meno affollato.",
+ "pt": "Isso mesmo! Talvez esteja menos lotado.",
+ "audioKey": "b4-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto, grazie della spiegazione!",
+ "pt": "Perfeito, obrigado pela explicação!",
+ "audioKey": "b4-d2-l6"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "All'ufficio informazioni turistiche",
+ "italianRole": "Addetto all'informazione turistica",
+ "goalPt": "Pedir informações sobre horários e entender a resposta pedindo para repetir",
+ "openerIt": "Buongiorno, posso aiutarla?"
+ },
+ "survivalChecklist": [
+ "Non capisco / Non ho capito",
+ "Può ripetere, per favore?",
+ "Può parlare più lentamente?",
+ "Cosa significa?",
+ "Ah, ora capisco!"
+ ]
+ },
+ {
+ "id": "B5",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Gusto / Non gusto",
+ "titlePt": "Gosto / Não gosto + adjetivos sensoriais",
+ "canDos": [
+ {
+ "id": "b5-cd1",
+ "text": "Consigo expressar preferência com 'mi piace / non mi piace'"
+ },
+ {
+ "id": "b5-cd2",
+ "text": "Consigo descrever comida com adjetivos básicos (buono, dolce, freddo)"
+ },
+ {
+ "id": "b5-cd3",
+ "text": "Consigo dizer que algo está gostoso ou ruim mesmo sem vocabulário rico"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Mi piace questo!",
+ "pt": "Gosto disto!",
+ "note": "'piacere' funciona ao contrário: a coisa agrada a mim",
+ "audioKey": "b5-chunk-01"
+ },
+ {
+ "it": "Non mi piace.",
+ "pt": "Não gosto.",
+ "audioKey": "b5-chunk-02"
+ },
+ {
+ "it": "Questo è buono!",
+ "pt": "Isto é bom/gostoso!",
+ "audioKey": "b5-chunk-03"
+ },
+ {
+ "it": "È buonissimo!",
+ "pt": "É muito gostoso!",
+ "note": "Superlativo: -issimo = muito",
+ "audioKey": "b5-chunk-04"
+ },
+ {
+ "it": "È troppo dolce.",
+ "pt": "É doce demais.",
+ "note": "'troppo' = demais/muito",
+ "audioKey": "b5-chunk-05"
+ },
+ {
+ "it": "È freddo.",
+ "pt": "Está frio.",
+ "audioKey": "b5-chunk-06"
+ },
+ {
+ "it": "È caldo.",
+ "pt": "Está quente.",
+ "audioKey": "b5-chunk-07"
+ },
+ {
+ "it": "Non è buono.",
+ "pt": "Não é bom.",
+ "audioKey": "b5-chunk-08"
+ },
+ {
+ "it": "Che buono! Mi piace tantissimo!",
+ "pt": "Que gostoso! Gosto muitíssimo!",
+ "audioKey": "b5-chunk-09"
+ },
+ {
+ "it": "È un po' amaro.",
+ "pt": "É um pouco amargo.",
+ "note": "'un po'' = um pouco",
+ "audioKey": "b5-chunk-10"
+ },
+ {
+ "it": "Come si dice 'picante' in italiano?",
+ "pt": "Como se diz 'apimentado' em italiano?",
+ "note": "SALVAVITA para sabores desconhecidos",
+ "audioKey": "b5-chunk-11"
+ },
+ {
+ "it": "Preferisco quello salato.",
+ "pt": "Prefiro aquele salgado.",
+ "audioKey": "b5-chunk-12"
+ },
+ {
+ "it": "È una cosa dolce o salata?",
+ "pt": "É uma coisa doce ou salgada?",
+ "note": "Circunlóquio para perguntar sobre sabor",
+ "audioKey": "b5-chunk-13"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "italian",
+ "it": "Assaggia! Ti piace la pizza?",
+ "pt": "Prove! Gosta de pizza?",
+ "audioKey": "b5-d1-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, mi piace tantissimo! È buonissima!",
+ "pt": "Sim, gosto muitíssimo! É muito gostosa!",
+ "audioKey": "b5-d1-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "E il gelato? Lo vuoi?",
+ "pt": "E o sorvete? Quer?",
+ "audioKey": "b5-d1-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì! Ma questo è troppo dolce per me.",
+ "pt": "Sim! Mas este é doce demais para mim.",
+ "audioKey": "b5-d1-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Dai, prova quello al limone. È meno dolce.",
+ "pt": "Vai, prove o de limão. É menos doce.",
+ "audioKey": "b5-d1-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Mmm, questo mi piace di più! È fresco e buono!",
+ "pt": "Hum, deste gosto mais! É fresco e bom!",
+ "audioKey": "b5-d1-l6"
+ },
+ {
+ "speaker": "italian",
+ "it": "Bravo! Hai buon gusto!",
+ "pt": "Muito bem! Você tem bom gosto!",
+ "audioKey": "b5-d1-l7"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Il caffè è caldo. Attento!",
+ "pt": "O café está quente. Cuidado!",
+ "audioKey": "b5-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie! Mmm… è un po' amaro.",
+ "pt": "Obrigado! Hum… é um pouco amargo.",
+ "audioKey": "b5-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Vuoi lo zucchero? O il latte?",
+ "pt": "Quer açúcar? Ou leite?",
+ "audioKey": "b5-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Un po' di latte, per favore. Non mi piace troppo amaro.",
+ "pt": "Um pouco de leite, por favor. Não gosto muito amargo.",
+ "audioKey": "b5-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ecco! Ora è più dolce?",
+ "pt": "Pronto! Agora está mais doce?",
+ "audioKey": "b5-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, ora è perfetto! Mi piace così!",
+ "pt": "Sim, agora está perfeito! Gosto assim!",
+ "audioKey": "b5-d2-l6"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "A tavola con la famiglia italiana",
+ "italianRole": "Nonna che ha preparato il pranzo",
+ "goalPt": "Experimentar pratos e expressar o que gosta/não gosta usando adjetivos sensoriais",
+ "openerIt": "Tesoro, assaggia! Ho cucinato per te!"
+ },
+ "survivalChecklist": [
+ "Mi piace / Non mi piace",
+ "È buono / È buonissimo",
+ "È troppo dolce / amaro / salato",
+ "È caldo / freddo",
+ "Come si dice 'apimentado'?"
+ ]
+ },
+ {
+ "id": "B6",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Ho fame / Ho sete / Fa male",
+ "titlePt": "Necessidades corporais básicas",
+ "canDos": [
+ {
+ "id": "b6-cd1",
+ "text": "Consigo expressar fome, sede, sono e dor com frases simples"
+ },
+ {
+ "id": "b6-cd2",
+ "text": "Consigo dizer onde dói apontando e usando 'mi fa male'"
+ },
+ {
+ "id": "b6-cd3",
+ "text": "Consigo pedir ajuda para uma necessidade física urgente"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Ho fame.",
+ "pt": "Estou com fome.",
+ "note": "Em italiano se usa 'avere' (ter) para sensações",
+ "audioKey": "b6-chunk-01"
+ },
+ {
+ "it": "Ho sete.",
+ "pt": "Estou com sede.",
+ "audioKey": "b6-chunk-02"
+ },
+ {
+ "it": "Ho sonno.",
+ "pt": "Estou com sono.",
+ "audioKey": "b6-chunk-03"
+ },
+ {
+ "it": "Ho freddo.",
+ "pt": "Estou com frio.",
+ "audioKey": "b6-chunk-04"
+ },
+ {
+ "it": "Ho caldo.",
+ "pt": "Estou com calor.",
+ "audioKey": "b6-chunk-05"
+ },
+ {
+ "it": "Mi fa male la testa.",
+ "pt": "Minha cabeça dói / Estou com dor de cabeça.",
+ "note": "'fa male' = dói; artigo + parte do corpo",
+ "audioKey": "b6-chunk-06"
+ },
+ {
+ "it": "Mi fa male qui.",
+ "pt": "Dói aqui.",
+ "note": "Aponte enquanto diz — gestos completam a frase",
+ "audioKey": "b6-chunk-07"
+ },
+ {
+ "it": "Ho mal di pancia.",
+ "pt": "Estou com dor de barriga.",
+ "note": "'mal di' + parte = dor de…",
+ "audioKey": "b6-chunk-08"
+ },
+ {
+ "it": "Sono stanco.",
+ "pt": "Estou cansado.",
+ "note": "'stanco/a' concorda com gênero",
+ "audioKey": "b6-chunk-09"
+ },
+ {
+ "it": "Ho bisogno di andare in bagno.",
+ "pt": "Preciso ir ao banheiro.",
+ "audioKey": "b6-chunk-10"
+ },
+ {
+ "it": "Come si dice 'dor de garganta'?",
+ "pt": "Como se diz 'dor de garganta'?",
+ "note": "SALVAVITA para dores que não sabe nomear",
+ "audioKey": "b6-chunk-11"
+ },
+ {
+ "it": "Non sto bene.",
+ "pt": "Não estou bem.",
+ "audioKey": "b6-chunk-12"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "italian",
+ "it": "Come stai? Hai mangiato?",
+ "pt": "Como está? Já comeu?",
+ "audioKey": "b6-d1-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, ho tanta fame! E ho anche sete.",
+ "pt": "Não, estou com muita fome! E também com sede.",
+ "audioKey": "b6-d1-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Poverino! Vieni, ti preparo qualcosa.",
+ "pt": "Coitado! Venha, te preparo alguma coisa.",
+ "audioKey": "b6-d1-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie! Ho anche un po' di sonno.",
+ "pt": "Obrigado! Também estou com um pouco de sono.",
+ "audioKey": "b6-d1-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Mangia prima, poi fai un riposino.",
+ "pt": "Coma primeiro, depois descanse um pouquinho.",
+ "audioKey": "b6-d1-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Va bene, grazie! Ho proprio fame!",
+ "pt": "Tudo bem, obrigado! Estou realmente com fome!",
+ "audioKey": "b6-d1-l6"
+ }
+ ],
+ [
+ {
+ "speaker": "learner",
+ "it": "Mamma, mi fa male la pancia!",
+ "pt": "Mamãe, minha barriga dói!",
+ "audioKey": "b6-d2-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Oh no! Dove ti fa male esattamente?",
+ "pt": "Ah não! Onde dói exatamente?",
+ "audioKey": "b6-d2-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Qui! Mi fa male proprio qui.",
+ "pt": "Aqui! Dói bem aqui.",
+ "audioKey": "b6-d2-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Hai mangiato qualcosa di strano?",
+ "pt": "Comeu alguma coisa estranha?",
+ "audioKey": "b6-d2-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Forse sì… non sto bene.",
+ "pt": "Talvez sim… não estou bem.",
+ "audioKey": "b6-d2-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Vieni, ti do una medicina. Stai tranquillo.",
+ "pt": "Venha, te dou um remédio. Fique tranquilo.",
+ "audioKey": "b6-d2-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie, mamma.",
+ "pt": "Obrigado, mamãe.",
+ "audioKey": "b6-d2-l7"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "A casa dopo la scuola",
+ "italianRole": "Genitore che accoglie il bambino",
+ "goalPt": "Expressar necessidades físicas (fome, sede, dor, cansaço) ao chegar em casa",
+ "openerIt": "Ciao tesoro! Come è andata a scuola?"
+ },
+ "survivalChecklist": [
+ "Ho fame / Ho sete / Ho sonno",
+ "Mi fa male la testa / la pancia",
+ "Mi fa male qui (apontando)",
+ "Non sto bene",
+ "Ho bisogno di andare in bagno"
+ ]
+ },
+ {
+ "id": "B7",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Numeri e quantità",
+ "titlePt": "Números 0-20, quanto custa?, pouco/muito",
+ "canDos": [
+ {
+ "id": "b7-cd1",
+ "text": "Consigo contar de 0 a 20 e perguntar preços"
+ },
+ {
+ "id": "b7-cd2",
+ "text": "Consigo expressar quantidade com 'poco', 'tanto', 'troppo'"
+ },
+ {
+ "id": "b7-cd3",
+ "text": "Consigo comparar tamanhos com 'piccolo' e 'grande'"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Zero, uno, due, tre, quattro, cinque.",
+ "pt": "Zero, um, dois, três, quatro, cinco.",
+ "audioKey": "b7-chunk-01"
+ },
+ {
+ "it": "Sei, sette, otto, nove, dieci.",
+ "pt": "Seis, sete, oito, nove, dez.",
+ "audioKey": "b7-chunk-02"
+ },
+ {
+ "it": "Undici, dodici, tredici, quattordici, quindici.",
+ "pt": "Onze, doze, treze, catorze, quinze.",
+ "audioKey": "b7-chunk-03"
+ },
+ {
+ "it": "Sedici, diciassette, diciotto, diciannove, venti.",
+ "pt": "Dezesseis, dezessete, dezoito, dezenove, vinte.",
+ "note": "'diciotto' perde o 'i' de 'dici'",
+ "audioKey": "b7-chunk-04"
+ },
+ {
+ "it": "Quanto costa?",
+ "pt": "Quanto custa?",
+ "audioKey": "b7-chunk-05"
+ },
+ {
+ "it": "Quanto costa questo?",
+ "pt": "Quanto custa isto?",
+ "audioKey": "b7-chunk-06"
+ },
+ {
+ "it": "Costa cinque euro.",
+ "pt": "Custa cinco euros.",
+ "audioKey": "b7-chunk-07"
+ },
+ {
+ "it": "È troppo caro!",
+ "pt": "É caro demais!",
+ "note": "'caro' = caro; 'economico' = barato",
+ "audioKey": "b7-chunk-08"
+ },
+ {
+ "it": "È piccolo.",
+ "pt": "É pequeno.",
+ "audioKey": "b7-chunk-09"
+ },
+ {
+ "it": "È grande.",
+ "pt": "É grande.",
+ "audioKey": "b7-chunk-10"
+ },
+ {
+ "it": "Un po' di più.",
+ "pt": "Um pouco mais.",
+ "audioKey": "b7-chunk-11"
+ },
+ {
+ "it": "Poco / Tanto / Troppo.",
+ "pt": "Pouco / Muito / Demais.",
+ "note": "'troppo' = excesso, além do desejado",
+ "audioKey": "b7-chunk-12"
+ },
+ {
+ "it": "Ce ne sono pochi.",
+ "pt": "Há poucos.",
+ "note": "'ce ne sono' = há/existem (de algo)",
+ "audioKey": "b7-chunk-13"
+ },
+ {
+ "it": "Quanti ce ne sono?",
+ "pt": "Quantos há?",
+ "audioKey": "b7-chunk-14"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Buongiorno! Quanto costa questo gelato?",
+ "pt": "Bom dia! Quanto custa este sorvete?",
+ "audioKey": "b7-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Tre euro e cinquanta. Ne vuoi due?",
+ "pt": "Três euros e cinquenta. Quer dois?",
+ "audioKey": "b7-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, solo uno per me. Ma quello grande, per favore!",
+ "pt": "Não, só um para mim. Mas o grande, por favor!",
+ "audioKey": "b7-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Il grande costa cinque euro.",
+ "pt": "O grande custa cinco euros.",
+ "audioKey": "b7-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Va bene! È troppo buono per dire di no!",
+ "pt": "Tudo bem! É gostoso demais para dizer não!",
+ "audioKey": "b7-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Dai, eccoti il gelato grande!",
+ "pt": "Vai, aqui está o sorvete grande!",
+ "audioKey": "b7-d1-l6"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Quante mele vuoi? Ce ne sono tante!",
+ "pt": "Quantas maçãs quer? Há muitas!",
+ "audioKey": "b7-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Ne voglio cinque, per favore.",
+ "pt": "Quero cinco, por favor.",
+ "audioKey": "b7-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Cinque? Sono un po' troppe per te!",
+ "pt": "Cinco? São um pouco demais para você!",
+ "audioKey": "b7-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, ho tanta fame! Ne mangio almeno tre!",
+ "pt": "Não, estou com muita fome! Como pelo menos três!",
+ "audioKey": "b7-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Va bene, dai! Prendine cinque.",
+ "pt": "Tudo bem, vai! Pegue cinco.",
+ "audioKey": "b7-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie! Quanto costano?",
+ "pt": "Obrigado! Quanto custam?",
+ "audioKey": "b7-d2-l6"
+ },
+ {
+ "speaker": "italian",
+ "it": "Due euro al chilo, quindi circa tre euro.",
+ "pt": "Dois euros o quilo, então cerca de três euros.",
+ "audioKey": "b7-d2-l7"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Al mercato della frutta",
+ "italianRole": "Fruttivendolo simpatico",
+ "goalPt": "Comprar frutas perguntando preço, quantidade e tamanho",
+ "openerIt": "Buongiorno! Ho della frutta freschissima oggi!"
+ },
+ "survivalChecklist": [
+ "Quanto costa?",
+ "Uno, due, tre… fino a venti",
+ "È piccolo / grande",
+ "Poco / Tanto / Troppo",
+ "È troppo caro"
+ ]
+ },
+ {
+ "id": "B8",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Sì, No, Forse, Non lo so",
+ "titlePt": "Sim, Não, Talvez, Não sei — responder simples",
+ "canDos": [
+ {
+ "id": "b8-cd1",
+ "text": "Consigo responder perguntas simples com sì/no/forse"
+ },
+ {
+ "id": "b8-cd2",
+ "text": "Consigo dizer que não sei sem travar a conversa"
+ },
+ {
+ "id": "b8-cd3",
+ "text": "Consigo confirmar ou negar acrescentando uma palavra extra"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Sì.",
+ "pt": "Sim.",
+ "audioKey": "b8-chunk-01"
+ },
+ {
+ "it": "No.",
+ "pt": "Não.",
+ "audioKey": "b8-chunk-02"
+ },
+ {
+ "it": "Forse.",
+ "pt": "Talvez.",
+ "audioKey": "b8-chunk-03"
+ },
+ {
+ "it": "Non lo so.",
+ "pt": "Não sei.",
+ "note": "'lo' = isso/aquilo (objeto direto)",
+ "audioKey": "b8-chunk-04"
+ },
+ {
+ "it": "Sì, certo!",
+ "pt": "Sim, claro!",
+ "audioKey": "b8-chunk-05"
+ },
+ {
+ "it": "No, purtroppo.",
+ "pt": "Não, infelizmente.",
+ "note": "'purtroppo' suaviza a negativa",
+ "audioKey": "b8-chunk-06"
+ },
+ {
+ "it": "Penso di sì.",
+ "pt": "Acho que sim.",
+ "audioKey": "b8-chunk-07"
+ },
+ {
+ "it": "Penso di no.",
+ "pt": "Acho que não.",
+ "audioKey": "b8-chunk-08"
+ },
+ {
+ "it": "Non sono sicuro.",
+ "pt": "Não tenho certeza.",
+ "note": "'sicuro/a' concorda com gênero",
+ "audioKey": "b8-chunk-09"
+ },
+ {
+ "it": "Mah, non saprei.",
+ "pt": "Sei lá, não saberia.",
+ "note": "'mah' é interjeição de dúvida muito italiana",
+ "audioKey": "b8-chunk-10"
+ },
+ {
+ "it": "Assolutamente sì!",
+ "pt": "Com certeza absoluta!",
+ "audioKey": "b8-chunk-11"
+ },
+ {
+ "it": "Come si dice 'talvez' in italiano?",
+ "pt": "Como se diz 'talvez' em italiano?",
+ "note": "SALVAVITA para expressar incerteza",
+ "audioKey": "b8-chunk-12"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "italian",
+ "it": "Ti piace la pasta?",
+ "pt": "Gosta de massa?",
+ "audioKey": "b8-d1-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, certo! Mi piace tantissimo!",
+ "pt": "Sim, claro! Gosto muitíssimo!",
+ "audioKey": "b8-d1-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "E il pesce? Ti piace il pesce?",
+ "pt": "E peixe? Gosta de peixe?",
+ "audioKey": "b8-d1-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Mah, forse. Non sono sicuro.",
+ "pt": "Sei lá, talvez. Não tenho certeza.",
+ "audioKey": "b8-d1-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "E le verdure? Mangi le verdure?",
+ "pt": "E legumes? Come legumes?",
+ "audioKey": "b8-d1-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, purtroppo. Non mi piacciono tanto.",
+ "pt": "Não, infelizmente. Não gosto tanto.",
+ "audioKey": "b8-d1-l6"
+ },
+ {
+ "speaker": "italian",
+ "it": "Dai, devi provare! Sono buone!",
+ "pt": "Vai, tem que provar! São bons!",
+ "audioKey": "b8-d1-l7"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Sai dove è la stazione?",
+ "pt": "Sabe onde fica a estação?",
+ "audioKey": "b8-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Non lo so, mi dispiace.",
+ "pt": "Não sei, desculpe.",
+ "audioKey": "b8-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "È lontana da qui?",
+ "pt": "É longe daqui?",
+ "audioKey": "b8-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Penso di no. Forse è vicino.",
+ "pt": "Acho que não. Talvez seja perto.",
+ "audioKey": "b8-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Va bene, chiedo a qualcun altro. Grazie comunque!",
+ "pt": "Tudo bem, pergunto a outra pessoa. Obrigado de qualquer forma!",
+ "audioKey": "b8-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Prego! Mi dispiace non poter aiutare.",
+ "pt": "De nada! Desculpe não poder ajudar.",
+ "audioKey": "b8-d2-l6"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Incontro con un nuovo amico italiano",
+ "italianRole": "Coetaneo italiano che fa domande semplici",
+ "goalPt": "Responder perguntas pessoais simples usando sì/no/forse/non lo so",
+ "openerIt": "Ciao! Ti chiami Marco, vero?"
+ },
+ "survivalChecklist": [
+ "Sì / No / Forse",
+ "Non lo so",
+ "Sì, certo! / No, purtroppo",
+ "Penso di sì / Penso di no",
+ "Mah, non saprei"
+ ]
+ },
+ {
+ "id": "B9",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Dov'è? Qui e Là",
+ "titlePt": "Onde está? Aqui e lá — direções básicas",
+ "canDos": [
+ {
+ "id": "b9-cd1",
+ "text": "Consigo perguntar onde algo está com 'dov'è'"
+ },
+ {
+ "id": "b9-cd2",
+ "text": "Consigo indicar localização com 'qui', 'là', 'vicino', 'lontano'"
+ },
+ {
+ "id": "b9-cd3",
+ "text": "Consigo entender direções simples como 'a destra/sinistra'"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Dov'è il bagno?",
+ "pt": "Onde fica o banheiro?",
+ "note": "'dov'è' = dove + è (onde está)",
+ "audioKey": "b9-chunk-01"
+ },
+ {
+ "it": "Dov'è la stazione?",
+ "pt": "Onde fica a estação?",
+ "audioKey": "b9-chunk-02"
+ },
+ {
+ "it": "È qui.",
+ "pt": "Está aqui.",
+ "audioKey": "b9-chunk-03"
+ },
+ {
+ "it": "È là.",
+ "pt": "Está lá.",
+ "audioKey": "b9-chunk-04"
+ },
+ {
+ "it": "È vicino.",
+ "pt": "É perto / Está perto.",
+ "audioKey": "b9-chunk-05"
+ },
+ {
+ "it": "È lontano.",
+ "pt": "É longe / Está longe.",
+ "audioKey": "b9-chunk-06"
+ },
+ {
+ "it": "A destra.",
+ "pt": "À direita.",
+ "audioKey": "b9-chunk-07"
+ },
+ {
+ "it": "A sinistra.",
+ "pt": "À esquerda.",
+ "audioKey": "b9-chunk-08"
+ },
+ {
+ "it": "Sempre dritto.",
+ "pt": "Sempre em frente / reto.",
+ "audioKey": "b9-chunk-09"
+ },
+ {
+ "it": "Gira a destra.",
+ "pt": "Vire à direita.",
+ "note": "'girare' = virar; imperativo informal",
+ "audioKey": "b9-chunk-10"
+ },
+ {
+ "it": "È dall'altra parte.",
+ "pt": "É do outro lado.",
+ "audioKey": "b9-chunk-11"
+ },
+ {
+ "it": "Come si dice 'atrás' in italiano?",
+ "pt": "Como se diz 'atrás' em italiano?",
+ "note": "SALVAVITA para direções",
+ "audioKey": "b9-chunk-12"
+ },
+ {
+ "it": "È proprio qui accanto.",
+ "pt": "É bem aqui ao lado.",
+ "audioKey": "b9-chunk-13"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Scusi, dov'è il bagno?",
+ "pt": "Desculpe, onde fica o banheiro?",
+ "audioKey": "b9-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "È laggiù, a destra, vicino alla porta.",
+ "pt": "É ali embaixo, à direita, perto da porta.",
+ "audioKey": "b9-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "A destra? È lontano?",
+ "pt": "À direita? É longe?",
+ "audioKey": "b9-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "No, è vicinissimo! Due passi.",
+ "pt": "Não, é pertíssimo! Dois passos.",
+ "audioKey": "b9-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto, grazie mille!",
+ "pt": "Perfeito, muito obrigado!",
+ "audioKey": "b9-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Di niente! Vai dritto e poi gira.",
+ "pt": "De nada! Vá em frente e depois vire.",
+ "audioKey": "b9-d1-l6"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Dove abiti? Sei di qui?",
+ "pt": "Onde mora? É daqui?",
+ "audioKey": "b9-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, non sono di qui. Abito lontano.",
+ "pt": "Não, não sou daqui. Moro longe.",
+ "audioKey": "b9-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ah, e dove esattamente?",
+ "pt": "Ah, e onde exatamente?",
+ "audioKey": "b9-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Non lo so spiegare bene… è vicino al parco grande.",
+ "pt": "Não sei explicar bem… é perto do parque grande.",
+ "audioKey": "b9-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ah, ho capito! Vicino al Parco Sempione?",
+ "pt": "Ah, entendi! Perto do Parque Sempione?",
+ "audioKey": "b9-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, proprio lì! È qui accanto!",
+ "pt": "Sim, bem ali! É aqui ao lado!",
+ "audioKey": "b9-d2-l6"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Per strada in centro città",
+ "italianRole": "Passante gentile per strada",
+ "goalPt": "Perguntar e entender direções para encontrar um lugar",
+ "openerIt": "Scusi, mi può aiutare?"
+ },
+ "survivalChecklist": [
+ "Dov'è…?",
+ "È qui / È là",
+ "È vicino / È lontano",
+ "A destra / A sinistra",
+ "Sempre dritto"
+ ]
+ },
+ {
+ "id": "V1",
+ "trail": "viaggio",
+ "cefr": "A1",
+ "titleIt": "In Aeroporto e alla Dogana",
+ "titlePt": "No aeroporto e na alfândega",
+ "canDos": [
+ {
+ "id": "v1-cd1",
+ "text": "Consigo responder perguntas básicas na imigração sobre destino e motivo da viagem"
+ },
+ {
+ "id": "v1-cd2",
+ "text": "Consigo declarar bagagem e dizer que é só bagagem de mão"
+ },
+ {
+ "id": "v1-cd3",
+ "text": "Consigo entender instruções simples no controle de segurança"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Buongiorno, ho il passaporto.",
+ "pt": "Bom dia, tenho o passaporte.",
+ "audioKey": "v1-chunk-01"
+ },
+ {
+ "it": "Sono qui in vacanza.",
+ "pt": "Estou aqui de férias.",
+ "note": "'in vacanza' = de férias; 'per lavoro' = a trabalho",
+ "audioKey": "v1-chunk-02"
+ },
+ {
+ "it": "Resto due settimane.",
+ "pt": "Fico duas semanas.",
+ "note": "'restare' = ficar/permanecer",
+ "audioKey": "v1-chunk-03"
+ },
+ {
+ "it": "È solo bagaglio a mano.",
+ "pt": "É só bagagem de mão.",
+ "note": "Frase essencial no check-in e segurança",
+ "audioKey": "v1-chunk-04"
+ },
+ {
+ "it": "Ho una valigia da imbarcare.",
+ "pt": "Tenho uma mala para despachar.",
+ "audioKey": "v1-chunk-05"
+ },
+ {
+ "it": "Il mio volo è per Roma.",
+ "pt": "Meu voo é para Roma.",
+ "audioKey": "v1-chunk-06"
+ },
+ {
+ "it": "Dov'è il gate?",
+ "pt": "Onde fica o portão?",
+ "audioKey": "v1-chunk-07"
+ },
+ {
+ "it": "A che ora parte il volo?",
+ "pt": "A que horas sai o voo?",
+ "note": "'partire' = partir/sair (para transportes)",
+ "audioKey": "v1-chunk-08"
+ },
+ {
+ "it": "Non ho niente da dichiarare.",
+ "pt": "Não tenho nada a declarar.",
+ "audioKey": "v1-chunk-09"
+ },
+ {
+ "it": "Scusi, dove ritiro i bagagli?",
+ "pt": "Desculpe, onde retiro as bagagens?",
+ "audioKey": "v1-chunk-10"
+ },
+ {
+ "it": "Potrebbe ripetere, per favore?",
+ "pt": "Poderia repetir, por favor?",
+ "note": "Condizionale = mais cortês; SALVAVITA formal",
+ "audioKey": "v1-chunk-11"
+ },
+ {
+ "it": "Il nastro dei bagagli è laggiù.",
+ "pt": "A esteira das bagagens é ali embaixo.",
+ "audioKey": "v1-chunk-12"
+ },
+ {
+ "it": "Ho perso la coincidenza.",
+ "pt": "Perdi a conexão.",
+ "note": "'coincidenza' = conexão de voo",
+ "audioKey": "v1-chunk-13"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "italian",
+ "it": "Buongiorno. Passaporto, prego.",
+ "pt": "Bom dia. Passaporte, por favor.",
+ "audioKey": "v1-d1-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "Buongiorno, ecco il passaporto.",
+ "pt": "Bom dia, aqui está o passaporte.",
+ "audioKey": "v1-d1-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Qual è il motivo del viaggio?",
+ "pt": "Qual é o motivo da viagem?",
+ "audioKey": "v1-d1-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sono qui in vacanza. Resto dieci giorni.",
+ "pt": "Estou aqui de férias. Fico dez dias.",
+ "audioKey": "v1-d1-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Dove alloggia?",
+ "pt": "Onde se hospeda?",
+ "audioKey": "v1-d1-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "In un hotel a Roma centro. Ho la prenotazione.",
+ "pt": "Num hotel no centro de Roma. Tenho a reserva.",
+ "audioKey": "v1-d1-l6"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ha qualcosa da dichiarare?",
+ "pt": "Tem algo a declarar?",
+ "audioKey": "v1-d1-l7"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, non ho niente da dichiarare.",
+ "pt": "Não, não tenho nada a declarar.",
+ "audioKey": "v1-d1-l8"
+ },
+ {
+ "speaker": "italian",
+ "it": "Bene, buon soggiorno in Italia!",
+ "pt": "Bem, boa estadia na Itália!",
+ "audioKey": "v1-d1-l9"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie mille! Arrivederci!",
+ "pt": "Muito obrigado! Até logo!",
+ "audioKey": "v1-d1-l10"
+ }
+ ],
+ [
+ {
+ "speaker": "italian",
+ "it": "Signore, ha bagagli da imbarcare?",
+ "pt": "Senhor, tem bagagem para despachar?",
+ "audioKey": "v1-d2-l1"
+ },
+ {
+ "speaker": "learner",
+ "it": "No, è solo bagaglio a mano. Questa valigia piccola.",
+ "pt": "Não, é só bagagem de mão. Esta mala pequena.",
+ "audioKey": "v1-d2-l2"
+ },
+ {
+ "speaker": "italian",
+ "it": "Va bene. Il gate è il B12, l'imbarco inizia alle quattordici.",
+ "pt": "Tudo bem. O portão é o B12, o embarque começa às quatorze.",
+ "audioKey": "v1-d2-l3"
+ },
+ {
+ "speaker": "learner",
+ "it": "Scusi, può ripetere il numero del gate?",
+ "pt": "Desculpe, pode repetir o número do portão?",
+ "audioKey": "v1-d2-l4"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certo! B come Bologna, uno due. Gate B12.",
+ "pt": "Claro! B como Bolonha, um dois. Portão B12.",
+ "audioKey": "v1-d2-l5"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto, grazie! A che ora parte?",
+ "pt": "Perfeito, obrigado! A que horas sai?",
+ "audioKey": "v1-d2-l6"
+ },
+ {
+ "speaker": "italian",
+ "it": "Alle quattordici e trenta. Buon viaggio!",
+ "pt": "Às quatorze e trinta. Boa viagem!",
+ "audioKey": "v1-d2-l7"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Al controllo passaporti dell'aeroporto",
+ "italianRole": "Agente della dogana / Addetto al check-in",
+ "goalPt": "Passar pela imigração respondendo perguntas sobre motivo, duração e hospedagem",
+ "openerIt": "Buongiorno, passaporto prego. Qual è il motivo del viaggio?"
+ },
+ "survivalChecklist": [
+ "Sono qui in vacanza / per lavoro",
+ "È solo bagaglio a mano",
+ "Non ho niente da dichiarare",
+ "Dov'è il gate? / A che ora parte?",
+ "Potrebbe ripetere, per favore?"
+ ]
+ },
+ {
+ "id": "V2",
+ "trail": "viaggio",
+ "cefr": "A1",
+ "titleIt": "Taxi, Treno e Bus",
+ "titlePt": "Táxi, trem e ônibus",
+ "canDos": [
+ {
+ "id": "v2-cd1",
+ "text": "Consigo perguntar horários e comprar bilhetes de transporte"
+ },
+ {
+ "id": "v2-cd2",
+ "text": "Consigo pedir um táxi e dizer o destino"
+ },
+ {
+ "id": "v2-cd3",
+ "text": "Consigo entender informações sobre plataforma e paradas"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "A che ora parte il treno?",
+ "pt": "A que horas sai o trem?",
+ "audioKey": "v2-chunk-01"
+ },
+ {
+ "it": "Da quale binario parte?",
+ "pt": "De qual plataforma sai?",
+ "note": "'binario' = plataforma de trem",
+ "audioKey": "v2-chunk-02"
+ },
+ {
+ "it": "Un biglietto di sola andata.",
+ "pt": "Uma passagem só de ida.",
+ "audioKey": "v2-chunk-03"
+ },
+ {
+ "it": "Un biglietto di andata e ritorno.",
+ "pt": "Uma passagem de ida e volta.",
+ "audioKey": "v2-chunk-04"
+ },
+ {
+ "it": "Quanto costa il biglietto?",
+ "pt": "Quanto custa a passagem?",
+ "audioKey": "v2-chunk-05"
+ },
+ {
+ "it": "Dov'è la fermata del bus?",
+ "pt": "Onde fica a parada de ônibus?",
+ "audioKey": "v2-chunk-06"
+ },
+ {
+ "it": "Mi porti alla stazione, per favore.",
+ "pt": "Me leve à estação, por favor.",
+ "note": "Para táxi: 'mi porti a…'",
+ "audioKey": "v2-chunk-07"
+ },
+ {
+ "it": "Mi porti in Via Roma numero dieci.",
+ "pt": "Me leve à Rua Roma número dez.",
+ "audioKey": "v2-chunk-08"
+ },
+ {
+ "it": "Questo treno va a Firenze?",
+ "pt": "Este trem vai para Florença?",
+ "audioKey": "v2-chunk-09"
+ },
+ {
+ "it": "C'è un ritardo?",
+ "pt": "Há atraso?",
+ "note": "'ritardo' = atraso; 'in anticipo' = adiantado",
+ "audioKey": "v2-chunk-10"
+ },
+ {
+ "it": "Scusi, questo è il binario giusto?",
+ "pt": "Desculpe, esta é a plataforma certa?",
+ "note": "SALVAVITA para confirmar antes de embarcar",
+ "audioKey": "v2-chunk-11"
+ },
+ {
+ "it": "Il prossimo treno arriva tra dieci minuti.",
+ "pt": "O próximo trem chega em dez minutos.",
+ "audioKey": "v2-chunk-12"
+ },
+ {
+ "it": "Posso sedermi qui?",
+ "pt": "Posso me sentar aqui?",
+ "audioKey": "v2-chunk-13"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Buongiorno! Un biglietto per Firenze, per favore.",
+ "pt": "Bom dia! Uma passagem para Florença, por favor.",
+ "audioKey": "v2-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Di sola andata o andata e ritorno?",
+ "pt": "Só ida ou ida e volta?",
+ "audioKey": "v2-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Andata e ritorno. Quanto costa?",
+ "pt": "Ida e volta. Quanto custa?",
+ "audioKey": "v2-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Trentacinque euro. Il prossimo treno parte alle undici dal binario sette.",
+ "pt": "Trinta e cinco euros. O próximo trem sai às onze da plataforma sete.",
+ "audioKey": "v2-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Alle undici, binario sette. Grazie! A che ora arrivo?",
+ "pt": "Às onze, plataforma sete. Obrigado! A que horas chego?",
+ "audioKey": "v2-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Alle dodici e quaranta. Sono due ore circa.",
+ "pt": "Às doze e quarenta. São cerca de duas horas.",
+ "audioKey": "v2-d1-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto, grazie mille!",
+ "pt": "Perfeito, muito obrigado!",
+ "audioKey": "v2-d1-l7"
+ }
+ ],
+ [
+ {
+ "speaker": "learner",
+ "it": "Taxi! Mi porti all'Hotel Centrale, per favore.",
+ "pt": "Táxi! Me leve ao Hotel Central, por favor.",
+ "audioKey": "v2-d2-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certo! In Via Garibaldi? È vicino alla stazione.",
+ "pt": "Claro! Na Rua Garibaldi? É perto da estação.",
+ "audioKey": "v2-d2-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, esatto! Quanto ci vuole?",
+ "pt": "Sim, exato! Quanto tempo leva?",
+ "audioKey": "v2-d2-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Dieci minuti, se non c'è traffico.",
+ "pt": "Dez minutos, se não houver trânsito.",
+ "audioKey": "v2-d2-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Va bene. Quanto costa più o meno?",
+ "pt": "Tudo bem. Quanto custa mais ou menos?",
+ "audioKey": "v2-d2-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Circa quindici euro. Accetta la carta?",
+ "pt": "Cerca de quinze euros. Aceita cartão?",
+ "audioKey": "v2-d2-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Sì, pago con la carta. Grazie!",
+ "pt": "Sim, pago com cartão. Obrigado!",
+ "audioKey": "v2-d2-l7"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Alla biglietteria della stazione ferroviaria",
+ "italianRole": "Addetto alla biglietteria",
+ "goalPt": "Comprar uma passagem de trem perguntando horário, plataforma e preço",
+ "openerIt": "Buongiorno! Dove deve andare?"
+ },
+ "survivalChecklist": [
+ "A che ora parte? / Da quale binario?",
+ "Biglietto di sola andata / andata e ritorno",
+ "Mi porti a…, per favore (taxi)",
+ "Dov'è la fermata del bus?",
+ "C'è un ritardo?"
+ ]
+ },
+ {
+ "id": "V3",
+ "trail": "viaggio",
+ "cefr": "A2",
+ "titleIt": "In Hotel: Prenotazione e Reclami",
+ "titlePt": "No hotel: reserva e reclamações",
+ "canDos": [
+ {
+ "id": "v3-cd1",
+ "text": "Consigo fazer check-in informando que tenho reserva"
+ },
+ {
+ "id": "v3-cd2",
+ "text": "Consigo reclamar de problemas no quarto de forma educada"
+ },
+ {
+ "id": "v3-cd3",
+ "text": "Consigo pedir informações sobre serviços do hotel"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Buongiorno, ho una prenotazione a nome Rossi.",
+ "pt": "Bom dia, tenho uma reserva em nome de Rossi.",
+ "note": "'a nome di' = em nome de",
+ "audioKey": "v3-chunk-01"
+ },
+ {
+ "it": "Ho prenotato una camera doppia.",
+ "pt": "Reservei um quarto duplo.",
+ "note": "'camera singola/doppia' = quarto individual/duplo",
+ "audioKey": "v3-chunk-02"
+ },
+ {
+ "it": "Per tre notti, fino a venerdì.",
+ "pt": "Por três noites, até sexta-feira.",
+ "audioKey": "v3-chunk-03"
+ },
+ {
+ "it": "La colazione è inclusa?",
+ "pt": "O café da manhã está incluído?",
+ "audioKey": "v3-chunk-04"
+ },
+ {
+ "it": "A che ora è la colazione?",
+ "pt": "A que horas é o café da manhã?",
+ "audioKey": "v3-chunk-05"
+ },
+ {
+ "it": "Manca il sapone in bagno.",
+ "pt": "Falta sabonete no banheiro.",
+ "note": "'mancare' = faltar; essencial para reclamar",
+ "audioKey": "v3-chunk-06"
+ },
+ {
+ "it": "L'aria condizionata non funziona.",
+ "pt": "O ar condicionado não funciona.",
+ "audioKey": "v3-chunk-07"
+ },
+ {
+ "it": "La camera è troppo rumorosa.",
+ "pt": "O quarto é barulhento demais.",
+ "audioKey": "v3-chunk-08"
+ },
+ {
+ "it": "Potrei cambiare camera?",
+ "pt": "Poderia trocar de quarto?",
+ "note": "Condizionale = cortês para pedidos",
+ "audioKey": "v3-chunk-09"
+ },
+ {
+ "it": "C'è il Wi-Fi? Qual è la password?",
+ "pt": "Tem Wi-Fi? Qual é a senha?",
+ "audioKey": "v3-chunk-10"
+ },
+ {
+ "it": "Vorrei fare il check-out.",
+ "pt": "Gostaria de fazer o check-out.",
+ "audioKey": "v3-chunk-11"
+ },
+ {
+ "it": "Scusi, potrei avere un asciugamano in più?",
+ "pt": "Desculpe, poderia ter uma toalha extra?",
+ "note": "SALVAVITA + condizionale para pedir extras",
+ "audioKey": "v3-chunk-12"
+ },
+ {
+ "it": "Il riscaldamento non va.",
+ "pt": "O aquecimento não funciona.",
+ "note": "'riscaldamento' = aquecimento; 'non va' = não vai/funciona",
+ "audioKey": "v3-chunk-13"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Buonasera! Ho una prenotazione a nome Silva.",
+ "pt": "Boa noite! Tenho uma reserva em nome de Silva.",
+ "audioKey": "v3-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Sì, signor Silva! Camera doppia per quattro notti, giusto?",
+ "pt": "Sim, senhor Silva! Quarto duplo por quatro noites, certo?",
+ "audioKey": "v3-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Esatto! La colazione è inclusa?",
+ "pt": "Exato! O café da manhã está incluído?",
+ "audioKey": "v3-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Sì, dalle sette alle dieci nella sala al piano terra.",
+ "pt": "Sim, das sete às dez na sala do térreo.",
+ "audioKey": "v3-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto. C'è il Wi-Fi in camera?",
+ "pt": "Perfeito. Tem Wi-Fi no quarto?",
+ "audioKey": "v3-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certamente! La password è scritta sul biglietto nella stanza.",
+ "pt": "Claro! A senha está escrita no cartão no quarto.",
+ "audioKey": "v3-d1-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie mille! Dov'è l'ascensore?",
+ "pt": "Muito obrigado! Onde fica o elevador?",
+ "audioKey": "v3-d1-l7"
+ },
+ {
+ "speaker": "italian",
+ "it": "Proprio lì a destra. Buona permanenza!",
+ "pt": "Bem ali à direita. Boa estadia!",
+ "audioKey": "v3-d1-l8"
+ }
+ ],
+ [
+ {
+ "speaker": "learner",
+ "it": "Buongiorno, scusi. Ho un problema con la camera.",
+ "pt": "Bom dia, desculpe. Tenho um problema com o quarto.",
+ "audioKey": "v3-d2-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Mi dica, cosa succede?",
+ "pt": "Me diga, o que acontece?",
+ "audioKey": "v3-d2-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "L'aria condizionata non funziona e manca il sapone in bagno.",
+ "pt": "O ar condicionado não funciona e falta sabonete no banheiro.",
+ "audioKey": "v3-d2-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Mi dispiace molto! Mando subito qualcuno.",
+ "pt": "Sinto muito! Mando alguém agora.",
+ "audioKey": "v3-d2-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie. Potrei anche avere un asciugamano in più?",
+ "pt": "Obrigado. Poderia também ter uma toalha extra?",
+ "audioKey": "v3-d2-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certamente! Glielo porto io stesso tra cinque minuti.",
+ "pt": "Claro! Eu mesmo levo em cinco minutos.",
+ "audioKey": "v3-d2-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "La ringrazio molto! È stato molto gentile.",
+ "pt": "Muito obrigado! Foi muito gentil.",
+ "audioKey": "v3-d2-l7"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Alla reception dell'hotel",
+ "italianRole": "Receptionist dell'hotel",
+ "goalPt": "Fazer check-in e depois reclamar educadamente de um problema no quarto",
+ "openerIt": "Buonasera! Benvenuto all'Hotel Bella Italia!"
+ },
+ "survivalChecklist": [
+ "Ho una prenotazione a nome…",
+ "La colazione è inclusa? / A che ora?",
+ "Manca il sapone / L'aria condizionata non funziona",
+ "Potrei cambiare camera?",
+ "Vorrei fare il check-out"
+ ]
+ },
+ {
+ "id": "V4",
+ "trail": "viaggio",
+ "cefr": "A2",
+ "titleIt": "Al Ristorante",
+ "titlePt": "No restaurante",
+ "canDos": [
+ {
+ "id": "v4-cd1",
+ "text": "Consigo pedir recomendações ao garçom e fazer pedidos completos"
+ },
+ {
+ "id": "v4-cd2",
+ "text": "Consigo informar alergias alimentares de forma clara"
+ },
+ {
+ "id": "v4-cd3",
+ "text": "Consigo pedir a conta e perguntar sobre formas de pagamento"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Buonasera! Un tavolo per due, per favore.",
+ "pt": "Boa noite! Uma mesa para dois, por favor.",
+ "audioKey": "v4-chunk-01"
+ },
+ {
+ "it": "Avete un tavolo libero?",
+ "pt": "Têm uma mesa livre?",
+ "audioKey": "v4-chunk-02"
+ },
+ {
+ "it": "Cosa ci consiglia?",
+ "pt": "O que nos recomenda?",
+ "note": "'consigliare' = recomendar; essencial no restaurante",
+ "audioKey": "v4-chunk-03"
+ },
+ {
+ "it": "Qual è il piatto del giorno?",
+ "pt": "Qual é o prato do dia?",
+ "audioKey": "v4-chunk-04"
+ },
+ {
+ "it": "Sono allergico alle noci.",
+ "pt": "Sou alérgico a nozes.",
+ "note": "'allergico a' + alimento; feminino: 'allergica'",
+ "audioKey": "v4-chunk-05"
+ },
+ {
+ "it": "Questo piatto contiene glutine?",
+ "pt": "Este prato contém glúten?",
+ "audioKey": "v4-chunk-06"
+ },
+ {
+ "it": "Vorrei le lasagne come primo.",
+ "pt": "Gostaria da lasanha como primeiro prato.",
+ "note": "'primo' = entrada/massa; 'secondo' = prato principal",
+ "audioKey": "v4-chunk-07"
+ },
+ {
+ "it": "Come secondo prendo il pesce alla griglia.",
+ "pt": "Como segundo prato pego o peixe grelhado.",
+ "audioKey": "v4-chunk-08"
+ },
+ {
+ "it": "Da bere, una bottiglia d'acqua naturale.",
+ "pt": "Para beber, uma garrafa de água sem gás.",
+ "audioKey": "v4-chunk-09"
+ },
+ {
+ "it": "Il conto, per favore.",
+ "pt": "A conta, por favor.",
+ "audioKey": "v4-chunk-10"
+ },
+ {
+ "it": "Possiamo pagare con la carta?",
+ "pt": "Podemos pagar com cartão?",
+ "audioKey": "v4-chunk-11"
+ },
+ {
+ "it": "Scusi, cos'è questo ingrediente? Non lo conosco.",
+ "pt": "Desculpe, o que é este ingrediente? Não conheço.",
+ "note": "SALVAVITA + circunlóquio para ingredientes desconhecidos",
+ "audioKey": "v4-chunk-12"
+ },
+ {
+ "it": "Era buonissimo, complimenti allo chef!",
+ "pt": "Estava muito gostoso, cumprimentos ao chef!",
+ "audioKey": "v4-chunk-13"
+ },
+ {
+ "it": "Il servizio è incluso nel conto?",
+ "pt": "O serviço está incluído na conta?",
+ "note": "'coperto' = taxa de couvert italiana",
+ "audioKey": "v4-chunk-14"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Buonasera! Avete un tavolo libero per due?",
+ "pt": "Boa noite! Têm uma mesa livre para dois?",
+ "audioKey": "v4-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certamente! Prego, accomodatevi qui.",
+ "pt": "Claro! Por favor, sentem-se aqui.",
+ "audioKey": "v4-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie! Cosa ci consiglia stasera?",
+ "pt": "Obrigado! O que recomenda esta noite?",
+ "audioKey": "v4-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Il nostro piatto forte sono le tagliatelle al ragù. Sono fatte in casa!",
+ "pt": "Nosso carro-chefe são as tagliatelle ao molho bolonhesa. São caseiras!",
+ "audioKey": "v4-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto! Le prendo. Ma sono allergico ai funghi, attenzione per favore.",
+ "pt": "Perfeito! Eu pego. Mas sou alérgico a cogumelos, atenção por favor.",
+ "audioKey": "v4-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Tranquillo, nessun fungo! Da bere?",
+ "pt": "Fique tranquilo, nenhum cogumelo! Para beber?",
+ "audioKey": "v4-d1-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Una bottiglia d'acqua naturale e un bicchiere di vino rosso.",
+ "pt": "Uma garrafa de água sem gás e uma taça de vinho tinto.",
+ "audioKey": "v4-d1-l7"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ottimo! Vi porto subito il pane e l'acqua.",
+ "pt": "Ótimo! Trago agora o pão e a água.",
+ "audioKey": "v4-d1-l8"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie mille!",
+ "pt": "Muito obrigado!",
+ "audioKey": "v4-d1-l9"
+ }
+ ],
+ [
+ {
+ "speaker": "learner",
+ "it": "Scusi, possiamo avere il conto?",
+ "pt": "Desculpe, podemos ter a conta?",
+ "audioKey": "v4-d2-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certo! Sono quarantadue euro totali.",
+ "pt": "Claro! São quarenta e dois euros no total.",
+ "audioKey": "v4-d2-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Possiamo pagare con la carta?",
+ "pt": "Podemos pagar com cartão?",
+ "audioKey": "v4-d2-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Sì, accettiamo tutte le carte. Bancomat o credito?",
+ "pt": "Sim, aceitamos todos os cartões. Débito ou crédito?",
+ "audioKey": "v4-d2-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Carta di credito, per favore. Il servizio è incluso?",
+ "pt": "Cartão de crédito, por favor. O serviço está incluído?",
+ "audioKey": "v4-d2-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Sì, il coperto è incluso. Grazie e buona serata!",
+ "pt": "Sim, o couvert está incluído. Obrigado e boa noite!",
+ "audioKey": "v4-d2-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Era tutto buonissimo! Complimenti! Buonasera!",
+ "pt": "Tudo estava muito gostoso! Cumprimentos! Boa noite!",
+ "audioKey": "v4-d2-l7"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In una trattoria tipica romana",
+ "italianRole": "Cameriere cordiale e disponibile",
+ "goalPt": "Pedir recomendação, informar alergia, fazer pedido completo e pagar",
+ "openerIt": "Buonasera! Benvenuti! Avete già scelto?"
+ },
+ "survivalChecklist": [
+ "Un tavolo per…, per favore",
+ "Cosa ci consiglia?",
+ "Sono allergico/a a…",
+ "Il conto, per favore / Possiamo pagare con la carta?",
+ "Scusi, cos'è questo ingrediente?"
+ ]
+ },
+ {
+ "id": "V5",
+ "trail": "viaggio",
+ "cefr": "A2",
+ "titleIt": "Al Bar e in Paninoteca",
+ "titlePt": "No bar e na sanduicheria — circunlóquio aplicado",
+ "canDos": [
+ {
+ "id": "v5-cd1",
+ "text": "Consigo pedir café e lanches rápidos no balcão como um italiano"
+ },
+ {
+ "id": "v5-cd2",
+ "text": "Consigo escolher frios e queijos descrevendo o que não sei nomear"
+ },
+ {
+ "id": "v5-cd3",
+ "text": "Consigo usar circunlóquio eficazmente para ingredientes desconhecidos"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Un caffè, per favore.",
+ "pt": "Um café, por favor.",
+ "note": "Na Itália, 'caffè' = espresso; peça 'caffè americano' se quiser fraco",
+ "audioKey": "v5-chunk-01"
+ },
+ {
+ "it": "Un caffè al banco.",
+ "pt": "Um café no balcão.",
+ "note": "'al banco' = no balcão (mais barato que ao tavolo)",
+ "audioKey": "v5-chunk-02"
+ },
+ {
+ "it": "Un cappuccino e un cornetto.",
+ "pt": "Um cappuccino e um croissant.",
+ "note": "'cornetto' = croissant italiano; cappuccino só de manhã!",
+ "audioKey": "v5-chunk-03"
+ },
+ {
+ "it": "Con cosa è fatto questo panino?",
+ "pt": "Com que é feito este sanduíche?",
+ "audioKey": "v5-chunk-04"
+ },
+ {
+ "it": "Vorrei un panino con quel formaggio giallo e morbido.",
+ "pt": "Gostaria de um sanduíche com aquele queijo amarelo e macio.",
+ "note": "Circunlóquio: cor + textura para descrever queijo",
+ "audioKey": "v5-chunk-05"
+ },
+ {
+ "it": "Ah, la fontina! Ottima scelta!",
+ "pt": "Ah, a fontina! Ótima escolha!",
+ "audioKey": "v5-chunk-06"
+ },
+ {
+ "it": "E quell'affettato rosso e piccante?",
+ "pt": "E aquele embutido vermelho e apimentado?",
+ "note": "'affettato' = frios fatiados; circunlóquio para salame/piccante/etc.",
+ "audioKey": "v5-chunk-07"
+ },
+ {
+ "it": "Il salame piccante! Lo metto volentieri!",
+ "pt": "O salame apimentado! Coloco com prazer!",
+ "audioKey": "v5-chunk-08"
+ },
+ {
+ "it": "Scusi, cos'è quella cosa verde nel piatto?",
+ "pt": "Desculpe, o que é aquela coisa verde no prato?",
+ "note": "SALVAVITA + circunlóquio para acompanhamentos",
+ "audioKey": "v5-chunk-09"
+ },
+ {
+ "it": "Sono olive! Le vuole sul panino?",
+ "pt": "São azeitonas! Quer no sanduíche?",
+ "audioKey": "v5-chunk-10"
+ },
+ {
+ "it": "Quanto viene?",
+ "pt": "Quanto dá? (informal para preço)",
+ "note": "'quanto viene' = quanto fica/dá (coloquial)",
+ "audioKey": "v5-chunk-11"
+ },
+ {
+ "it": "Lo prendo da portare via.",
+ "pt": "Levo para viagem.",
+ "note": "'da portare via' = para levar/takeaway",
+ "audioKey": "v5-chunk-12"
+ },
+ {
+ "it": "Posso assaggiare prima di scegliere?",
+ "pt": "Posso provar antes de escolher?",
+ "audioKey": "v5-chunk-13"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Buongiorno! Un caffè al banco, per favore.",
+ "pt": "Bom dia! Um café no balcão, por favor.",
+ "audioKey": "v5-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Subito! Lo vuoi normale o macchiato?",
+ "pt": "Agora! Quer normal ou pingado?",
+ "audioKey": "v5-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Normale, grazie. E vorrei anche un cornetto.",
+ "pt": "Normal, obrigado. E gostaria também de um croissant.",
+ "audioKey": "v5-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Vuoto o alla crema?",
+ "pt": "Simples ou com creme?",
+ "audioKey": "v5-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Alla crema, per favore! Quant'è?",
+ "pt": "Com creme, por favor! Quanto é?",
+ "audioKey": "v5-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Due euro e ottanta. Ecco il caffè!",
+ "pt": "Dois euros e oitenta. Aqui está o café!",
+ "audioKey": "v5-d1-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie! Buonissimo come sempre!",
+ "pt": "Obrigado! Muito gostoso como sempre!",
+ "audioKey": "v5-d1-l7"
+ }
+ ],
+ [
+ {
+ "speaker": "learner",
+ "it": "Buongiorno! Vorrei un panino ma non so i nomi dei formaggi.",
+ "pt": "Bom dia! Gostaria de um sanduíche mas não sei os nomes dos queijos.",
+ "audioKey": "v5-d2-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Nessun problema! Dimmi che tipo ti piace.",
+ "pt": "Sem problema! Me diga que tipo gosta.",
+ "audioKey": "v5-d2-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Mi piace quello giallo, morbido e un po' dolce.",
+ "pt": "Gosto daquele amarelo, macio e um pouco doce.",
+ "audioKey": "v5-d2-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Intendi la fontina? O forse il taleggio?",
+ "pt": "Quer dizer a fontina? Ou talvez o taleggio?",
+ "audioKey": "v5-d2-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "La fontina! Sì! E poi quell'affettato… è rosso, sottile e un po' salato.",
+ "pt": "A fontina! Sim! E depois aquele embutido… é vermelho, fino e um pouco salgado.",
+ "audioKey": "v5-d2-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "Il prosciutto crudo! Perfetto insieme alla fontina!",
+ "pt": "O presunto cru! Perfeito junto com a fontina!",
+ "audioKey": "v5-d2-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Esatto! Lo prendo! Quanto viene?",
+ "pt": "Exato! Eu pego! Quanto dá?",
+ "audioKey": "v5-d2-l7"
+ },
+ {
+ "speaker": "italian",
+ "it": "Sei euro e cinquanta. Lo mangi qui o da portare via?",
+ "pt": "Seis euros e cinquenta. Come aqui ou para levar?",
+ "audioKey": "v5-d2-l8"
+ },
+ {
+ "speaker": "learner",
+ "it": "Da portare via, grazie! Complimenti per la pazienza!",
+ "pt": "Para levar, obrigado! Cumprimentos pela paciência!",
+ "audioKey": "v5-d2-l9"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In una paninoteca artigianale",
+ "italianRole": "Paninaro appassionato che spiega i prodotti",
+ "goalPt": "Montar um sanduíche descrevendo frios e queijos que não sabe nomear usando circunlóquio",
+ "openerIt": "Buongiorno! Che panino desideri oggi?"
+ },
+ "survivalChecklist": [
+ "Un caffè al banco / Un cappuccino e un cornetto",
+ "Vorrei un panino con quel [colore] [texture]",
+ "Cos'è quella cosa [descrizione]?",
+ "Quanto viene? / Da portare via",
+ "Con cosa è fatto questo?"
+ ]
+ },
+ {
+ "id": "V6",
+ "trail": "viaggio",
+ "cefr": "B1",
+ "titleIt": "In Farmacia e dal Medico",
+ "titlePt": "Na farmácia e no médico",
+ "canDos": [
+ {
+ "id": "v6-cd1",
+ "text": "Consigo descrever sintomas ao médico usando condizionale cortese"
+ },
+ {
+ "id": "v6-cd2",
+ "text": "Consigo informar alergias e pedir medicamentos específicos na farmácia"
+ },
+ {
+ "id": "v6-cd3",
+ "text": "Consigo entender instruções de dosagem e posologia"
+ }
+ ],
+ "chunks": [
+ {
+ "it": "Mi fa male la gola.",
+ "pt": "Minha garganta dói / Estou com dor de garganta.",
+ "audioKey": "v6-chunk-01"
+ },
+ {
+ "it": "Ho la febbre da due giorni.",
+ "pt": "Estou com febre há dois dias.",
+ "note": "'da' + tempo = há (duração)",
+ "audioKey": "v6-chunk-02"
+ },
+ {
+ "it": "Ho il raffreddore e il naso chiuso.",
+ "pt": "Estou resfriado e com o nariz entupido.",
+ "audioKey": "v6-chunk-03"
+ },
+ {
+ "it": "Sono allergico alla penicillina.",
+ "pt": "Sou alérgico a penicilina.",
+ "note": "CRÍTICO: sempre informe alergias; fem. 'allergica'",
+ "audioKey": "v6-chunk-04"
+ },
+ {
+ "it": "Vorrei qualcosa per il mal di testa.",
+ "pt": "Gostaria de algo para dor de cabeça.",
+ "note": "Condizionale 'vorrei' = cortês em contextos formais",
+ "audioKey": "v6-chunk-05"
+ },
+ {
+ "it": "Mi dia qualcosa per la tosse.",
+ "pt": "Me dê algo para a tosse.",
+ "note": "'mi dia' = imperativo formal (Lei)",
+ "audioKey": "v6-chunk-06"
+ },
+ {
+ "it": "Quante volte al giorno devo prenderlo?",
+ "pt": "Quantas vezes por dia devo tomar?",
+ "audioKey": "v6-chunk-07"
+ },
+ {
+ "it": "Va preso a stomaco pieno.",
+ "pt": "Deve ser tomado de estômago cheio.",
+ "note": "'a stomaco pieno/vuoto' = após/antes das refeições",
+ "audioKey": "v6-chunk-08"
+ },
+ {
+ "it": "Saprebbe dirmi dove trovare un medico?",
+ "pt": "Saberia me dizer onde encontrar um médico?",
+ "note": "Condizionale 'saprebbe' = extremamente cortês",
+ "audioKey": "v6-chunk-09"
+ },
+ {
+ "it": "Potrei avere una ricetta?",
+ "pt": "Poderia ter uma receita?",
+ "note": "'ricetta' = receita médica",
+ "audioKey": "v6-chunk-10"
+ },
+ {
+ "it": "Da quanto tempo ha questi sintomi?",
+ "pt": "Há quanto tempo tem estes sintomas?",
+ "note": "Pergunta típica do médico — saiba responder",
+ "audioKey": "v6-chunk-11"
+ },
+ {
+ "it": "Ho anche nausea e mal di stomaco.",
+ "pt": "Também tenho náusea e dor de estômago.",
+ "audioKey": "v6-chunk-12"
+ },
+ {
+ "it": "Scusi, come si dice 'tontura' in italiano?",
+ "pt": "Desculpe, como se diz 'tontura' em italiano?",
+ "note": "SALVAVITA para sintomas que não sabe nomear",
+ "audioKey": "v6-chunk-13"
+ },
+ {
+ "it": "È un farmaco da banco o serve la ricetta?",
+ "pt": "É um medicamento de venda livre ou precisa de receita?",
+ "note": "'da banco' = sem prescrição",
+ "audioKey": "v6-chunk-14"
+ },
+ {
+ "it": "Prenda due compresse al giorno dopo i pasti.",
+ "pt": "Tome dois comprimidos por dia após as refeições.",
+ "note": "'compresse' = comprimidos; 'pastiglie' = pastilhas",
+ "audioKey": "v6-chunk-15"
+ }
+ ],
+ "dialogues": [
+ [
+ {
+ "speaker": "learner",
+ "it": "Buongiorno, dottore. Non sto bene da tre giorni.",
+ "pt": "Bom dia, doutor. Não estou bem há três dias.",
+ "audioKey": "v6-d1-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Mi dica. Quali sintomi ha?",
+ "pt": "Me diga. Quais sintomas tem?",
+ "audioKey": "v6-d1-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Ho la febbre, mi fa male la gola e ho anche un po' di tosse.",
+ "pt": "Estou com febre, minha garganta dói e também tenho um pouco de tosse.",
+ "audioKey": "v6-d1-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Da quanto tempo ha la febbre? È alta?",
+ "pt": "Há quanto tempo tem febre? É alta?",
+ "audioKey": "v6-d1-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Da due giorni. Ho misurato trentotto e mezzo.",
+ "pt": "Há dois dias. Medi trinta e oito e meio.",
+ "audioKey": "v6-d1-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "È un'infezione virale. Le prescrivo un antinfiammatorio. Lo prenda due volte al giorno dopo i pasti.",
+ "pt": "É uma infecção viral. Te prescrevo um anti-inflamatório. Tome duas vezes ao dia após as refeições.",
+ "audioKey": "v6-d1-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Grazie, dottore. Sono allergico all'aspirina, va bene questo farmaco?",
+ "pt": "Obrigado, doutor. Sou alérgico a aspirina, este remédio é adequado?",
+ "audioKey": "v6-d1-l7"
+ },
+ {
+ "speaker": "italian",
+ "it": "Tranquillo, non contiene aspirina. Riposi e beva molta acqua!",
+ "pt": "Fique tranquilo, não contém aspirina. Descanse e beba muita água!",
+ "audioKey": "v6-d1-l8"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto, la ringrazio molto! Potrei avere la ricetta?",
+ "pt": "Perfeito, muito obrigado! Poderia ter a receita?",
+ "audioKey": "v6-d1-l9"
+ },
+ {
+ "speaker": "italian",
+ "it": "Ecco la ricetta! Buona guarigione!",
+ "pt": "Aqui está a receita! Boa recuperação!",
+ "audioKey": "v6-d1-l10"
+ }
+ ],
+ [
+ {
+ "speaker": "learner",
+ "it": "Buonasera! Vorrei qualcosa per il mal di testa.",
+ "pt": "Boa noite! Gostaria de algo para dor de cabeça.",
+ "audioKey": "v6-d2-l1"
+ },
+ {
+ "speaker": "italian",
+ "it": "Certo. Ha preferenze? Compresse o bustine?",
+ "pt": "Claro. Tem preferência? Comprimidos ou sachês?",
+ "audioKey": "v6-d2-l2"
+ },
+ {
+ "speaker": "learner",
+ "it": "Compresse, per favore. Ma scusi, ho anche un po' di nausea… c'è qualcosa che aiuta anche quello?",
+ "pt": "Comprimidos, por favor. Mas desculpe, também tenho um pouco de náusea… tem algo que ajude também nisso?",
+ "audioKey": "v6-d2-l3"
+ },
+ {
+ "speaker": "italian",
+ "it": "Sì, questo qui va bene per entrambi. Prenda una compressa ogni otto ore, a stomaco pieno.",
+ "pt": "Sim, este aqui serve para ambos. Tome um comprimido a cada oito horas, de estômago cheio.",
+ "audioKey": "v6-d2-l4"
+ },
+ {
+ "speaker": "learner",
+ "it": "Ogni otto ore, a stomaco pieno. Ho capito. Serve la ricetta?",
+ "pt": "A cada oito horas, de estômago cheio. Entendi. Precisa de receita?",
+ "audioKey": "v6-d2-l5"
+ },
+ {
+ "speaker": "italian",
+ "it": "No, è un farmaco da banco. Sono dodici euro.",
+ "pt": "Não, é medicamento de venda livre. São doze euros.",
+ "audioKey": "v6-d2-l6"
+ },
+ {
+ "speaker": "learner",
+ "it": "Perfetto, pago con la carta. Grazie mille e buona serata!",
+ "pt": "Perfeito, pago com cartão. Muito obrigado e boa noite!",
+ "audioKey": "v6-d2-l7"
+ }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Nello studio del medico di base",
+ "italianRole": "Medico di famiglia gentile e professionale",
+ "goalPt": "Descrever sintomas ao médico, informar alergias e entender a prescrição usando condizionale cortese",
+ "openerIt": "Buongiorno! Si accomodi. Mi dica, cosa non va?"
+ },
+ "survivalChecklist": [
+ "Mi fa male… / Ho la febbre / Ho il raffreddore",
+ "Sono allergico/a a…",
+ "Vorrei qualcosa per… / Mi dia qualcosa per…",
+ "Quante volte al giorno? / A stomaco pieno?",
+ "Saprebbe dirmi dove trovare un medico?"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/content/missions/B1.json b/content/missions/B1.json
new file mode 100644
index 0000000..5926325
--- /dev/null
+++ b/content/missions/B1.json
@@ -0,0 +1,56 @@
+{
+ "id": "B1",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Voglio / Non voglio",
+ "titlePt": "Quero / Não quero",
+ "canDos": [
+ { "id": "b1-cd1", "text": "Consigo expressar desejo básico com 'voglio' e apontar para objetos" },
+ { "id": "b1-cd2", "text": "Consigo recusar algo educadamente dizendo 'non voglio'" },
+ { "id": "b1-cd3", "text": "Consigo combinar 'voglio questo/quello' com gestos para me fazer entender" }
+ ],
+ "chunks": [
+ { "it": "Voglio questo.", "pt": "Quero isto.", "audioKey": "b1-chunk-01" },
+ { "it": "Voglio quello.", "pt": "Quero aquilo.", "audioKey": "b1-chunk-02" },
+ { "it": "Non voglio questo.", "pt": "Não quero isto.", "audioKey": "b1-chunk-03" },
+ { "it": "Non voglio, grazie.", "pt": "Não quero, obrigado.", "note": "Sempre acrescente 'grazie' ao recusar", "audioKey": "b1-chunk-04" },
+ { "it": "Voglio un gelato.", "pt": "Quero um sorvete.", "audioKey": "b1-chunk-05" },
+ { "it": "Voglio l'acqua.", "pt": "Quero a água.", "note": "Artigo definido quando o objeto é específico", "audioKey": "b1-chunk-06" },
+ { "it": "Non lo voglio.", "pt": "Não o quero.", "note": "'lo' substitui o objeto masculino já mencionado", "audioKey": "b1-chunk-07" },
+ { "it": "Sì, lo voglio!", "pt": "Sim, eu quero!", "audioKey": "b1-chunk-08" },
+ { "it": "No, non voglio.", "pt": "Não, não quero.", "audioKey": "b1-chunk-09" },
+ { "it": "Scusi, come si dice 'quero' in italiano?", "pt": "Desculpe, como se diz 'quero' em italiano?", "note": "SALVAVITA: peça a palavra que falta", "audioKey": "b1-chunk-10" },
+ { "it": "Vorrei questo, per favore.", "pt": "Gostaria disto, por favor.", "note": "'Vorrei' é mais cortês que 'voglio'", "audioKey": "b1-chunk-11" },
+ { "it": "Voglio anche quello.", "pt": "Quero também aquilo.", "audioKey": "b1-chunk-12" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "italian", "it": "Vuoi un biscotto?", "pt": "Quer um biscoito?", "audioKey": "b1-d1-l1" },
+ { "speaker": "learner", "it": "Sì, voglio un biscotto!", "pt": "Sim, quero um biscoito!", "audioKey": "b1-d1-l2" },
+ { "speaker": "italian", "it": "E vuoi anche il latte?", "pt": "E quer também o leite?", "audioKey": "b1-d1-l3" },
+ { "speaker": "learner", "it": "No, non voglio il latte. Voglio l'acqua.", "pt": "Não, não quero leite. Quero água.", "audioKey": "b1-d1-l4" },
+ { "speaker": "italian", "it": "Va bene! Ecco l'acqua.", "pt": "Tudo bem! Aqui está a água.", "audioKey": "b1-d1-l5" },
+ { "speaker": "learner", "it": "Grazie!", "pt": "Obrigado!", "audioKey": "b1-d1-l6" }
+ ],
+ [
+ { "speaker": "italian", "it": "Cosa vuoi? Questo o quello?", "pt": "O que você quer? Isto ou aquilo?", "audioKey": "b1-d2-l1" },
+ { "speaker": "learner", "it": "Voglio quello, per favore.", "pt": "Quero aquilo, por favor.", "audioKey": "b1-d2-l2" },
+ { "speaker": "italian", "it": "Sei sicuro? Questo è più buono.", "pt": "Tem certeza? Este é mais gostoso.", "audioKey": "b1-d2-l3" },
+ { "speaker": "learner", "it": "No, non voglio questo. Voglio quello!", "pt": "Não, não quero este. Quero aquele!", "audioKey": "b1-d2-l4" },
+ { "speaker": "italian", "it": "Dai, va bene! Prendi quello.", "pt": "Vai, tudo bem! Pegue aquele.", "audioKey": "b1-d2-l5" },
+ { "speaker": "learner", "it": "Grazie mille!", "pt": "Muito obrigado!", "audioKey": "b1-d2-l6" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In cucina con la mamma",
+ "italianRole": "Mamma che offre snack al bambino",
+ "goalPt": "Expressar o que quer e o que não quer entre duas opções de lanche",
+ "openerIt": "Tesoro, cosa vuoi mangiare?"
+ },
+ "survivalChecklist": [
+ "Voglio questo / quello",
+ "Non voglio, grazie",
+ "Sì, lo voglio",
+ "Scusi, come si dice…?"
+ ]
+}
diff --git a/content/missions/B10.json b/content/missions/B10.json
new file mode 100644
index 0000000..0eb91ee
--- /dev/null
+++ b/content/missions/B10.json
@@ -0,0 +1,59 @@
+{
+ "id": "B10",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Cortesia Italiana: Tu e Lei",
+ "titlePt": "Cortesia italiana: obrigado, desculpe, bom dia — tu vs Lei",
+ "canDos": [
+ { "id": "b10-cd1", "text": "Consigo usar saudações adequadas ao período do dia" },
+ { "id": "b10-cd2", "text": "Consigo agradecer, pedir licença e desculpas de forma natural" },
+ { "id": "b10-cd3", "text": "Consigo distinguir quando usar 'tu' e quando usar 'Lei'" }
+ ],
+ "chunks": [
+ { "it": "Buongiorno!", "pt": "Bom dia! (manhã/tarde cedo)", "note": "Use até ~17h; depois 'buonasera'", "audioKey": "b10-chunk-01" },
+ { "it": "Buonasera!", "pt": "Boa noite! (tarde/noite)", "audioKey": "b10-chunk-02" },
+ { "it": "Buonanotte!", "pt": "Boa noite! (ao se despedir para dormir)", "note": "Só na hora de ir dormir", "audioKey": "b10-chunk-03" },
+ { "it": "Grazie! / Grazie mille!", "pt": "Obrigado! / Muito obrigado!", "audioKey": "b10-chunk-04" },
+ { "it": "Prego!", "pt": "De nada! / Pois não!", "note": "'prego' também = 'pois não' ao oferecer algo", "audioKey": "b10-chunk-05" },
+ { "it": "Scusa! (informale)", "pt": "Desculpe! (com amigos/família)", "note": "Tu: scusa; Lei: scusi", "audioKey": "b10-chunk-06" },
+ { "it": "Mi scusi! (formale)", "pt": "Desculpe-me! (com estranhos/mais velhos)", "note": "Sempre use com desconhecidos", "audioKey": "b10-chunk-07" },
+ { "it": "Permesso!", "pt": "Com licença! (para passar)", "audioKey": "b10-chunk-08" },
+ { "it": "Per favore.", "pt": "Por favor.", "audioKey": "b10-chunk-09" },
+ { "it": "Arrivederci!", "pt": "Até logo! (formal/neutro)", "audioKey": "b10-chunk-10" },
+ { "it": "Ciao!", "pt": "Oi/Tchau! (informal)", "note": "Serve tanto para chegar quanto para sair", "audioKey": "b10-chunk-11" },
+ { "it": "Come si dice 'com licença' in italiano?", "pt": "Como se diz 'com licença' em italiano?", "note": "SALVAVITA de cortesia", "audioKey": "b10-chunk-12" },
+ { "it": "È stato un piacere!", "pt": "Foi um prazer!", "audioKey": "b10-chunk-13" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Buongiorno! Mi scusi, posso passare?", "pt": "Bom dia! Desculpe, posso passar?", "audioKey": "b10-d1-l1" },
+ { "speaker": "italian", "it": "Certamente! Prego, passi pure!", "pt": "Certamente! Pois não, passe!", "audioKey": "b10-d1-l2" },
+ { "speaker": "learner", "it": "Grazie mille! Permesso!", "pt": "Muito obrigado! Com licença!", "audioKey": "b10-d1-l3" },
+ { "speaker": "italian", "it": "Di niente! Buongiorno!", "pt": "De nada! Bom dia!", "audioKey": "b10-d1-l4" },
+ { "speaker": "learner", "it": "Arrivederci e buona giornata!", "pt": "Até logo e bom dia!", "audioKey": "b10-d1-l5" },
+ { "speaker": "italian", "it": "Altrettanto! Arrivederci!", "pt": "Igualmente! Até logo!", "audioKey": "b10-d1-l6" }
+ ],
+ [
+ { "speaker": "italian", "it": "Ciao! Come stai?", "pt": "Oi! Como está?", "audioKey": "b10-d2-l1" },
+ { "speaker": "learner", "it": "Ciao! Sto bene, grazie! E tu?", "pt": "Oi! Estou bem, obrigado! E você?", "note": "'tu' porque é amigo", "audioKey": "b10-d2-l2" },
+ { "speaker": "italian", "it": "Benissimo! Vuoi un caffè?", "pt": "Muito bem! Quer um café?", "audioKey": "b10-d2-l3" },
+ { "speaker": "learner", "it": "Sì, grazie! Per favore.", "pt": "Sim, obrigado! Por favor.", "audioKey": "b10-d2-l4" },
+ { "speaker": "italian", "it": "Ecco il caffè! Prego!", "pt": "Aqui o café! Pois não!", "audioKey": "b10-d2-l5" },
+ { "speaker": "learner", "it": "Grazie! Sei molto gentile!", "pt": "Obrigado! Você é muito gentil!", "audioKey": "b10-d2-l6" },
+ { "speaker": "italian", "it": "Ma figurati! Ciao!", "pt": "Imagina! Tchau!", "audioKey": "b10-d2-l7" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In un negozio del centro",
+ "italianRole": "Commesso/a del negozio (formale) e poi amico incontrato fuori (informale)",
+ "goalPt": "Praticar a diferença entre tratamento formal (Lei) e informal (tu) na mesma situação",
+ "openerIt": "Buongiorno, signore! Posso aiutarla?"
+ },
+ "survivalChecklist": [
+ "Buongiorno / Buonasera / Buonanotte",
+ "Grazie! / Prego!",
+ "Scusa (tu) / Mi scusi (Lei)",
+ "Permesso! / Per favore",
+ "Ciao! / Arrivederci!"
+ ]
+}
diff --git a/content/missions/B11.json b/content/missions/B11.json
new file mode 100644
index 0000000..a1239f1
--- /dev/null
+++ b/content/missions/B11.json
@@ -0,0 +1,59 @@
+{
+ "id": "B11",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Questo è… / Quello è… + Circunlóquio",
+ "titlePt": "Isto é… / Aquilo é… + descrever sem saber o nome",
+ "canDos": [
+ { "id": "b11-cd1", "text": "Consigo apresentar objetos com 'questo è' e 'quello è'" },
+ { "id": "b11-cd2", "text": "Consigo descrever algo que não sei nomear com 'è una cosa per…' e 'serve a…'" },
+ { "id": "b11-cd3", "text": "Consigo manter comunicação eficaz mesmo sem vocabulário específico" }
+ ],
+ "chunks": [
+ { "it": "Questo è un libro.", "pt": "Isto é um livro.", "audioKey": "b11-chunk-01" },
+ { "it": "Quello è un albero.", "pt": "Aquilo é uma árvore.", "note": "'quello' = aquele (longe); 'questo' = este (perto)", "audioKey": "b11-chunk-02" },
+ { "it": "Questa è una mela.", "pt": "Esta é uma maçã.", "note": "'questa' feminino; 'quella' feminino longe", "audioKey": "b11-chunk-03" },
+ { "it": "È una cosa per cucinare.", "pt": "É uma coisa para cozinhar.", "note": "Circunlóquio: função = 'per' + infinitivo", "audioKey": "b11-chunk-04" },
+ { "it": "Serve a scrivere sulla lavagna.", "pt": "Serve para escrever no quadro.", "note": "'servire a' + infinitivo", "audioKey": "b11-chunk-05" },
+ { "it": "È quella cosa lunga e nera.", "pt": "É aquela coisa comprida e preta.", "note": "Adjetivos ajudam muito no circunlóquio", "audioKey": "b11-chunk-06" },
+ { "it": "È fatto di legno.", "pt": "É feito de madeira.", "note": "'fatto di' + material", "audioKey": "b11-chunk-07" },
+ { "it": "Si usa in cucina.", "pt": "Se usa na cozinha.", "note": "'si usa' = se usa (impessoal)", "audioKey": "b11-chunk-08" },
+ { "it": "Assomiglia a un cucchiaio ma è più grande.", "pt": "Parece uma colher mas é maior.", "note": "'assomigliare a' = parecer com", "audioKey": "b11-chunk-09" },
+ { "it": "Non so il nome, ma è una cosa che serve per…", "pt": "Não sei o nome, mas é uma coisa que serve para…", "note": "Fórmula completa de circunlóquio", "audioKey": "b11-chunk-10" },
+ { "it": "Come si chiama questa cosa?", "pt": "Como se chama esta coisa?", "note": "SALVAVITA combinado com descrição", "audioKey": "b11-chunk-11" },
+ { "it": "Ah, è un mestolo! Grazie!", "pt": "Ah, é uma concha! Obrigado!", "audioKey": "b11-chunk-12" },
+ { "it": "È piccolo, rotondo e rosso.", "pt": "É pequeno, redondo e vermelho.", "note": "Encadeie adjetivos para precisão", "audioKey": "b11-chunk-13" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Guarda, questo è… non so il nome. È una cosa per aprire le bottiglie.", "pt": "Olha, isto é… não sei o nome. É uma coisa para abrir garrafas.", "audioKey": "b11-d1-l1" },
+ { "speaker": "italian", "it": "Ah, intendi il cavatappi?", "pt": "Ah, quer dizer o saca-rolhas?", "audioKey": "b11-d1-l2" },
+ { "speaker": "learner", "it": "Sì, il cavatappi! E quello là, cos'è?", "pt": "Sim, o saca-rolhas! E aquilo ali, o que é?", "audioKey": "b11-d1-l3" },
+ { "speaker": "italian", "it": "Quello è un tagliere. Serve a tagliare il formaggio.", "pt": "Aquilo é uma tábua de corte. Serve para cortar queijo.", "audioKey": "b11-d1-l4" },
+ { "speaker": "learner", "it": "Un tagliere! Ho capito. E questa cosa piccola e rotonda?", "pt": "Uma tábua de corte! Entendi. E esta coisa pequena e redonda?", "audioKey": "b11-d1-l5" },
+ { "speaker": "italian", "it": "È un tappo! Si usa per chiudere la bottiglia.", "pt": "É uma rolha/tampa! Se usa para fechar a garrafa.", "audioKey": "b11-d1-l6" },
+ { "speaker": "learner", "it": "Perfetto! Ora so tre parole nuove!", "pt": "Perfeito! Agora sei três palavras novas!", "audioKey": "b11-d1-l7" }
+ ],
+ [
+ { "speaker": "italian", "it": "Cos'hai nella borsa?", "pt": "O que tem na bolsa?", "audioKey": "b11-d2-l1" },
+ { "speaker": "learner", "it": "Ho questa cosa… è lunga, nera e serve a scrivere.", "pt": "Tenho esta coisa… é comprida, preta e serve para escrever.", "audioKey": "b11-d2-l2" },
+ { "speaker": "italian", "it": "Una penna?", "pt": "Uma caneta?", "audioKey": "b11-d2-l3" },
+ { "speaker": "learner", "it": "Sì, una penna! E poi ho quell'altra cosa, è fatta di stoffa e serve a pulire.", "pt": "Sim, uma caneta! E depois tenho aquela outra coisa, é feita de tecido e serve para limpar.", "audioKey": "b11-d2-l4" },
+ { "speaker": "italian", "it": "Un fazzoletto? O un tovagliolo?", "pt": "Um lenço? Ou um guardanapo?", "audioKey": "b11-d2-l5" },
+ { "speaker": "learner", "it": "Un fazzoletto! Esatto! Grazie, non mi veniva la parola.", "pt": "Um lenço! Exato! Obrigado, não me vinha a palavra.", "audioKey": "b11-d2-l6" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In cucina con un amico italiano",
+ "italianRole": "Amico che mostra utensili da cucina",
+ "goalPt": "Descrever utensílios cujo nome não sabe usando circunlóquio e aprender os nomes corretos",
+ "openerIt": "Vieni, ti mostro gli utensili della mia cucina!"
+ },
+ "survivalChecklist": [
+ "Questo è… / Quello è…",
+ "È una cosa per… / Serve a…",
+ "È fatto di… / Si usa in…",
+ "Non so il nome, ma…",
+ "Come si chiama questa cosa?"
+ ]
+}
diff --git a/content/missions/B12.json b/content/missions/B12.json
new file mode 100644
index 0000000..e6505e9
--- /dev/null
+++ b/content/missions/B12.json
@@ -0,0 +1,65 @@
+{
+ "id": "B12",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Mini-Conversazione al Bar",
+ "titlePt": "Mini-conversa: pedindo um lanche no bar",
+ "canDos": [
+ { "id": "b12-cd1", "text": "Consigo pedir um lanche combinando todas as estruturas da trilha bambino" },
+ { "id": "b12-cd2", "text": "Consigo usar circunlóquio quando não sei o nome de um ingrediente" },
+ { "id": "b12-cd3", "text": "Consigo manter uma mini-conversa completa num bar italiano" }
+ ],
+ "chunks": [
+ { "it": "Buongiorno! Vorrei un panino.", "pt": "Bom dia! Gostaria de um sanduíche.", "note": "'Vorrei' = condizionale, mais cortês que 'voglio'", "audioKey": "b12-chunk-01" },
+ { "it": "Un panino con prosciutto e formaggio.", "pt": "Um sanduíche com presunto e queijo.", "audioKey": "b12-chunk-02" },
+ { "it": "Scusi, come si chiama quello lì?", "pt": "Desculpe, como se chama aquilo ali?", "note": "Circunlóquio + apontar = estratégia vencedora", "audioKey": "b12-chunk-03" },
+ { "it": "È una cosa verde, per il panino.", "pt": "É uma coisa verde, para o sanduíche.", "note": "Descreva cor + função", "audioKey": "b12-chunk-04" },
+ { "it": "Ah, la rucola! Sì, la voglio!", "pt": "Ah, a rúcula! Sim, eu quero!", "audioKey": "b12-chunk-05" },
+ { "it": "Quanto costa?", "pt": "Quanto custa?", "audioKey": "b12-chunk-06" },
+ { "it": "Posso pagare con la carta?", "pt": "Posso pagar com cartão?", "audioKey": "b12-chunk-07" },
+ { "it": "Mi piace tantissimo!", "pt": "Gosto muitíssimo!", "audioKey": "b12-chunk-08" },
+ { "it": "È buonissimo, grazie!", "pt": "Está muito gostoso, obrigado!", "audioKey": "b12-chunk-09" },
+ { "it": "Non capisco bene, può ripetere?", "pt": "Não entendo bem, pode repetir?", "audioKey": "b12-chunk-10" },
+ { "it": "Ho anche sete. Un'acqua naturale, per favore.", "pt": "Também estou com sede. Uma água sem gás, por favor.", "note": "'naturale' = sem gás; 'frizzante/gassata' = com gás", "audioKey": "b12-chunk-11" },
+ { "it": "Grazie mille! Arrivederci!", "pt": "Muito obrigado! Até logo!", "audioKey": "b12-chunk-12" },
+ { "it": "È una cosa rossa e dolce, dentro il panino.", "pt": "É uma coisa vermelha e doce, dentro do sanduíche.", "note": "Circunlóquio para tomate/pimentão/etc.", "audioKey": "b12-chunk-13" },
+ { "it": "Il pomodoro! Certo, lo metto!", "pt": "O tomate! Claro, eu coloco!", "audioKey": "b12-chunk-14" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Buongiorno! Vorrei un panino, per favore.", "pt": "Bom dia! Gostaria de um sanduíche, por favor.", "audioKey": "b12-d1-l1" },
+ { "speaker": "italian", "it": "Certamente! Con cosa lo vuoi? Abbiamo prosciutto, salame, formaggio…", "pt": "Claro! Com que quer? Temos presunto, salame, queijo…", "audioKey": "b12-d1-l2" },
+ { "speaker": "learner", "it": "Con prosciutto e… scusi, come si chiama quella cosa verde?", "pt": "Com presunto e… desculpe, como se chama aquela coisa verde?", "audioKey": "b12-d1-l3" },
+ { "speaker": "italian", "it": "La rucola? È un'insalata, un po' amara ma buonissima!", "pt": "A rúcula? É uma salada, um pouco amarga mas muito gostosa!", "audioKey": "b12-d1-l4" },
+ { "speaker": "learner", "it": "Sì, la rucola! Mi piace! E anche quel formaggio bianco.", "pt": "Sim, a rúcula! Gosto! E também aquele queijo branco.", "audioKey": "b12-d1-l5" },
+ { "speaker": "italian", "it": "La mozzarella? Ottima scelta!", "pt": "A mussarela? Ótima escolha!", "audioKey": "b12-d1-l6" },
+ { "speaker": "learner", "it": "Perfetto! Quanto costa?", "pt": "Perfeito! Quanto custa?", "audioKey": "b12-d1-l7" },
+ { "speaker": "italian", "it": "Sette euro. Vuoi anche da bere?", "pt": "Sete euros. Quer também algo para beber?", "audioKey": "b12-d1-l8" },
+ { "speaker": "learner", "it": "Sì, un'acqua naturale, per favore. Posso pagare con la carta?", "pt": "Sim, uma água sem gás, por favor. Posso pagar com cartão?", "audioKey": "b12-d1-l9" },
+ { "speaker": "italian", "it": "Certo! Ecco il tuo panino. Buon appetito!", "pt": "Claro! Aqui está seu sanduíche. Bom apetite!", "audioKey": "b12-d1-l10" }
+ ],
+ [
+ { "speaker": "italian", "it": "Ciao! Cosa prendi oggi?", "pt": "Oi! O que vai pegar hoje?", "audioKey": "b12-d2-l1" },
+ { "speaker": "learner", "it": "Vorrei qualcosa di caldo. Ho fame e ho freddo!", "pt": "Gostaria de algo quente. Estou com fome e com frio!", "audioKey": "b12-d2-l2" },
+ { "speaker": "italian", "it": "Allora ti consiglio una zuppa! È buonissima!", "pt": "Então te recomendo uma sopa! É muito gostosa!", "audioKey": "b12-d2-l3" },
+ { "speaker": "learner", "it": "Una zuppa? Di cosa è fatta?", "pt": "Uma sopa? De que é feita?", "audioKey": "b12-d2-l4" },
+ { "speaker": "italian", "it": "Di verdure, pomodoro e un po' di pasta.", "pt": "De legumes, tomate e um pouco de massa.", "audioKey": "b12-d2-l5" },
+ { "speaker": "learner", "it": "Mi piace! La prendo! E un caffè dopo, per favore.", "pt": "Gosto! Eu pego! E um café depois, por favor.", "audioKey": "b12-d2-l6" },
+ { "speaker": "italian", "it": "Volentieri! Ti porto subito la zuppa!", "pt": "Com prazer! Te trago a sopa agora!", "audioKey": "b12-d2-l7" },
+ { "speaker": "learner", "it": "Grazie! Non vedo l'ora!", "pt": "Obrigado! Mal posso esperar!", "audioKey": "b12-d2-l8" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Al bar della piazza",
+ "italianRole": "Barista simpatico e paziente",
+ "goalPt": "Pedir um lanche completo usando tutte le strutture apprese: vorrei, circunlóquio, quanto costa, cortesia",
+ "openerIt": "Buongiorno! Cosa desidera?"
+ },
+ "survivalChecklist": [
+ "Vorrei un panino con…",
+ "Scusi, come si chiama quello lì?",
+ "È una cosa [colore] per…",
+ "Quanto costa? / Posso pagare con la carta?",
+ "Grazie mille! Buon appetito!"
+ ]
+}
diff --git a/content/missions/B2.json b/content/missions/B2.json
new file mode 100644
index 0000000..1bd9f73
--- /dev/null
+++ b/content/missions/B2.json
@@ -0,0 +1,58 @@
+{
+ "id": "B2",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Ho bisogno / Mi serve",
+ "titlePt": "Preciso de / Me faz falta",
+ "canDos": [
+ { "id": "b2-cd1", "text": "Consigo dizer que preciso de algo com 'ho bisogno di'" },
+ { "id": "b2-cd2", "text": "Consigo usar 'mi serve' para pedir objetos do dia a dia" },
+ { "id": "b2-cd3", "text": "Consigo explicar por que preciso usando gestos e palavras simples" }
+ ],
+ "chunks": [
+ { "it": "Ho bisogno di aiuto.", "pt": "Preciso de ajuda.", "audioKey": "b2-chunk-01" },
+ { "it": "Ho bisogno dell'acqua.", "pt": "Preciso de água.", "note": "'di' + artigo = 'dell'' antes de vogal", "audioKey": "b2-chunk-02" },
+ { "it": "Mi serve una penna.", "pt": "Me faz falta uma caneta / Preciso de uma caneta.", "audioKey": "b2-chunk-03" },
+ { "it": "Mi serve il bagno.", "pt": "Preciso do banheiro.", "note": "Frase essencial em qualquer situação", "audioKey": "b2-chunk-04" },
+ { "it": "Non ho bisogno di niente.", "pt": "Não preciso de nada.", "audioKey": "b2-chunk-05" },
+ { "it": "Ho bisogno di mangiare.", "pt": "Preciso comer.", "note": "'di' + infinitivo para ações", "audioKey": "b2-chunk-06" },
+ { "it": "Mi servono le scarpe.", "pt": "Preciso dos sapatos.", "note": "'servono' plural quando o objeto é plural", "audioKey": "b2-chunk-07" },
+ { "it": "Scusi, mi serve un telefono.", "pt": "Desculpe, preciso de um telefone.", "audioKey": "b2-chunk-08" },
+ { "it": "Ho bisogno di dormire.", "pt": "Preciso dormir.", "audioKey": "b2-chunk-09" },
+ { "it": "Come si dice 'preciso' in italiano?", "pt": "Como se diz 'preciso' em italiano?", "note": "SALVAVITA: peça a palavra", "audioKey": "b2-chunk-10" },
+ { "it": "Mi serve anche questo.", "pt": "Também preciso disto.", "audioKey": "b2-chunk-11" },
+ { "it": "Ho proprio bisogno di te.", "pt": "Realmente preciso de você.", "note": "'proprio' intensifica", "audioKey": "b2-chunk-12" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Mamma, ho bisogno di aiuto!", "pt": "Mamãe, preciso de ajuda!", "audioKey": "b2-d1-l1" },
+ { "speaker": "italian", "it": "Cosa ti serve, tesoro?", "pt": "O que você precisa, querido?", "audioKey": "b2-d1-l2" },
+ { "speaker": "learner", "it": "Mi serve una matita.", "pt": "Preciso de um lápis.", "audioKey": "b2-d1-l3" },
+ { "speaker": "italian", "it": "Ecco! Ti serve altro?", "pt": "Aqui! Precisa de mais alguma coisa?", "audioKey": "b2-d1-l4" },
+ { "speaker": "learner", "it": "Sì, ho bisogno anche della gomma.", "pt": "Sim, também preciso da borracha.", "audioKey": "b2-d1-l5" },
+ { "speaker": "italian", "it": "Dai, prendi. Hai bisogno di nient'altro?", "pt": "Vai, pegue. Precisa de mais nada?", "audioKey": "b2-d1-l6" },
+ { "speaker": "learner", "it": "No, grazie! Non ho bisogno di niente.", "pt": "Não, obrigado! Não preciso de nada.", "audioKey": "b2-d1-l7" }
+ ],
+ [
+ { "speaker": "italian", "it": "Stai bene? Hai bisogno di qualcosa?", "pt": "Está bem? Precisa de alguma coisa?", "audioKey": "b2-d2-l1" },
+ { "speaker": "learner", "it": "Sì, mi serve il bagno, per favore.", "pt": "Sim, preciso do banheiro, por favor.", "audioKey": "b2-d2-l2" },
+ { "speaker": "italian", "it": "Certo! È laggiù, a destra.", "pt": "Claro! É ali embaixo, à direita.", "audioKey": "b2-d2-l3" },
+ { "speaker": "learner", "it": "Grazie! Ho anche bisogno di lavarmi le mani.", "pt": "Obrigado! Também preciso lavar as mãos.", "audioKey": "b2-d2-l4" },
+ { "speaker": "italian", "it": "Va bene, c'è il sapone lì.", "pt": "Tudo bem, há sabonete ali.", "audioKey": "b2-d2-l5" },
+ { "speaker": "learner", "it": "Perfetto, grazie mille!", "pt": "Perfeito, muito obrigado!", "audioKey": "b2-d2-l6" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "A scuola con l'insegnante",
+ "italianRole": "Maestra che chiede cosa serve all'alunno",
+ "goalPt": "Pedir materiais escolares usando 'ho bisogno di' e 'mi serve'",
+ "openerIt": "Bambini, cosa vi serve per la lezione?"
+ },
+ "survivalChecklist": [
+ "Ho bisogno di…",
+ "Mi serve…",
+ "Mi serve il bagno",
+ "Non ho bisogno di niente",
+ "Come si dice 'preciso'?"
+ ]
+}
diff --git a/content/missions/B3.json b/content/missions/B3.json
new file mode 100644
index 0000000..18a48a8
--- /dev/null
+++ b/content/missions/B3.json
@@ -0,0 +1,59 @@
+{
+ "id": "B3",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Come si dice…?",
+ "titlePt": "Como se diz…? (O Salvavidas)",
+ "canDos": [
+ { "id": "b3-cd1", "text": "Consigo pedir a palavra que não sei com 'come si dice'" },
+ { "id": "b3-cd2", "text": "Consigo descrever algo que não sei nomear usando gestos e 'è una cosa per…'" },
+ { "id": "b3-cd3", "text": "Consigo manter a conversa mesmo sem saber todas as palavras" }
+ ],
+ "chunks": [
+ { "it": "Scusi, come si dice questo in italiano?", "pt": "Desculpe, como se diz isto em italiano?", "note": "SALVAVITA PRINCIPAL — use sempre!", "audioKey": "b3-chunk-01" },
+ { "it": "Come si chiama questa cosa?", "pt": "Como se chama esta coisa?", "audioKey": "b3-chunk-02" },
+ { "it": "Non so la parola.", "pt": "Não sei a palavra.", "audioKey": "b3-chunk-03" },
+ { "it": "È una cosa per scrivere.", "pt": "É uma coisa para escrever.", "note": "Circunlóquio: 'per' + infinitivo descreve função", "audioKey": "b3-chunk-04" },
+ { "it": "Serve a tagliare.", "pt": "Serve para cortar.", "note": "'servire a' + infinitivo = serve para", "audioKey": "b3-chunk-05" },
+ { "it": "Quella cosa lì, come si dice?", "pt": "Aquela coisa ali, como se diz?", "audioKey": "b3-chunk-06" },
+ { "it": "Non mi viene la parola.", "pt": "Não me vem a palavra.", "note": "Expressão idiomática natural", "audioKey": "b3-chunk-07" },
+ { "it": "Ah, sì! È un cucchiaio!", "pt": "Ah, sim! É uma colher!", "audioKey": "b3-chunk-08" },
+ { "it": "Grazie, non lo sapevo.", "pt": "Obrigado, não sabia.", "audioKey": "b3-chunk-09" },
+ { "it": "Come si dice 'garfo' in italiano?", "pt": "Como se diz 'garfo' em italiano?", "audioKey": "b3-chunk-10" },
+ { "it": "È quella cosa rotonda per bere.", "pt": "É aquela coisa redonda para beber.", "note": "Combine adjetivo + 'per' para circunlóquio rico", "audioKey": "b3-chunk-11" },
+ { "it": "Mi può aiutare con la parola?", "pt": "Pode me ajudar com a palavra?", "note": "Use Lei (formal) com desconhecidos", "audioKey": "b3-chunk-12" },
+ { "it": "Ho dimenticato come si dice.", "pt": "Esqueci como se diz.", "audioKey": "b3-chunk-13" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Scusi, come si dice questa cosa?", "pt": "Desculpe, como se diz esta coisa?", "audioKey": "b3-d1-l1" },
+ { "speaker": "italian", "it": "Quale cosa? Questa qui?", "pt": "Qual coisa? Esta aqui?", "audioKey": "b3-d1-l2" },
+ { "speaker": "learner", "it": "Sì! È una cosa per aprire la bottiglia.", "pt": "Sim! É uma coisa para abrir a garrafa.", "audioKey": "b3-d1-l3" },
+ { "speaker": "italian", "it": "Ah, è un cavatappi!", "pt": "Ah, é um saca-rolhas!", "audioKey": "b3-d1-l4" },
+ { "speaker": "learner", "it": "Un cavatappi! Grazie, non lo sapevo.", "pt": "Um saca-rolhas! Obrigado, não sabia.", "audioKey": "b3-d1-l5" },
+ { "speaker": "italian", "it": "Di niente! Chiedi pure se non sai una parola.", "pt": "De nada! Pergunte à vontade se não souber uma palavra.", "audioKey": "b3-d1-l6" },
+ { "speaker": "learner", "it": "Perfetto, grazie mille!", "pt": "Perfeito, muito obrigado!", "audioKey": "b3-d1-l7" }
+ ],
+ [
+ { "speaker": "italian", "it": "Cosa stai cercando?", "pt": "O que está procurando?", "audioKey": "b3-d2-l1" },
+ { "speaker": "learner", "it": "Non mi viene la parola… è quella cosa per asciugare i piatti.", "pt": "Não me vem a palavra… é aquela coisa para secar os pratos.", "audioKey": "b3-d2-l2" },
+ { "speaker": "italian", "it": "Intendi lo strofinaccio?", "pt": "Quer dizer o pano de prato?", "audioKey": "b3-d2-l3" },
+ { "speaker": "learner", "it": "Sì, sì! Lo strofinaccio! Come si dice in portoghese?", "pt": "Sim, sim! O pano de prato! Como se diz em português?", "audioKey": "b3-d2-l4" },
+ { "speaker": "italian", "it": "Magari non lo so, ma ora hai la parola italiana!", "pt": "Talvez eu não saiba, mas agora você tem a palavra italiana!", "audioKey": "b3-d2-l5" },
+ { "speaker": "learner", "it": "Dai, grazie! Ora me la ricordo.", "pt": "Vai, obrigado! Agora vou lembrar.", "audioKey": "b3-d2-l6" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Al mercato rionale",
+ "italianRole": "Venditore del banco della frutta",
+ "goalPt": "Pedir frutas cujo nome você não sabe, descrevendo-as ou perguntando 'come si dice'",
+ "openerIt": "Buongiorno! Cosa desidera oggi?"
+ },
+ "survivalChecklist": [
+ "Scusi, come si dice…?",
+ "Come si chiama questa cosa?",
+ "È una cosa per… / Serve a…",
+ "Non so la parola",
+ "Non mi viene la parola"
+ ]
+}
diff --git a/content/missions/B4.json b/content/missions/B4.json
new file mode 100644
index 0000000..8bdeb2e
--- /dev/null
+++ b/content/missions/B4.json
@@ -0,0 +1,57 @@
+{
+ "id": "B4",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Non capisco / Può ripetere?",
+ "titlePt": "Não entendo / Pode repetir?",
+ "canDos": [
+ { "id": "b4-cd1", "text": "Consigo sinalizar que não entendi sem travar a conversa" },
+ { "id": "b4-cd2", "text": "Consigo pedir para falar mais devagar com 'più lentamente'" },
+ { "id": "b4-cd3", "text": "Consigo confirmar o que entendi repetindo com minhas palavras" }
+ ],
+ "chunks": [
+ { "it": "Non capisco.", "pt": "Não entendo.", "audioKey": "b4-chunk-01" },
+ { "it": "Non ho capito.", "pt": "Não entendi.", "note": "Passado: quando já foi dito e você perdeu", "audioKey": "b4-chunk-02" },
+ { "it": "Può ripetere, per favore?", "pt": "Pode repetir, por favor?", "note": "Use Lei (formal) com estranhos", "audioKey": "b4-chunk-03" },
+ { "it": "Può parlare più lentamente?", "pt": "Pode falar mais devagar?", "audioKey": "b4-chunk-04" },
+ { "it": "Un po' più piano, per favore.", "pt": "Um pouco mais devagar, por favor.", "note": "'piano' = devagar/baixo; 'forte' = alto/rápido", "audioKey": "b4-chunk-05" },
+ { "it": "Cosa significa?", "pt": "O que significa?", "audioKey": "b4-chunk-06" },
+ { "it": "Non capisco bene.", "pt": "Não entendo bem.", "audioKey": "b4-chunk-07" },
+ { "it": "Ah, ora capisco!", "pt": "Ah, agora entendo!", "audioKey": "b4-chunk-08" },
+ { "it": "Ho capito! Vuol dire…", "pt": "Entendi! Quer dizer…", "note": "Confirme reformulando", "audioKey": "b4-chunk-09" },
+ { "it": "Scusi, non ho sentito.", "pt": "Desculpe, não ouvi.", "audioKey": "b4-chunk-10" },
+ { "it": "Può spiegare in modo semplice?", "pt": "Pode explicar de forma simples?", "audioKey": "b4-chunk-11" },
+ { "it": "Come si dice 'non entendo' in italiano?", "pt": "Como se diz 'não entendo' em italiano?", "note": "SALVAVITA: até para dizer que não entende!", "audioKey": "b4-chunk-12" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "italian", "it": "Allora, domani andiamo alla stazione alle otto e prendiamo il treno per Firenze.", "pt": "Então, amanhã vamos à estação às oito e pegamos o trem para Florença.", "audioKey": "b4-d1-l1" },
+ { "speaker": "learner", "it": "Scusi, non ho capito. Può ripetere più lentamente?", "pt": "Desculpe, não entendi. Pode repetir mais devagar?", "audioKey": "b4-d1-l2" },
+ { "speaker": "italian", "it": "Certo! Domani… alle otto… andiamo alla stazione.", "pt": "Claro! Amanhã… às oito… vamos à estação.", "audioKey": "b4-d1-l3" },
+ { "speaker": "learner", "it": "Ah, ora capisco! Alle otto, giusto?", "pt": "Ah, agora entendo! Às oito, certo?", "audioKey": "b4-d1-l4" },
+ { "speaker": "italian", "it": "Esatto! Bravo!", "pt": "Exato! Muito bem!", "audioKey": "b4-d1-l5" },
+ { "speaker": "learner", "it": "Grazie! Quando parli piano, capisco meglio.", "pt": "Obrigado! Quando fala devagar, entendo melhor.", "audioKey": "b4-d1-l6" }
+ ],
+ [
+ { "speaker": "italian", "it": "Il museo chiude alle diciassette, ma sabato resta aperto fino alle venti.", "pt": "O museu fecha às dezessete, mas sábado fica aberto até as vinte.", "audioKey": "b4-d2-l1" },
+ { "speaker": "learner", "it": "Cosa significa 'resta aperto'?", "pt": "O que significa 'resta aperto'?", "audioKey": "b4-d2-l2" },
+ { "speaker": "italian", "it": "Vuol dire che rimane aperto, non chiude.", "pt": "Quer dizer que permanece aberto, não fecha.", "audioKey": "b4-d2-l3" },
+ { "speaker": "learner", "it": "Ho capito! Quindi sabato posso andare anche la sera?", "pt": "Entendi! Então sábado posso ir também à noite?", "audioKey": "b4-d2-l4" },
+ { "speaker": "italian", "it": "Proprio così! Magari è meno affollato.", "pt": "Isso mesmo! Talvez esteja menos lotado.", "audioKey": "b4-d2-l5" },
+ { "speaker": "learner", "it": "Perfetto, grazie della spiegazione!", "pt": "Perfeito, obrigado pela explicação!", "audioKey": "b4-d2-l6" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "All'ufficio informazioni turistiche",
+ "italianRole": "Addetto all'informazione turistica",
+ "goalPt": "Pedir informações sobre horários e entender a resposta pedindo para repetir",
+ "openerIt": "Buongiorno, posso aiutarla?"
+ },
+ "survivalChecklist": [
+ "Non capisco / Non ho capito",
+ "Può ripetere, per favore?",
+ "Può parlare più lentamente?",
+ "Cosa significa?",
+ "Ah, ora capisco!"
+ ]
+}
diff --git a/content/missions/B5.json b/content/missions/B5.json
new file mode 100644
index 0000000..fe54ff3
--- /dev/null
+++ b/content/missions/B5.json
@@ -0,0 +1,59 @@
+{
+ "id": "B5",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Gusto / Non gusto",
+ "titlePt": "Gosto / Não gosto + adjetivos sensoriais",
+ "canDos": [
+ { "id": "b5-cd1", "text": "Consigo expressar preferência com 'mi piace / non mi piace'" },
+ { "id": "b5-cd2", "text": "Consigo descrever comida com adjetivos básicos (buono, dolce, freddo)" },
+ { "id": "b5-cd3", "text": "Consigo dizer que algo está gostoso ou ruim mesmo sem vocabulário rico" }
+ ],
+ "chunks": [
+ { "it": "Mi piace questo!", "pt": "Gosto disto!", "note": "'piacere' funciona ao contrário: a coisa agrada a mim", "audioKey": "b5-chunk-01" },
+ { "it": "Non mi piace.", "pt": "Não gosto.", "audioKey": "b5-chunk-02" },
+ { "it": "Questo è buono!", "pt": "Isto é bom/gostoso!", "audioKey": "b5-chunk-03" },
+ { "it": "È buonissimo!", "pt": "É muito gostoso!", "note": "Superlativo: -issimo = muito", "audioKey": "b5-chunk-04" },
+ { "it": "È troppo dolce.", "pt": "É doce demais.", "note": "'troppo' = demais/muito", "audioKey": "b5-chunk-05" },
+ { "it": "È freddo.", "pt": "Está frio.", "audioKey": "b5-chunk-06" },
+ { "it": "È caldo.", "pt": "Está quente.", "audioKey": "b5-chunk-07" },
+ { "it": "Non è buono.", "pt": "Não é bom.", "audioKey": "b5-chunk-08" },
+ { "it": "Che buono! Mi piace tantissimo!", "pt": "Que gostoso! Gosto muitíssimo!", "audioKey": "b5-chunk-09" },
+ { "it": "È un po' amaro.", "pt": "É um pouco amargo.", "note": "'un po'' = um pouco", "audioKey": "b5-chunk-10" },
+ { "it": "Come si dice 'picante' in italiano?", "pt": "Como se diz 'apimentado' em italiano?", "note": "SALVAVITA para sabores desconhecidos", "audioKey": "b5-chunk-11" },
+ { "it": "Preferisco quello salato.", "pt": "Prefiro aquele salgado.", "audioKey": "b5-chunk-12" },
+ { "it": "È una cosa dolce o salata?", "pt": "É uma coisa doce ou salgada?", "note": "Circunlóquio para perguntar sobre sabor", "audioKey": "b5-chunk-13" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "italian", "it": "Assaggia! Ti piace la pizza?", "pt": "Prove! Gosta de pizza?", "audioKey": "b5-d1-l1" },
+ { "speaker": "learner", "it": "Sì, mi piace tantissimo! È buonissima!", "pt": "Sim, gosto muitíssimo! É muito gostosa!", "audioKey": "b5-d1-l2" },
+ { "speaker": "italian", "it": "E il gelato? Lo vuoi?", "pt": "E o sorvete? Quer?", "audioKey": "b5-d1-l3" },
+ { "speaker": "learner", "it": "Sì! Ma questo è troppo dolce per me.", "pt": "Sim! Mas este é doce demais para mim.", "audioKey": "b5-d1-l4" },
+ { "speaker": "italian", "it": "Dai, prova quello al limone. È meno dolce.", "pt": "Vai, prove o de limão. É menos doce.", "audioKey": "b5-d1-l5" },
+ { "speaker": "learner", "it": "Mmm, questo mi piace di più! È fresco e buono!", "pt": "Hum, deste gosto mais! É fresco e bom!", "audioKey": "b5-d1-l6" },
+ { "speaker": "italian", "it": "Bravo! Hai buon gusto!", "pt": "Muito bem! Você tem bom gosto!", "audioKey": "b5-d1-l7" }
+ ],
+ [
+ { "speaker": "italian", "it": "Il caffè è caldo. Attento!", "pt": "O café está quente. Cuidado!", "audioKey": "b5-d2-l1" },
+ { "speaker": "learner", "it": "Grazie! Mmm… è un po' amaro.", "pt": "Obrigado! Hum… é um pouco amargo.", "audioKey": "b5-d2-l2" },
+ { "speaker": "italian", "it": "Vuoi lo zucchero? O il latte?", "pt": "Quer açúcar? Ou leite?", "audioKey": "b5-d2-l3" },
+ { "speaker": "learner", "it": "Un po' di latte, per favore. Non mi piace troppo amaro.", "pt": "Um pouco de leite, por favor. Não gosto muito amargo.", "audioKey": "b5-d2-l4" },
+ { "speaker": "italian", "it": "Ecco! Ora è più dolce?", "pt": "Pronto! Agora está mais doce?", "audioKey": "b5-d2-l5" },
+ { "speaker": "learner", "it": "Sì, ora è perfetto! Mi piace così!", "pt": "Sim, agora está perfeito! Gosto assim!", "audioKey": "b5-d2-l6" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "A tavola con la famiglia italiana",
+ "italianRole": "Nonna che ha preparato il pranzo",
+ "goalPt": "Experimentar pratos e expressar o que gosta/não gosta usando adjetivos sensoriais",
+ "openerIt": "Tesoro, assaggia! Ho cucinato per te!"
+ },
+ "survivalChecklist": [
+ "Mi piace / Non mi piace",
+ "È buono / È buonissimo",
+ "È troppo dolce / amaro / salato",
+ "È caldo / freddo",
+ "Come si dice 'apimentado'?"
+ ]
+}
diff --git a/content/missions/B6.json b/content/missions/B6.json
new file mode 100644
index 0000000..c76688f
--- /dev/null
+++ b/content/missions/B6.json
@@ -0,0 +1,58 @@
+{
+ "id": "B6",
+ "trail": "bambino",
+ "cefr": "preA1",
+ "titleIt": "Ho fame / Ho sete / Fa male",
+ "titlePt": "Necessidades corporais básicas",
+ "canDos": [
+ { "id": "b6-cd1", "text": "Consigo expressar fome, sede, sono e dor com frases simples" },
+ { "id": "b6-cd2", "text": "Consigo dizer onde dói apontando e usando 'mi fa male'" },
+ { "id": "b6-cd3", "text": "Consigo pedir ajuda para uma necessidade física urgente" }
+ ],
+ "chunks": [
+ { "it": "Ho fame.", "pt": "Estou com fome.", "note": "Em italiano se usa 'avere' (ter) para sensações", "audioKey": "b6-chunk-01" },
+ { "it": "Ho sete.", "pt": "Estou com sede.", "audioKey": "b6-chunk-02" },
+ { "it": "Ho sonno.", "pt": "Estou com sono.", "audioKey": "b6-chunk-03" },
+ { "it": "Ho freddo.", "pt": "Estou com frio.", "audioKey": "b6-chunk-04" },
+ { "it": "Ho caldo.", "pt": "Estou com calor.", "audioKey": "b6-chunk-05" },
+ { "it": "Mi fa male la testa.", "pt": "Minha cabeça dói / Estou com dor de cabeça.", "note": "'fa male' = dói; artigo + parte do corpo", "audioKey": "b6-chunk-06" },
+ { "it": "Mi fa male qui.", "pt": "Dói aqui.", "note": "Aponte enquanto diz — gestos completam a frase", "audioKey": "b6-chunk-07" },
+ { "it": "Ho mal di pancia.", "pt": "Estou com dor de barriga.", "note": "'mal di' + parte = dor de…", "audioKey": "b6-chunk-08" },
+ { "it": "Sono stanco.", "pt": "Estou cansado.", "note": "'stanco/a' concorda com gênero", "audioKey": "b6-chunk-09" },
+ { "it": "Ho bisogno di andare in bagno.", "pt": "Preciso ir ao banheiro.", "audioKey": "b6-chunk-10" },
+ { "it": "Come si dice 'dor de garganta'?", "pt": "Como se diz 'dor de garganta'?", "note": "SALVAVITA para dores que não sabe nomear", "audioKey": "b6-chunk-11" },
+ { "it": "Non sto bene.", "pt": "Não estou bem.", "audioKey": "b6-chunk-12" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "italian", "it": "Come stai? Hai mangiato?", "pt": "Como está? Já comeu?", "audioKey": "b6-d1-l1" },
+ { "speaker": "learner", "it": "No, ho tanta fame! E ho anche sete.", "pt": "Não, estou com muita fome! E também com sede.", "audioKey": "b6-d1-l2" },
+ { "speaker": "italian", "it": "Poverino! Vieni, ti preparo qualcosa.", "pt": "Coitado! Venha, te preparo alguma coisa.", "audioKey": "b6-d1-l3" },
+ { "speaker": "learner", "it": "Grazie! Ho anche un po' di sonno.", "pt": "Obrigado! Também estou com um pouco de sono.", "audioKey": "b6-d1-l4" },
+ { "speaker": "italian", "it": "Mangia prima, poi fai un riposino.", "pt": "Coma primeiro, depois descanse um pouquinho.", "audioKey": "b6-d1-l5" },
+ { "speaker": "learner", "it": "Va bene, grazie! Ho proprio fame!", "pt": "Tudo bem, obrigado! Estou realmente com fome!", "audioKey": "b6-d1-l6" }
+ ],
+ [
+ { "speaker": "learner", "it": "Mamma, mi fa male la pancia!", "pt": "Mamãe, minha barriga dói!", "audioKey": "b6-d2-l1" },
+ { "speaker": "italian", "it": "Oh no! Dove ti fa male esattamente?", "pt": "Ah não! Onde dói exatamente?", "audioKey": "b6-d2-l2" },
+ { "speaker": "learner", "it": "Qui! Mi fa male proprio qui.", "pt": "Aqui! Dói bem aqui.", "audioKey": "b6-d2-l3" },
+ { "speaker": "italian", "it": "Hai mangiato qualcosa di strano?", "pt": "Comeu alguma coisa estranha?", "audioKey": "b6-d2-l4" },
+ { "speaker": "learner", "it": "Forse sì… non sto bene.", "pt": "Talvez sim… não estou bem.", "audioKey": "b6-d2-l5" },
+ { "speaker": "italian", "it": "Vieni, ti do una medicina. Stai tranquillo.", "pt": "Venha, te dou um remédio. Fique tranquilo.", "audioKey": "b6-d2-l6" },
+ { "speaker": "learner", "it": "Grazie, mamma.", "pt": "Obrigado, mamãe.", "audioKey": "b6-d2-l7" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "A casa dopo la scuola",
+ "italianRole": "Genitore che accoglie il bambino",
+ "goalPt": "Expressar necessidades físicas (fome, sede, dor, cansaço) ao chegar em casa",
+ "openerIt": "Ciao tesoro! Come è andata a scuola?"
+ },
+ "survivalChecklist": [
+ "Ho fame / Ho sete / Ho sonno",
+ "Mi fa male la testa / la pancia",
+ "Mi fa male qui (apontando)",
+ "Non sto bene",
+ "Ho bisogno di andare in bagno"
+ ]
+}
diff --git a/content/missions/B7.json b/content/missions/B7.json
new file mode 100644
index 0000000..33b71cb
--- /dev/null
+++ b/content/missions/B7.json
@@ -0,0 +1,60 @@
+{
+ "id": "B7",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Numeri e quantità",
+ "titlePt": "Números 0-20, quanto custa?, pouco/muito",
+ "canDos": [
+ { "id": "b7-cd1", "text": "Consigo contar de 0 a 20 e perguntar preços" },
+ { "id": "b7-cd2", "text": "Consigo expressar quantidade com 'poco', 'tanto', 'troppo'" },
+ { "id": "b7-cd3", "text": "Consigo comparar tamanhos com 'piccolo' e 'grande'" }
+ ],
+ "chunks": [
+ { "it": "Zero, uno, due, tre, quattro, cinque.", "pt": "Zero, um, dois, três, quatro, cinco.", "audioKey": "b7-chunk-01" },
+ { "it": "Sei, sette, otto, nove, dieci.", "pt": "Seis, sete, oito, nove, dez.", "audioKey": "b7-chunk-02" },
+ { "it": "Undici, dodici, tredici, quattordici, quindici.", "pt": "Onze, doze, treze, catorze, quinze.", "audioKey": "b7-chunk-03" },
+ { "it": "Sedici, diciassette, diciotto, diciannove, venti.", "pt": "Dezesseis, dezessete, dezoito, dezenove, vinte.", "note": "'diciotto' perde o 'i' de 'dici'", "audioKey": "b7-chunk-04" },
+ { "it": "Quanto costa?", "pt": "Quanto custa?", "audioKey": "b7-chunk-05" },
+ { "it": "Quanto costa questo?", "pt": "Quanto custa isto?", "audioKey": "b7-chunk-06" },
+ { "it": "Costa cinque euro.", "pt": "Custa cinco euros.", "audioKey": "b7-chunk-07" },
+ { "it": "È troppo caro!", "pt": "É caro demais!", "note": "'caro' = caro; 'economico' = barato", "audioKey": "b7-chunk-08" },
+ { "it": "È piccolo.", "pt": "É pequeno.", "audioKey": "b7-chunk-09" },
+ { "it": "È grande.", "pt": "É grande.", "audioKey": "b7-chunk-10" },
+ { "it": "Un po' di più.", "pt": "Um pouco mais.", "audioKey": "b7-chunk-11" },
+ { "it": "Poco / Tanto / Troppo.", "pt": "Pouco / Muito / Demais.", "note": "'troppo' = excesso, além do desejado", "audioKey": "b7-chunk-12" },
+ { "it": "Ce ne sono pochi.", "pt": "Há poucos.", "note": "'ce ne sono' = há/existem (de algo)", "audioKey": "b7-chunk-13" },
+ { "it": "Quanti ce ne sono?", "pt": "Quantos há?", "audioKey": "b7-chunk-14" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Buongiorno! Quanto costa questo gelato?", "pt": "Bom dia! Quanto custa este sorvete?", "audioKey": "b7-d1-l1" },
+ { "speaker": "italian", "it": "Tre euro e cinquanta. Ne vuoi due?", "pt": "Três euros e cinquenta. Quer dois?", "audioKey": "b7-d1-l2" },
+ { "speaker": "learner", "it": "No, solo uno per me. Ma quello grande, per favore!", "pt": "Não, só um para mim. Mas o grande, por favor!", "audioKey": "b7-d1-l3" },
+ { "speaker": "italian", "it": "Il grande costa cinque euro.", "pt": "O grande custa cinco euros.", "audioKey": "b7-d1-l4" },
+ { "speaker": "learner", "it": "Va bene! È troppo buono per dire di no!", "pt": "Tudo bem! É gostoso demais para dizer não!", "audioKey": "b7-d1-l5" },
+ { "speaker": "italian", "it": "Dai, eccoti il gelato grande!", "pt": "Vai, aqui está o sorvete grande!", "audioKey": "b7-d1-l6" }
+ ],
+ [
+ { "speaker": "italian", "it": "Quante mele vuoi? Ce ne sono tante!", "pt": "Quantas maçãs quer? Há muitas!", "audioKey": "b7-d2-l1" },
+ { "speaker": "learner", "it": "Ne voglio cinque, per favore.", "pt": "Quero cinco, por favor.", "audioKey": "b7-d2-l2" },
+ { "speaker": "italian", "it": "Cinque? Sono un po' troppe per te!", "pt": "Cinco? São um pouco demais para você!", "audioKey": "b7-d2-l3" },
+ { "speaker": "learner", "it": "No, ho tanta fame! Ne mangio almeno tre!", "pt": "Não, estou com muita fome! Como pelo menos três!", "audioKey": "b7-d2-l4" },
+ { "speaker": "italian", "it": "Va bene, dai! Prendine cinque.", "pt": "Tudo bem, vai! Pegue cinco.", "audioKey": "b7-d2-l5" },
+ { "speaker": "learner", "it": "Grazie! Quanto costano?", "pt": "Obrigado! Quanto custam?", "audioKey": "b7-d2-l6" },
+ { "speaker": "italian", "it": "Due euro al chilo, quindi circa tre euro.", "pt": "Dois euros o quilo, então cerca de três euros.", "audioKey": "b7-d2-l7" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Al mercato della frutta",
+ "italianRole": "Fruttivendolo simpatico",
+ "goalPt": "Comprar frutas perguntando preço, quantidade e tamanho",
+ "openerIt": "Buongiorno! Ho della frutta freschissima oggi!"
+ },
+ "survivalChecklist": [
+ "Quanto costa?",
+ "Uno, due, tre… fino a venti",
+ "È piccolo / grande",
+ "Poco / Tanto / Troppo",
+ "È troppo caro"
+ ]
+}
diff --git a/content/missions/B8.json b/content/missions/B8.json
new file mode 100644
index 0000000..94d4453
--- /dev/null
+++ b/content/missions/B8.json
@@ -0,0 +1,58 @@
+{
+ "id": "B8",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Sì, No, Forse, Non lo so",
+ "titlePt": "Sim, Não, Talvez, Não sei — responder simples",
+ "canDos": [
+ { "id": "b8-cd1", "text": "Consigo responder perguntas simples com sì/no/forse" },
+ { "id": "b8-cd2", "text": "Consigo dizer que não sei sem travar a conversa" },
+ { "id": "b8-cd3", "text": "Consigo confirmar ou negar acrescentando uma palavra extra" }
+ ],
+ "chunks": [
+ { "it": "Sì.", "pt": "Sim.", "audioKey": "b8-chunk-01" },
+ { "it": "No.", "pt": "Não.", "audioKey": "b8-chunk-02" },
+ { "it": "Forse.", "pt": "Talvez.", "audioKey": "b8-chunk-03" },
+ { "it": "Non lo so.", "pt": "Não sei.", "note": "'lo' = isso/aquilo (objeto direto)", "audioKey": "b8-chunk-04" },
+ { "it": "Sì, certo!", "pt": "Sim, claro!", "audioKey": "b8-chunk-05" },
+ { "it": "No, purtroppo.", "pt": "Não, infelizmente.", "note": "'purtroppo' suaviza a negativa", "audioKey": "b8-chunk-06" },
+ { "it": "Penso di sì.", "pt": "Acho que sim.", "audioKey": "b8-chunk-07" },
+ { "it": "Penso di no.", "pt": "Acho que não.", "audioKey": "b8-chunk-08" },
+ { "it": "Non sono sicuro.", "pt": "Não tenho certeza.", "note": "'sicuro/a' concorda com gênero", "audioKey": "b8-chunk-09" },
+ { "it": "Mah, non saprei.", "pt": "Sei lá, não saberia.", "note": "'mah' é interjeição de dúvida muito italiana", "audioKey": "b8-chunk-10" },
+ { "it": "Assolutamente sì!", "pt": "Com certeza absoluta!", "audioKey": "b8-chunk-11" },
+ { "it": "Come si dice 'talvez' in italiano?", "pt": "Como se diz 'talvez' em italiano?", "note": "SALVAVITA para expressar incerteza", "audioKey": "b8-chunk-12" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "italian", "it": "Ti piace la pasta?", "pt": "Gosta de massa?", "audioKey": "b8-d1-l1" },
+ { "speaker": "learner", "it": "Sì, certo! Mi piace tantissimo!", "pt": "Sim, claro! Gosto muitíssimo!", "audioKey": "b8-d1-l2" },
+ { "speaker": "italian", "it": "E il pesce? Ti piace il pesce?", "pt": "E peixe? Gosta de peixe?", "audioKey": "b8-d1-l3" },
+ { "speaker": "learner", "it": "Mah, forse. Non sono sicuro.", "pt": "Sei lá, talvez. Não tenho certeza.", "audioKey": "b8-d1-l4" },
+ { "speaker": "italian", "it": "E le verdure? Mangi le verdure?", "pt": "E legumes? Come legumes?", "audioKey": "b8-d1-l5" },
+ { "speaker": "learner", "it": "No, purtroppo. Non mi piacciono tanto.", "pt": "Não, infelizmente. Não gosto tanto.", "audioKey": "b8-d1-l6" },
+ { "speaker": "italian", "it": "Dai, devi provare! Sono buone!", "pt": "Vai, tem que provar! São bons!", "audioKey": "b8-d1-l7" }
+ ],
+ [
+ { "speaker": "italian", "it": "Sai dove è la stazione?", "pt": "Sabe onde fica a estação?", "audioKey": "b8-d2-l1" },
+ { "speaker": "learner", "it": "Non lo so, mi dispiace.", "pt": "Não sei, desculpe.", "audioKey": "b8-d2-l2" },
+ { "speaker": "italian", "it": "È lontana da qui?", "pt": "É longe daqui?", "audioKey": "b8-d2-l3" },
+ { "speaker": "learner", "it": "Penso di no. Forse è vicino.", "pt": "Acho que não. Talvez seja perto.", "audioKey": "b8-d2-l4" },
+ { "speaker": "italian", "it": "Va bene, chiedo a qualcun altro. Grazie comunque!", "pt": "Tudo bem, pergunto a outra pessoa. Obrigado de qualquer forma!", "audioKey": "b8-d2-l5" },
+ { "speaker": "learner", "it": "Prego! Mi dispiace non poter aiutare.", "pt": "De nada! Desculpe não poder ajudar.", "audioKey": "b8-d2-l6" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Incontro con un nuovo amico italiano",
+ "italianRole": "Coetaneo italiano che fa domande semplici",
+ "goalPt": "Responder perguntas pessoais simples usando sì/no/forse/non lo so",
+ "openerIt": "Ciao! Ti chiami Marco, vero?"
+ },
+ "survivalChecklist": [
+ "Sì / No / Forse",
+ "Non lo so",
+ "Sì, certo! / No, purtroppo",
+ "Penso di sì / Penso di no",
+ "Mah, non saprei"
+ ]
+}
diff --git a/content/missions/B9.json b/content/missions/B9.json
new file mode 100644
index 0000000..bc91153
--- /dev/null
+++ b/content/missions/B9.json
@@ -0,0 +1,58 @@
+{
+ "id": "B9",
+ "trail": "bambino",
+ "cefr": "A1",
+ "titleIt": "Dov'è? Qui e Là",
+ "titlePt": "Onde está? Aqui e lá — direções básicas",
+ "canDos": [
+ { "id": "b9-cd1", "text": "Consigo perguntar onde algo está com 'dov'è'" },
+ { "id": "b9-cd2", "text": "Consigo indicar localização com 'qui', 'là', 'vicino', 'lontano'" },
+ { "id": "b9-cd3", "text": "Consigo entender direções simples como 'a destra/sinistra'" }
+ ],
+ "chunks": [
+ { "it": "Dov'è il bagno?", "pt": "Onde fica o banheiro?", "note": "'dov'è' = dove + è (onde está)", "audioKey": "b9-chunk-01" },
+ { "it": "Dov'è la stazione?", "pt": "Onde fica a estação?", "audioKey": "b9-chunk-02" },
+ { "it": "È qui.", "pt": "Está aqui.", "audioKey": "b9-chunk-03" },
+ { "it": "È là.", "pt": "Está lá.", "audioKey": "b9-chunk-04" },
+ { "it": "È vicino.", "pt": "É perto / Está perto.", "audioKey": "b9-chunk-05" },
+ { "it": "È lontano.", "pt": "É longe / Está longe.", "audioKey": "b9-chunk-06" },
+ { "it": "A destra.", "pt": "À direita.", "audioKey": "b9-chunk-07" },
+ { "it": "A sinistra.", "pt": "À esquerda.", "audioKey": "b9-chunk-08" },
+ { "it": "Sempre dritto.", "pt": "Sempre em frente / reto.", "audioKey": "b9-chunk-09" },
+ { "it": "Gira a destra.", "pt": "Vire à direita.", "note": "'girare' = virar; imperativo informal", "audioKey": "b9-chunk-10" },
+ { "it": "È dall'altra parte.", "pt": "É do outro lado.", "audioKey": "b9-chunk-11" },
+ { "it": "Come si dice 'atrás' in italiano?", "pt": "Como se diz 'atrás' em italiano?", "note": "SALVAVITA para direções", "audioKey": "b9-chunk-12" },
+ { "it": "È proprio qui accanto.", "pt": "É bem aqui ao lado.", "audioKey": "b9-chunk-13" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Scusi, dov'è il bagno?", "pt": "Desculpe, onde fica o banheiro?", "audioKey": "b9-d1-l1" },
+ { "speaker": "italian", "it": "È laggiù, a destra, vicino alla porta.", "pt": "É ali embaixo, à direita, perto da porta.", "audioKey": "b9-d1-l2" },
+ { "speaker": "learner", "it": "A destra? È lontano?", "pt": "À direita? É longe?", "audioKey": "b9-d1-l3" },
+ { "speaker": "italian", "it": "No, è vicinissimo! Due passi.", "pt": "Não, é pertíssimo! Dois passos.", "audioKey": "b9-d1-l4" },
+ { "speaker": "learner", "it": "Perfetto, grazie mille!", "pt": "Perfeito, muito obrigado!", "audioKey": "b9-d1-l5" },
+ { "speaker": "italian", "it": "Di niente! Vai dritto e poi gira.", "pt": "De nada! Vá em frente e depois vire.", "audioKey": "b9-d1-l6" }
+ ],
+ [
+ { "speaker": "italian", "it": "Dove abiti? Sei di qui?", "pt": "Onde mora? É daqui?", "audioKey": "b9-d2-l1" },
+ { "speaker": "learner", "it": "No, non sono di qui. Abito lontano.", "pt": "Não, não sou daqui. Moro longe.", "audioKey": "b9-d2-l2" },
+ { "speaker": "italian", "it": "Ah, e dove esattamente?", "pt": "Ah, e onde exatamente?", "audioKey": "b9-d2-l3" },
+ { "speaker": "learner", "it": "Non lo so spiegare bene… è vicino al parco grande.", "pt": "Não sei explicar bem… é perto do parque grande.", "audioKey": "b9-d2-l4" },
+ { "speaker": "italian", "it": "Ah, ho capito! Vicino al Parco Sempione?", "pt": "Ah, entendi! Perto do Parque Sempione?", "audioKey": "b9-d2-l5" },
+ { "speaker": "learner", "it": "Sì, proprio lì! È qui accanto!", "pt": "Sim, bem ali! É aqui ao lado!", "audioKey": "b9-d2-l6" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Per strada in centro città",
+ "italianRole": "Passante gentile per strada",
+ "goalPt": "Perguntar e entender direções para encontrar um lugar",
+ "openerIt": "Scusi, mi può aiutare?"
+ },
+ "survivalChecklist": [
+ "Dov'è…?",
+ "È qui / È là",
+ "È vicino / È lontano",
+ "A destra / A sinistra",
+ "Sempre dritto"
+ ]
+}
diff --git a/content/missions/V1.json b/content/missions/V1.json
new file mode 100644
index 0000000..bcd182f
--- /dev/null
+++ b/content/missions/V1.json
@@ -0,0 +1,63 @@
+{
+ "id": "V1",
+ "trail": "viaggio",
+ "cefr": "A1",
+ "titleIt": "In Aeroporto e alla Dogana",
+ "titlePt": "No aeroporto e na alfândega",
+ "canDos": [
+ { "id": "v1-cd1", "text": "Consigo responder perguntas básicas na imigração sobre destino e motivo da viagem" },
+ { "id": "v1-cd2", "text": "Consigo declarar bagagem e dizer que é só bagagem de mão" },
+ { "id": "v1-cd3", "text": "Consigo entender instruções simples no controle de segurança" }
+ ],
+ "chunks": [
+ { "it": "Buongiorno, ho il passaporto.", "pt": "Bom dia, tenho o passaporte.", "audioKey": "v1-chunk-01" },
+ { "it": "Sono qui in vacanza.", "pt": "Estou aqui de férias.", "note": "'in vacanza' = de férias; 'per lavoro' = a trabalho", "audioKey": "v1-chunk-02" },
+ { "it": "Resto due settimane.", "pt": "Fico duas semanas.", "note": "'restare' = ficar/permanecer", "audioKey": "v1-chunk-03" },
+ { "it": "È solo bagaglio a mano.", "pt": "É só bagagem de mão.", "note": "Frase essencial no check-in e segurança", "audioKey": "v1-chunk-04" },
+ { "it": "Ho una valigia da imbarcare.", "pt": "Tenho uma mala para despachar.", "audioKey": "v1-chunk-05" },
+ { "it": "Il mio volo è per Roma.", "pt": "Meu voo é para Roma.", "audioKey": "v1-chunk-06" },
+ { "it": "Dov'è il gate?", "pt": "Onde fica o portão?", "audioKey": "v1-chunk-07" },
+ { "it": "A che ora parte il volo?", "pt": "A que horas sai o voo?", "note": "'partire' = partir/sair (para transportes)", "audioKey": "v1-chunk-08" },
+ { "it": "Non ho niente da dichiarare.", "pt": "Não tenho nada a declarar.", "audioKey": "v1-chunk-09" },
+ { "it": "Scusi, dove ritiro i bagagli?", "pt": "Desculpe, onde retiro as bagagens?", "audioKey": "v1-chunk-10" },
+ { "it": "Potrebbe ripetere, per favore?", "pt": "Poderia repetir, por favor?", "note": "Condizionale = mais cortês; SALVAVITA formal", "audioKey": "v1-chunk-11" },
+ { "it": "Il nastro dei bagagli è laggiù.", "pt": "A esteira das bagagens é ali embaixo.", "audioKey": "v1-chunk-12" },
+ { "it": "Ho perso la coincidenza.", "pt": "Perdi a conexão.", "note": "'coincidenza' = conexão de voo", "audioKey": "v1-chunk-13" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "italian", "it": "Buongiorno. Passaporto, prego.", "pt": "Bom dia. Passaporte, por favor.", "audioKey": "v1-d1-l1" },
+ { "speaker": "learner", "it": "Buongiorno, ecco il passaporto.", "pt": "Bom dia, aqui está o passaporte.", "audioKey": "v1-d1-l2" },
+ { "speaker": "italian", "it": "Qual è il motivo del viaggio?", "pt": "Qual é o motivo da viagem?", "audioKey": "v1-d1-l3" },
+ { "speaker": "learner", "it": "Sono qui in vacanza. Resto dieci giorni.", "pt": "Estou aqui de férias. Fico dez dias.", "audioKey": "v1-d1-l4" },
+ { "speaker": "italian", "it": "Dove alloggia?", "pt": "Onde se hospeda?", "audioKey": "v1-d1-l5" },
+ { "speaker": "learner", "it": "In un hotel a Roma centro. Ho la prenotazione.", "pt": "Num hotel no centro de Roma. Tenho a reserva.", "audioKey": "v1-d1-l6" },
+ { "speaker": "italian", "it": "Ha qualcosa da dichiarare?", "pt": "Tem algo a declarar?", "audioKey": "v1-d1-l7" },
+ { "speaker": "learner", "it": "No, non ho niente da dichiarare.", "pt": "Não, não tenho nada a declarar.", "audioKey": "v1-d1-l8" },
+ { "speaker": "italian", "it": "Bene, buon soggiorno in Italia!", "pt": "Bem, boa estadia na Itália!", "audioKey": "v1-d1-l9" },
+ { "speaker": "learner", "it": "Grazie mille! Arrivederci!", "pt": "Muito obrigado! Até logo!", "audioKey": "v1-d1-l10" }
+ ],
+ [
+ { "speaker": "italian", "it": "Signore, ha bagagli da imbarcare?", "pt": "Senhor, tem bagagem para despachar?", "audioKey": "v1-d2-l1" },
+ { "speaker": "learner", "it": "No, è solo bagaglio a mano. Questa valigia piccola.", "pt": "Não, é só bagagem de mão. Esta mala pequena.", "audioKey": "v1-d2-l2" },
+ { "speaker": "italian", "it": "Va bene. Il gate è il B12, l'imbarco inizia alle quattordici.", "pt": "Tudo bem. O portão é o B12, o embarque começa às quatorze.", "audioKey": "v1-d2-l3" },
+ { "speaker": "learner", "it": "Scusi, può ripetere il numero del gate?", "pt": "Desculpe, pode repetir o número do portão?", "audioKey": "v1-d2-l4" },
+ { "speaker": "italian", "it": "Certo! B come Bologna, uno due. Gate B12.", "pt": "Claro! B como Bolonha, um dois. Portão B12.", "audioKey": "v1-d2-l5" },
+ { "speaker": "learner", "it": "Perfetto, grazie! A che ora parte?", "pt": "Perfeito, obrigado! A que horas sai?", "audioKey": "v1-d2-l6" },
+ { "speaker": "italian", "it": "Alle quattordici e trenta. Buon viaggio!", "pt": "Às quatorze e trinta. Boa viagem!", "audioKey": "v1-d2-l7" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Al controllo passaporti dell'aeroporto",
+ "italianRole": "Agente della dogana / Addetto al check-in",
+ "goalPt": "Passar pela imigração respondendo perguntas sobre motivo, duração e hospedagem",
+ "openerIt": "Buongiorno, passaporto prego. Qual è il motivo del viaggio?"
+ },
+ "survivalChecklist": [
+ "Sono qui in vacanza / per lavoro",
+ "È solo bagaglio a mano",
+ "Non ho niente da dichiarare",
+ "Dov'è il gate? / A che ora parte?",
+ "Potrebbe ripetere, per favore?"
+ ]
+}
diff --git a/content/missions/V2.json b/content/missions/V2.json
new file mode 100644
index 0000000..8373c7d
--- /dev/null
+++ b/content/missions/V2.json
@@ -0,0 +1,60 @@
+{
+ "id": "V2",
+ "trail": "viaggio",
+ "cefr": "A1",
+ "titleIt": "Taxi, Treno e Bus",
+ "titlePt": "Táxi, trem e ônibus",
+ "canDos": [
+ { "id": "v2-cd1", "text": "Consigo perguntar horários e comprar bilhetes de transporte" },
+ { "id": "v2-cd2", "text": "Consigo pedir um táxi e dizer o destino" },
+ { "id": "v2-cd3", "text": "Consigo entender informações sobre plataforma e paradas" }
+ ],
+ "chunks": [
+ { "it": "A che ora parte il treno?", "pt": "A que horas sai o trem?", "audioKey": "v2-chunk-01" },
+ { "it": "Da quale binario parte?", "pt": "De qual plataforma sai?", "note": "'binario' = plataforma de trem", "audioKey": "v2-chunk-02" },
+ { "it": "Un biglietto di sola andata.", "pt": "Uma passagem só de ida.", "audioKey": "v2-chunk-03" },
+ { "it": "Un biglietto di andata e ritorno.", "pt": "Uma passagem de ida e volta.", "audioKey": "v2-chunk-04" },
+ { "it": "Quanto costa il biglietto?", "pt": "Quanto custa a passagem?", "audioKey": "v2-chunk-05" },
+ { "it": "Dov'è la fermata del bus?", "pt": "Onde fica a parada de ônibus?", "audioKey": "v2-chunk-06" },
+ { "it": "Mi porti alla stazione, per favore.", "pt": "Me leve à estação, por favor.", "note": "Para táxi: 'mi porti a…'", "audioKey": "v2-chunk-07" },
+ { "it": "Mi porti in Via Roma numero dieci.", "pt": "Me leve à Rua Roma número dez.", "audioKey": "v2-chunk-08" },
+ { "it": "Questo treno va a Firenze?", "pt": "Este trem vai para Florença?", "audioKey": "v2-chunk-09" },
+ { "it": "C'è un ritardo?", "pt": "Há atraso?", "note": "'ritardo' = atraso; 'in anticipo' = adiantado", "audioKey": "v2-chunk-10" },
+ { "it": "Scusi, questo è il binario giusto?", "pt": "Desculpe, esta é a plataforma certa?", "note": "SALVAVITA para confirmar antes de embarcar", "audioKey": "v2-chunk-11" },
+ { "it": "Il prossimo treno arriva tra dieci minuti.", "pt": "O próximo trem chega em dez minutos.", "audioKey": "v2-chunk-12" },
+ { "it": "Posso sedermi qui?", "pt": "Posso me sentar aqui?", "audioKey": "v2-chunk-13" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Buongiorno! Un biglietto per Firenze, per favore.", "pt": "Bom dia! Uma passagem para Florença, por favor.", "audioKey": "v2-d1-l1" },
+ { "speaker": "italian", "it": "Di sola andata o andata e ritorno?", "pt": "Só ida ou ida e volta?", "audioKey": "v2-d1-l2" },
+ { "speaker": "learner", "it": "Andata e ritorno. Quanto costa?", "pt": "Ida e volta. Quanto custa?", "audioKey": "v2-d1-l3" },
+ { "speaker": "italian", "it": "Trentacinque euro. Il prossimo treno parte alle undici dal binario sette.", "pt": "Trinta e cinco euros. O próximo trem sai às onze da plataforma sete.", "audioKey": "v2-d1-l4" },
+ { "speaker": "learner", "it": "Alle undici, binario sette. Grazie! A che ora arrivo?", "pt": "Às onze, plataforma sete. Obrigado! A que horas chego?", "audioKey": "v2-d1-l5" },
+ { "speaker": "italian", "it": "Alle dodici e quaranta. Sono due ore circa.", "pt": "Às doze e quarenta. São cerca de duas horas.", "audioKey": "v2-d1-l6" },
+ { "speaker": "learner", "it": "Perfetto, grazie mille!", "pt": "Perfeito, muito obrigado!", "audioKey": "v2-d1-l7" }
+ ],
+ [
+ { "speaker": "learner", "it": "Taxi! Mi porti all'Hotel Centrale, per favore.", "pt": "Táxi! Me leve ao Hotel Central, por favor.", "audioKey": "v2-d2-l1" },
+ { "speaker": "italian", "it": "Certo! In Via Garibaldi? È vicino alla stazione.", "pt": "Claro! Na Rua Garibaldi? É perto da estação.", "audioKey": "v2-d2-l2" },
+ { "speaker": "learner", "it": "Sì, esatto! Quanto ci vuole?", "pt": "Sim, exato! Quanto tempo leva?", "audioKey": "v2-d2-l3" },
+ { "speaker": "italian", "it": "Dieci minuti, se non c'è traffico.", "pt": "Dez minutos, se não houver trânsito.", "audioKey": "v2-d2-l4" },
+ { "speaker": "learner", "it": "Va bene. Quanto costa più o meno?", "pt": "Tudo bem. Quanto custa mais ou menos?", "audioKey": "v2-d2-l5" },
+ { "speaker": "italian", "it": "Circa quindici euro. Accetta la carta?", "pt": "Cerca de quinze euros. Aceita cartão?", "audioKey": "v2-d2-l6" },
+ { "speaker": "learner", "it": "Sì, pago con la carta. Grazie!", "pt": "Sim, pago com cartão. Obrigado!", "audioKey": "v2-d2-l7" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Alla biglietteria della stazione ferroviaria",
+ "italianRole": "Addetto alla biglietteria",
+ "goalPt": "Comprar uma passagem de trem perguntando horário, plataforma e preço",
+ "openerIt": "Buongiorno! Dove deve andare?"
+ },
+ "survivalChecklist": [
+ "A che ora parte? / Da quale binario?",
+ "Biglietto di sola andata / andata e ritorno",
+ "Mi porti a…, per favore (taxi)",
+ "Dov'è la fermata del bus?",
+ "C'è un ritardo?"
+ ]
+}
diff --git a/content/missions/V3.json b/content/missions/V3.json
new file mode 100644
index 0000000..0ae5b20
--- /dev/null
+++ b/content/missions/V3.json
@@ -0,0 +1,61 @@
+{
+ "id": "V3",
+ "trail": "viaggio",
+ "cefr": "A2",
+ "titleIt": "In Hotel: Prenotazione e Reclami",
+ "titlePt": "No hotel: reserva e reclamações",
+ "canDos": [
+ { "id": "v3-cd1", "text": "Consigo fazer check-in informando que tenho reserva" },
+ { "id": "v3-cd2", "text": "Consigo reclamar de problemas no quarto de forma educada" },
+ { "id": "v3-cd3", "text": "Consigo pedir informações sobre serviços do hotel" }
+ ],
+ "chunks": [
+ { "it": "Buongiorno, ho una prenotazione a nome Rossi.", "pt": "Bom dia, tenho uma reserva em nome de Rossi.", "note": "'a nome di' = em nome de", "audioKey": "v3-chunk-01" },
+ { "it": "Ho prenotato una camera doppia.", "pt": "Reservei um quarto duplo.", "note": "'camera singola/doppia' = quarto individual/duplo", "audioKey": "v3-chunk-02" },
+ { "it": "Per tre notti, fino a venerdì.", "pt": "Por três noites, até sexta-feira.", "audioKey": "v3-chunk-03" },
+ { "it": "La colazione è inclusa?", "pt": "O café da manhã está incluído?", "audioKey": "v3-chunk-04" },
+ { "it": "A che ora è la colazione?", "pt": "A que horas é o café da manhã?", "audioKey": "v3-chunk-05" },
+ { "it": "Manca il sapone in bagno.", "pt": "Falta sabonete no banheiro.", "note": "'mancare' = faltar; essencial para reclamar", "audioKey": "v3-chunk-06" },
+ { "it": "L'aria condizionata non funziona.", "pt": "O ar condicionado não funciona.", "audioKey": "v3-chunk-07" },
+ { "it": "La camera è troppo rumorosa.", "pt": "O quarto é barulhento demais.", "audioKey": "v3-chunk-08" },
+ { "it": "Potrei cambiare camera?", "pt": "Poderia trocar de quarto?", "note": "Condizionale = cortês para pedidos", "audioKey": "v3-chunk-09" },
+ { "it": "C'è il Wi-Fi? Qual è la password?", "pt": "Tem Wi-Fi? Qual é a senha?", "audioKey": "v3-chunk-10" },
+ { "it": "Vorrei fare il check-out.", "pt": "Gostaria de fazer o check-out.", "audioKey": "v3-chunk-11" },
+ { "it": "Scusi, potrei avere un asciugamano in più?", "pt": "Desculpe, poderia ter uma toalha extra?", "note": "SALVAVITA + condizionale para pedir extras", "audioKey": "v3-chunk-12" },
+ { "it": "Il riscaldamento non va.", "pt": "O aquecimento não funciona.", "note": "'riscaldamento' = aquecimento; 'non va' = não vai/funciona", "audioKey": "v3-chunk-13" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Buonasera! Ho una prenotazione a nome Silva.", "pt": "Boa noite! Tenho uma reserva em nome de Silva.", "audioKey": "v3-d1-l1" },
+ { "speaker": "italian", "it": "Sì, signor Silva! Camera doppia per quattro notti, giusto?", "pt": "Sim, senhor Silva! Quarto duplo por quatro noites, certo?", "audioKey": "v3-d1-l2" },
+ { "speaker": "learner", "it": "Esatto! La colazione è inclusa?", "pt": "Exato! O café da manhã está incluído?", "audioKey": "v3-d1-l3" },
+ { "speaker": "italian", "it": "Sì, dalle sette alle dieci nella sala al piano terra.", "pt": "Sim, das sete às dez na sala do térreo.", "audioKey": "v3-d1-l4" },
+ { "speaker": "learner", "it": "Perfetto. C'è il Wi-Fi in camera?", "pt": "Perfeito. Tem Wi-Fi no quarto?", "audioKey": "v3-d1-l5" },
+ { "speaker": "italian", "it": "Certamente! La password è scritta sul biglietto nella stanza.", "pt": "Claro! A senha está escrita no cartão no quarto.", "audioKey": "v3-d1-l6" },
+ { "speaker": "learner", "it": "Grazie mille! Dov'è l'ascensore?", "pt": "Muito obrigado! Onde fica o elevador?", "audioKey": "v3-d1-l7" },
+ { "speaker": "italian", "it": "Proprio lì a destra. Buona permanenza!", "pt": "Bem ali à direita. Boa estadia!", "audioKey": "v3-d1-l8" }
+ ],
+ [
+ { "speaker": "learner", "it": "Buongiorno, scusi. Ho un problema con la camera.", "pt": "Bom dia, desculpe. Tenho um problema com o quarto.", "audioKey": "v3-d2-l1" },
+ { "speaker": "italian", "it": "Mi dica, cosa succede?", "pt": "Me diga, o que acontece?", "audioKey": "v3-d2-l2" },
+ { "speaker": "learner", "it": "L'aria condizionata non funziona e manca il sapone in bagno.", "pt": "O ar condicionado não funciona e falta sabonete no banheiro.", "audioKey": "v3-d2-l3" },
+ { "speaker": "italian", "it": "Mi dispiace molto! Mando subito qualcuno.", "pt": "Sinto muito! Mando alguém agora.", "audioKey": "v3-d2-l4" },
+ { "speaker": "learner", "it": "Grazie. Potrei anche avere un asciugamano in più?", "pt": "Obrigado. Poderia também ter uma toalha extra?", "audioKey": "v3-d2-l5" },
+ { "speaker": "italian", "it": "Certamente! Glielo porto io stesso tra cinque minuti.", "pt": "Claro! Eu mesmo levo em cinco minutos.", "audioKey": "v3-d2-l6" },
+ { "speaker": "learner", "it": "La ringrazio molto! È stato molto gentile.", "pt": "Muito obrigado! Foi muito gentil.", "audioKey": "v3-d2-l7" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Alla reception dell'hotel",
+ "italianRole": "Receptionist dell'hotel",
+ "goalPt": "Fazer check-in e depois reclamar educadamente de um problema no quarto",
+ "openerIt": "Buonasera! Benvenuto all'Hotel Bella Italia!"
+ },
+ "survivalChecklist": [
+ "Ho una prenotazione a nome…",
+ "La colazione è inclusa? / A che ora?",
+ "Manca il sapone / L'aria condizionata non funziona",
+ "Potrei cambiare camera?",
+ "Vorrei fare il check-out"
+ ]
+}
diff --git a/content/missions/V4.json b/content/missions/V4.json
new file mode 100644
index 0000000..eb6b737
--- /dev/null
+++ b/content/missions/V4.json
@@ -0,0 +1,63 @@
+{
+ "id": "V4",
+ "trail": "viaggio",
+ "cefr": "A2",
+ "titleIt": "Al Ristorante",
+ "titlePt": "No restaurante",
+ "canDos": [
+ { "id": "v4-cd1", "text": "Consigo pedir recomendações ao garçom e fazer pedidos completos" },
+ { "id": "v4-cd2", "text": "Consigo informar alergias alimentares de forma clara" },
+ { "id": "v4-cd3", "text": "Consigo pedir a conta e perguntar sobre formas de pagamento" }
+ ],
+ "chunks": [
+ { "it": "Buonasera! Un tavolo per due, per favore.", "pt": "Boa noite! Uma mesa para dois, por favor.", "audioKey": "v4-chunk-01" },
+ { "it": "Avete un tavolo libero?", "pt": "Têm uma mesa livre?", "audioKey": "v4-chunk-02" },
+ { "it": "Cosa ci consiglia?", "pt": "O que nos recomenda?", "note": "'consigliare' = recomendar; essencial no restaurante", "audioKey": "v4-chunk-03" },
+ { "it": "Qual è il piatto del giorno?", "pt": "Qual é o prato do dia?", "audioKey": "v4-chunk-04" },
+ { "it": "Sono allergico alle noci.", "pt": "Sou alérgico a nozes.", "note": "'allergico a' + alimento; feminino: 'allergica'", "audioKey": "v4-chunk-05" },
+ { "it": "Questo piatto contiene glutine?", "pt": "Este prato contém glúten?", "audioKey": "v4-chunk-06" },
+ { "it": "Vorrei le lasagne come primo.", "pt": "Gostaria da lasanha como primeiro prato.", "note": "'primo' = entrada/massa; 'secondo' = prato principal", "audioKey": "v4-chunk-07" },
+ { "it": "Come secondo prendo il pesce alla griglia.", "pt": "Como segundo prato pego o peixe grelhado.", "audioKey": "v4-chunk-08" },
+ { "it": "Da bere, una bottiglia d'acqua naturale.", "pt": "Para beber, uma garrafa de água sem gás.", "audioKey": "v4-chunk-09" },
+ { "it": "Il conto, per favore.", "pt": "A conta, por favor.", "audioKey": "v4-chunk-10" },
+ { "it": "Possiamo pagare con la carta?", "pt": "Podemos pagar com cartão?", "audioKey": "v4-chunk-11" },
+ { "it": "Scusi, cos'è questo ingrediente? Non lo conosco.", "pt": "Desculpe, o que é este ingrediente? Não conheço.", "note": "SALVAVITA + circunlóquio para ingredientes desconhecidos", "audioKey": "v4-chunk-12" },
+ { "it": "Era buonissimo, complimenti allo chef!", "pt": "Estava muito gostoso, cumprimentos ao chef!", "audioKey": "v4-chunk-13" },
+ { "it": "Il servizio è incluso nel conto?", "pt": "O serviço está incluído na conta?", "note": "'coperto' = taxa de couvert italiana", "audioKey": "v4-chunk-14" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Buonasera! Avete un tavolo libero per due?", "pt": "Boa noite! Têm uma mesa livre para dois?", "audioKey": "v4-d1-l1" },
+ { "speaker": "italian", "it": "Certamente! Prego, accomodatevi qui.", "pt": "Claro! Por favor, sentem-se aqui.", "audioKey": "v4-d1-l2" },
+ { "speaker": "learner", "it": "Grazie! Cosa ci consiglia stasera?", "pt": "Obrigado! O que recomenda esta noite?", "audioKey": "v4-d1-l3" },
+ { "speaker": "italian", "it": "Il nostro piatto forte sono le tagliatelle al ragù. Sono fatte in casa!", "pt": "Nosso carro-chefe são as tagliatelle ao molho bolonhesa. São caseiras!", "audioKey": "v4-d1-l4" },
+ { "speaker": "learner", "it": "Perfetto! Le prendo. Ma sono allergico ai funghi, attenzione per favore.", "pt": "Perfeito! Eu pego. Mas sou alérgico a cogumelos, atenção por favor.", "audioKey": "v4-d1-l5" },
+ { "speaker": "italian", "it": "Tranquillo, nessun fungo! Da bere?", "pt": "Fique tranquilo, nenhum cogumelo! Para beber?", "audioKey": "v4-d1-l6" },
+ { "speaker": "learner", "it": "Una bottiglia d'acqua naturale e un bicchiere di vino rosso.", "pt": "Uma garrafa de água sem gás e uma taça de vinho tinto.", "audioKey": "v4-d1-l7" },
+ { "speaker": "italian", "it": "Ottimo! Vi porto subito il pane e l'acqua.", "pt": "Ótimo! Trago agora o pão e a água.", "audioKey": "v4-d1-l8" },
+ { "speaker": "learner", "it": "Grazie mille!", "pt": "Muito obrigado!", "audioKey": "v4-d1-l9" }
+ ],
+ [
+ { "speaker": "learner", "it": "Scusi, possiamo avere il conto?", "pt": "Desculpe, podemos ter a conta?", "audioKey": "v4-d2-l1" },
+ { "speaker": "italian", "it": "Certo! Sono quarantadue euro totali.", "pt": "Claro! São quarenta e dois euros no total.", "audioKey": "v4-d2-l2" },
+ { "speaker": "learner", "it": "Possiamo pagare con la carta?", "pt": "Podemos pagar com cartão?", "audioKey": "v4-d2-l3" },
+ { "speaker": "italian", "it": "Sì, accettiamo tutte le carte. Bancomat o credito?", "pt": "Sim, aceitamos todos os cartões. Débito ou crédito?", "audioKey": "v4-d2-l4" },
+ { "speaker": "learner", "it": "Carta di credito, per favore. Il servizio è incluso?", "pt": "Cartão de crédito, por favor. O serviço está incluído?", "audioKey": "v4-d2-l5" },
+ { "speaker": "italian", "it": "Sì, il coperto è incluso. Grazie e buona serata!", "pt": "Sim, o couvert está incluído. Obrigado e boa noite!", "audioKey": "v4-d2-l6" },
+ { "speaker": "learner", "it": "Era tutto buonissimo! Complimenti! Buonasera!", "pt": "Tudo estava muito gostoso! Cumprimentos! Boa noite!", "audioKey": "v4-d2-l7" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In una trattoria tipica romana",
+ "italianRole": "Cameriere cordiale e disponibile",
+ "goalPt": "Pedir recomendação, informar alergia, fazer pedido completo e pagar",
+ "openerIt": "Buonasera! Benvenuti! Avete già scelto?"
+ },
+ "survivalChecklist": [
+ "Un tavolo per…, per favore",
+ "Cosa ci consiglia?",
+ "Sono allergico/a a…",
+ "Il conto, per favore / Possiamo pagare con la carta?",
+ "Scusi, cos'è questo ingrediente?"
+ ]
+}
diff --git a/content/missions/V5.json b/content/missions/V5.json
new file mode 100644
index 0000000..3dd1b08
--- /dev/null
+++ b/content/missions/V5.json
@@ -0,0 +1,62 @@
+{
+ "id": "V5",
+ "trail": "viaggio",
+ "cefr": "A2",
+ "titleIt": "Al Bar e in Paninoteca",
+ "titlePt": "No bar e na sanduicheria — circunlóquio aplicado",
+ "canDos": [
+ { "id": "v5-cd1", "text": "Consigo pedir café e lanches rápidos no balcão como um italiano" },
+ { "id": "v5-cd2", "text": "Consigo escolher frios e queijos descrevendo o que não sei nomear" },
+ { "id": "v5-cd3", "text": "Consigo usar circunlóquio eficazmente para ingredientes desconhecidos" }
+ ],
+ "chunks": [
+ { "it": "Un caffè, per favore.", "pt": "Um café, por favor.", "note": "Na Itália, 'caffè' = espresso; peça 'caffè americano' se quiser fraco", "audioKey": "v5-chunk-01" },
+ { "it": "Un caffè al banco.", "pt": "Um café no balcão.", "note": "'al banco' = no balcão (mais barato que ao tavolo)", "audioKey": "v5-chunk-02" },
+ { "it": "Un cappuccino e un cornetto.", "pt": "Um cappuccino e um croissant.", "note": "'cornetto' = croissant italiano; cappuccino só de manhã!", "audioKey": "v5-chunk-03" },
+ { "it": "Con cosa è fatto questo panino?", "pt": "Com que é feito este sanduíche?", "audioKey": "v5-chunk-04" },
+ { "it": "Vorrei un panino con quel formaggio giallo e morbido.", "pt": "Gostaria de um sanduíche com aquele queijo amarelo e macio.", "note": "Circunlóquio: cor + textura para descrever queijo", "audioKey": "v5-chunk-05" },
+ { "it": "Ah, la fontina! Ottima scelta!", "pt": "Ah, a fontina! Ótima escolha!", "audioKey": "v5-chunk-06" },
+ { "it": "E quell'affettato rosso e piccante?", "pt": "E aquele embutido vermelho e apimentado?", "note": "'affettato' = frios fatiados; circunlóquio para salame/piccante/etc.", "audioKey": "v5-chunk-07" },
+ { "it": "Il salame piccante! Lo metto volentieri!", "pt": "O salame apimentado! Coloco com prazer!", "audioKey": "v5-chunk-08" },
+ { "it": "Scusi, cos'è quella cosa verde nel piatto?", "pt": "Desculpe, o que é aquela coisa verde no prato?", "note": "SALVAVITA + circunlóquio para acompanhamentos", "audioKey": "v5-chunk-09" },
+ { "it": "Sono olive! Le vuole sul panino?", "pt": "São azeitonas! Quer no sanduíche?", "audioKey": "v5-chunk-10" },
+ { "it": "Quanto viene?", "pt": "Quanto dá? (informal para preço)", "note": "'quanto viene' = quanto fica/dá (coloquial)", "audioKey": "v5-chunk-11" },
+ { "it": "Lo prendo da portare via.", "pt": "Levo para viagem.", "note": "'da portare via' = para levar/takeaway", "audioKey": "v5-chunk-12" },
+ { "it": "Posso assaggiare prima di scegliere?", "pt": "Posso provar antes de escolher?", "audioKey": "v5-chunk-13" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Buongiorno! Un caffè al banco, per favore.", "pt": "Bom dia! Um café no balcão, por favor.", "audioKey": "v5-d1-l1" },
+ { "speaker": "italian", "it": "Subito! Lo vuoi normale o macchiato?", "pt": "Agora! Quer normal ou pingado?", "audioKey": "v5-d1-l2" },
+ { "speaker": "learner", "it": "Normale, grazie. E vorrei anche un cornetto.", "pt": "Normal, obrigado. E gostaria também de um croissant.", "audioKey": "v5-d1-l3" },
+ { "speaker": "italian", "it": "Vuoto o alla crema?", "pt": "Simples ou com creme?", "audioKey": "v5-d1-l4" },
+ { "speaker": "learner", "it": "Alla crema, per favore! Quant'è?", "pt": "Com creme, por favor! Quanto é?", "audioKey": "v5-d1-l5" },
+ { "speaker": "italian", "it": "Due euro e ottanta. Ecco il caffè!", "pt": "Dois euros e oitenta. Aqui está o café!", "audioKey": "v5-d1-l6" },
+ { "speaker": "learner", "it": "Grazie! Buonissimo come sempre!", "pt": "Obrigado! Muito gostoso como sempre!", "audioKey": "v5-d1-l7" }
+ ],
+ [
+ { "speaker": "learner", "it": "Buongiorno! Vorrei un panino ma non so i nomi dei formaggi.", "pt": "Bom dia! Gostaria de um sanduíche mas não sei os nomes dos queijos.", "audioKey": "v5-d2-l1" },
+ { "speaker": "italian", "it": "Nessun problema! Dimmi che tipo ti piace.", "pt": "Sem problema! Me diga que tipo gosta.", "audioKey": "v5-d2-l2" },
+ { "speaker": "learner", "it": "Mi piace quello giallo, morbido e un po' dolce.", "pt": "Gosto daquele amarelo, macio e um pouco doce.", "audioKey": "v5-d2-l3" },
+ { "speaker": "italian", "it": "Intendi la fontina? O forse il taleggio?", "pt": "Quer dizer a fontina? Ou talvez o taleggio?", "audioKey": "v5-d2-l4" },
+ { "speaker": "learner", "it": "La fontina! Sì! E poi quell'affettato… è rosso, sottile e un po' salato.", "pt": "A fontina! Sim! E depois aquele embutido… é vermelho, fino e um pouco salgado.", "audioKey": "v5-d2-l5" },
+ { "speaker": "italian", "it": "Il prosciutto crudo! Perfetto insieme alla fontina!", "pt": "O presunto cru! Perfeito junto com a fontina!", "audioKey": "v5-d2-l6" },
+ { "speaker": "learner", "it": "Esatto! Lo prendo! Quanto viene?", "pt": "Exato! Eu pego! Quanto dá?", "audioKey": "v5-d2-l7" },
+ { "speaker": "italian", "it": "Sei euro e cinquanta. Lo mangi qui o da portare via?", "pt": "Seis euros e cinquenta. Come aqui ou para levar?", "audioKey": "v5-d2-l8" },
+ { "speaker": "learner", "it": "Da portare via, grazie! Complimenti per la pazienza!", "pt": "Para levar, obrigado! Cumprimentos pela paciência!", "audioKey": "v5-d2-l9" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "In una paninoteca artigianale",
+ "italianRole": "Paninaro appassionato che spiega i prodotti",
+ "goalPt": "Montar um sanduíche descrevendo frios e queijos que não sabe nomear usando circunlóquio",
+ "openerIt": "Buongiorno! Che panino desideri oggi?"
+ },
+ "survivalChecklist": [
+ "Un caffè al banco / Un cappuccino e un cornetto",
+ "Vorrei un panino con quel [colore] [texture]",
+ "Cos'è quella cosa [descrizione]?",
+ "Quanto viene? / Da portare via",
+ "Con cosa è fatto questo?"
+ ]
+}
diff --git a/content/missions/V6.json b/content/missions/V6.json
new file mode 100644
index 0000000..659c8f2
--- /dev/null
+++ b/content/missions/V6.json
@@ -0,0 +1,65 @@
+{
+ "id": "V6",
+ "trail": "viaggio",
+ "cefr": "B1",
+ "titleIt": "In Farmacia e dal Medico",
+ "titlePt": "Na farmácia e no médico",
+ "canDos": [
+ { "id": "v6-cd1", "text": "Consigo descrever sintomas ao médico usando condizionale cortese" },
+ { "id": "v6-cd2", "text": "Consigo informar alergias e pedir medicamentos específicos na farmácia" },
+ { "id": "v6-cd3", "text": "Consigo entender instruções de dosagem e posologia" }
+ ],
+ "chunks": [
+ { "it": "Mi fa male la gola.", "pt": "Minha garganta dói / Estou com dor de garganta.", "audioKey": "v6-chunk-01" },
+ { "it": "Ho la febbre da due giorni.", "pt": "Estou com febre há dois dias.", "note": "'da' + tempo = há (duração)", "audioKey": "v6-chunk-02" },
+ { "it": "Ho il raffreddore e il naso chiuso.", "pt": "Estou resfriado e com o nariz entupido.", "audioKey": "v6-chunk-03" },
+ { "it": "Sono allergico alla penicillina.", "pt": "Sou alérgico a penicilina.", "note": "CRÍTICO: sempre informe alergias; fem. 'allergica'", "audioKey": "v6-chunk-04" },
+ { "it": "Vorrei qualcosa per il mal di testa.", "pt": "Gostaria de algo para dor de cabeça.", "note": "Condizionale 'vorrei' = cortês em contextos formais", "audioKey": "v6-chunk-05" },
+ { "it": "Mi dia qualcosa per la tosse.", "pt": "Me dê algo para a tosse.", "note": "'mi dia' = imperativo formal (Lei)", "audioKey": "v6-chunk-06" },
+ { "it": "Quante volte al giorno devo prenderlo?", "pt": "Quantas vezes por dia devo tomar?", "audioKey": "v6-chunk-07" },
+ { "it": "Va preso a stomaco pieno.", "pt": "Deve ser tomado de estômago cheio.", "note": "'a stomaco pieno/vuoto' = após/antes das refeições", "audioKey": "v6-chunk-08" },
+ { "it": "Saprebbe dirmi dove trovare un medico?", "pt": "Saberia me dizer onde encontrar um médico?", "note": "Condizionale 'saprebbe' = extremamente cortês", "audioKey": "v6-chunk-09" },
+ { "it": "Potrei avere una ricetta?", "pt": "Poderia ter uma receita?", "note": "'ricetta' = receita médica", "audioKey": "v6-chunk-10" },
+ { "it": "Da quanto tempo ha questi sintomi?", "pt": "Há quanto tempo tem estes sintomas?", "note": "Pergunta típica do médico — saiba responder", "audioKey": "v6-chunk-11" },
+ { "it": "Ho anche nausea e mal di stomaco.", "pt": "Também tenho náusea e dor de estômago.", "audioKey": "v6-chunk-12" },
+ { "it": "Scusi, come si dice 'tontura' in italiano?", "pt": "Desculpe, como se diz 'tontura' em italiano?", "note": "SALVAVITA para sintomas que não sabe nomear", "audioKey": "v6-chunk-13" },
+ { "it": "È un farmaco da banco o serve la ricetta?", "pt": "É um medicamento de venda livre ou precisa de receita?", "note": "'da banco' = sem prescrição", "audioKey": "v6-chunk-14" },
+ { "it": "Prenda due compresse al giorno dopo i pasti.", "pt": "Tome dois comprimidos por dia após as refeições.", "note": "'compresse' = comprimidos; 'pastiglie' = pastilhas", "audioKey": "v6-chunk-15" }
+ ],
+ "dialogues": [
+ [
+ { "speaker": "learner", "it": "Buongiorno, dottore. Non sto bene da tre giorni.", "pt": "Bom dia, doutor. Não estou bem há três dias.", "audioKey": "v6-d1-l1" },
+ { "speaker": "italian", "it": "Mi dica. Quali sintomi ha?", "pt": "Me diga. Quais sintomas tem?", "audioKey": "v6-d1-l2" },
+ { "speaker": "learner", "it": "Ho la febbre, mi fa male la gola e ho anche un po' di tosse.", "pt": "Estou com febre, minha garganta dói e também tenho um pouco de tosse.", "audioKey": "v6-d1-l3" },
+ { "speaker": "italian", "it": "Da quanto tempo ha la febbre? È alta?", "pt": "Há quanto tempo tem febre? É alta?", "audioKey": "v6-d1-l4" },
+ { "speaker": "learner", "it": "Da due giorni. Ho misurato trentotto e mezzo.", "pt": "Há dois dias. Medi trinta e oito e meio.", "audioKey": "v6-d1-l5" },
+ { "speaker": "italian", "it": "È un'infezione virale. Le prescrivo un antinfiammatorio. Lo prenda due volte al giorno dopo i pasti.", "pt": "É uma infecção viral. Te prescrevo um anti-inflamatório. Tome duas vezes ao dia após as refeições.", "audioKey": "v6-d1-l6" },
+ { "speaker": "learner", "it": "Grazie, dottore. Sono allergico all'aspirina, va bene questo farmaco?", "pt": "Obrigado, doutor. Sou alérgico a aspirina, este remédio é adequado?", "audioKey": "v6-d1-l7" },
+ { "speaker": "italian", "it": "Tranquillo, non contiene aspirina. Riposi e beva molta acqua!", "pt": "Fique tranquilo, não contém aspirina. Descanse e beba muita água!", "audioKey": "v6-d1-l8" },
+ { "speaker": "learner", "it": "Perfetto, la ringrazio molto! Potrei avere la ricetta?", "pt": "Perfeito, muito obrigado! Poderia ter a receita?", "audioKey": "v6-d1-l9" },
+ { "speaker": "italian", "it": "Ecco la ricetta! Buona guarigione!", "pt": "Aqui está a receita! Boa recuperação!", "audioKey": "v6-d1-l10" }
+ ],
+ [
+ { "speaker": "learner", "it": "Buonasera! Vorrei qualcosa per il mal di testa.", "pt": "Boa noite! Gostaria de algo para dor de cabeça.", "audioKey": "v6-d2-l1" },
+ { "speaker": "italian", "it": "Certo. Ha preferenze? Compresse o bustine?", "pt": "Claro. Tem preferência? Comprimidos ou sachês?", "audioKey": "v6-d2-l2" },
+ { "speaker": "learner", "it": "Compresse, per favore. Ma scusi, ho anche un po' di nausea… c'è qualcosa che aiuta anche quello?", "pt": "Comprimidos, por favor. Mas desculpe, também tenho um pouco de náusea… tem algo que ajude também nisso?", "audioKey": "v6-d2-l3" },
+ { "speaker": "italian", "it": "Sì, questo qui va bene per entrambi. Prenda una compressa ogni otto ore, a stomaco pieno.", "pt": "Sim, este aqui serve para ambos. Tome um comprimido a cada oito horas, de estômago cheio.", "audioKey": "v6-d2-l4" },
+ { "speaker": "learner", "it": "Ogni otto ore, a stomaco pieno. Ho capito. Serve la ricetta?", "pt": "A cada oito horas, de estômago cheio. Entendi. Precisa de receita?", "audioKey": "v6-d2-l5" },
+ { "speaker": "italian", "it": "No, è un farmaco da banco. Sono dodici euro.", "pt": "Não, é medicamento de venda livre. São doze euros.", "audioKey": "v6-d2-l6" },
+ { "speaker": "learner", "it": "Perfetto, pago con la carta. Grazie mille e buona serata!", "pt": "Perfeito, pago com cartão. Muito obrigado e boa noite!", "audioKey": "v6-d2-l7" }
+ ]
+ ],
+ "roleplay": {
+ "settingIt": "Nello studio del medico di base",
+ "italianRole": "Medico di famiglia gentile e professionale",
+ "goalPt": "Descrever sintomas ao médico, informar alergias e entender a prescrição usando condizionale cortese",
+ "openerIt": "Buongiorno! Si accomodi. Mi dica, cosa non va?"
+ },
+ "survivalChecklist": [
+ "Mi fa male… / Ho la febbre / Ho il raffreddore",
+ "Sono allergico/a a…",
+ "Vorrei qualcosa per… / Mi dia qualcosa per…",
+ "Quante volte al giorno? / A stomaco pieno?",
+ "Saprebbe dirmi dove trovare un medico?"
+ ]
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..c9baf88
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,4153 @@
+{
+ "name": "italiano-vivo",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "italiano-vivo",
+ "version": "0.1.0",
+ "workspaces": [
+ "app",
+ "server"
+ ],
+ "devDependencies": {
+ "concurrently": "^9.1.0",
+ "rimraf": "^6.0.1",
+ "typescript": "^5.7.0"
+ }
+ },
+ "app": {
+ "name": "italiano-vivo-app",
+ "version": "0.1.0",
+ "dependencies": {
+ "dexie": "^4.0.11",
+ "dexie-react-hooks": "^1.1.7",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "zustand": "^5.0.2"
+ },
+ "devDependencies": {
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "^4.3.4",
+ "typescript": "^5.7.0",
+ "vite": "^6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.12",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.12.tgz",
+ "integrity": "sha512-2W76mhjrpQFO1lK5e8IvGYB2D0YZ7S2R1Z2BjO2ZUYuW+amcpK/z60U6ZZmzskwoa7sJ6XohP/Wg8H5KxXI/IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.12",
+ "@babel/types": "^7.29.11",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.11",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz",
+ "integrity": "sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.10",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz",
+ "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.12",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.12.tgz",
+ "integrity": "sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.11"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.12",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.12.tgz",
+ "integrity": "sha512-MscXe/NAZcOkYEsRajHZFt3ynsb22PUi6CTrp5U7+RYycR9By84mn9mFN54YgfITiqK4hFx3Ju3tZSyzfpS5ew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.12",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.12",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.11",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.11",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz",
+ "integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.11",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.67.4.tgz",
+ "integrity": "sha512-/v/pUAPOcwp+r1Hjjdg0cBV1TE89CzGKrKh2FgkIQw2QBcIMmVPOCSfuOZStCn4Xh/98HXfo/ht6ztcssctPNg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.67.4.tgz",
+ "integrity": "sha512-6tmmB/P52Vt167FXXEw5auiEUgTtZz7aqUgWHqYXToIUE0HFDY6fTdAyeekjjsNk6O/8dtpSKtClxSj1mMneKQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-x64": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-x64/-/rollup-android-x64-4.67.4.tgz",
+ "integrity": "sha512-8Z9Q4RmotVmuL4llOvqX814JNiOK1oqoBv+glG5KbpRfUiypqzz2/fxIzK4Pq129l4dT5a8Gszmpc0/XPYoFuQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.67.4.tgz",
+ "integrity": "sha512-pnq6hssxTtwuRAl6Kdi/+IDY+b1IhkUetjhUkdLAAJCssjfqtL2Oqy/FXJVJFtAWOu1oRlArIvf0Yk1O92x2HQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.67.4.tgz",
+ "integrity": "sha512-BeIrGZvHMVdJg/N+ohZfFywG/gF41684YhDj2wBvouj7m9ziP1tZDUNwzXRiDY+3WIKngaC4hiUBqVtBPGP5og==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.67.4.tgz",
+ "integrity": "sha512-fRFX/oqNg/g780DKoS8RIfYcZD59Ru5O/bSeK1N7gQljBFrTxJY/q5EX4eMs5SrJf32VgVBVVYCdfPpzAwPPnQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.67.4.tgz",
+ "integrity": "sha512-bSrBRopG1q+0RuOsW0NtYTtIODlvNA3T3ebx5WnQVdSxWIoXFmj+rZdHUHdSLgFdZ5M6VGmekYE7r4RMGQXjfg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.67.4.tgz",
+ "integrity": "sha512-L8IvdrJVtl65g1ozwDSyjGPG+FmibhCdFUwmqTItJLw0S3+Oj4QzFs7rfgTUiK7rsPjkCLdG1/GApFxoynXFJQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.67.4.tgz",
+ "integrity": "sha512-h7PcoQOzC31jshNpQ48J0akB9tsghjkSjSl5AZ6XsVGTxVsln7t7C4SQyw62tNVSaz2SeHgk4VT2ELbL17BSHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.67.4.tgz",
+ "integrity": "sha512-KSPuOBNHoOl7AsVjeY1KZci8+0up0U4TFAHU5IzBYN8P+Z6cB9DZq28+JQycZW7f6WLLa/tSzhV7Rdy95bVi+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.67.4.tgz",
+ "integrity": "sha512-0E/mKmVk1PlQeQYygIwQm0jlwjANTzGSiNBMPrFx+rdFGFj/2Axh4p6XwcrystQIqXZVudTaGsdbDpzYgJ4wSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.67.4.tgz",
+ "integrity": "sha512-Fvw7dOSc2Sa9UOipRZVI0zOgdLCzsjVyD/6JuHXzUHTXFu7mMgmeX/0/bCVBIut1dz+nWz3pV4WpS/Qg0XVnxw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.67.4.tgz",
+ "integrity": "sha512-jzxbVHaE7V3F+P0wEK7UydQtvv7Twfdtx58jI5/3VRx1jJxyrn+qFwfXmqjVFxPhBl+zjaRs+ZJMFJw7vdHaQA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.67.4.tgz",
+ "integrity": "sha512-fhtrzp9p4GjEJVaigaap9g6X9nP6xgJtKiLLXSDgpE18HXufA5aqfltGn+KAqMtVZReJZfOrXm//y5YSA9BWbA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.67.4.tgz",
+ "integrity": "sha512-uynaF7IUHwAckV0S+yC1f5esy9gB+aqCBsPH5BOck32TYdCuAn2lVG5tvUcFnEsrviGEmRZxE1NHSOieUPYJEg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.67.4.tgz",
+ "integrity": "sha512-uaqRFdfb8eIa2bVHN/FE3ndCvR0zV2xv0AwjCfxrqSu6l6fp/54VhjfhslKlf2RIik9L1jwlt4E56PeVX6tQtQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.67.4.tgz",
+ "integrity": "sha512-JZRow7kmxUDEASnxHuVG5itTm+nTXi9BQUABcVUnUEngC+OPwMrpx1uJ9hIA/v+IcAEBL52ktCSPXk5OJn9Jqg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.67.4.tgz",
+ "integrity": "sha512-rdrh4e+Lq9fCNKoDPaCqeHXbQCe7yl77Ukl45tlSClwYdZ1gpFGZz1XLJdYtY++FRlfL5aHvb6/0n3GDW/Nxsw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.67.4.tgz",
+ "integrity": "sha512-wL7MS4r0fYcxluF3ne2plofHZiB/LsqNCnyaUlyiKpAJLb6agPSFz7DEFCfkonWAZXMrLwCmC08b2ZEnEdJkog==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.67.4.tgz",
+ "integrity": "sha512-4H0XHfd2VufGjX7NjUyeue78sD0bRIoKSGjGqO+F5nSvL6jN48NAU7esdmtFEYxZXwObLtt3MJ/kHS0jASMW3g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.67.4.tgz",
+ "integrity": "sha512-osRHQbE55pZpCLetpMsFmhkapyZnAUH73qRKMh+npi3boUom6u6hdaTTPSxlInXxwJHsn9r2uDhdz0V7Yoxx2A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.67.4.tgz",
+ "integrity": "sha512-95+exYEyOwrZePsE0czi4q7X5lxKPa21+lmyAZlwbYENITQ0Unk4HAQAFp/p+KhXBHBx9mUABXRYjOC5A1wY3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.67.4.tgz",
+ "integrity": "sha512-FOXOcKhNQpO4Po+Ynan6IbtM67SrXZbgpRJWVYwz53CT5veI7sVzDwVBmWS65N5Imv+wK3SVhIPAl+C5DXXNCg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.67.4.tgz",
+ "integrity": "sha512-cklKh0gJOkAZQcWziQa3hrjxZKN+PCHZ7SPTyGJDAiBVr05ZXlEjGFCG7zFJMaVibxsmzAe1FimN4u/HBns9+Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.67.4.tgz",
+ "integrity": "sha512-AegwxJPc9bvUp8fQtlsUHkFMOMkGylWU7glyL+YD4vwG+QaYoGXYLSsQoeTD7kvoLZrVlMWNVhTg1dedY5C1VQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.67.4.tgz",
+ "integrity": "sha512-RJZnrwTWj7Iq2ooBwoGbDdC65tDwYawneGcAfFMJqVUrRZMcskka6nTkBpXR/47G7kzn3EW2qupExnaBy11vwg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.19",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz",
+ "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/express": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz",
+ "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.20.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz",
+ "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.13",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
+ "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "form-data": "^4.0.4"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.3.0"
+ }
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "license": "MIT",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/agentkeepalive": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
+ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "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/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "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/baseline-browser-mapping": {
+ "version": "2.11.34",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.34.tgz",
+ "integrity": "sha512-Aqr8mA1kIvyJ3UY5YAY7ib8LSo1DmDMbMnVqGPtsKW1LxaFQFwn9Eb3yvq2fUKJsRvaXjnAV7R5osqETapzNiA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.9",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.9.tgz",
+ "integrity": "sha512-FISIQxV5u91Y9gvuFq3q/tA/qTqLJn3AhRcEvyozyhJTh5OBFFtv827AdHU7Rbyct4r+Ba1/99oFa1Y3yuFsdw==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.16.0",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.29.5",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.5.tgz",
+ "integrity": "sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.33",
+ "caniuse-lite": "^1.0.30001819",
+ "electron-to-chromium": "^1.5.464",
+ "node-releases": "^2.0.60",
+ "update-browserslist-db": "^1.3.4"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "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==",
+ "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/caniuse-lite": {
+ "version": "1.0.30001820",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001820.tgz",
+ "integrity": "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/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/chalk/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/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concurrently": {
+ "version": "9.2.5",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.5.tgz",
+ "integrity": "sha512-YMZlvWrTPQEhjKp60XSmMGt60+pffbv6DyRnOMvVO98+J7aF8w8Ui9FhdcOwvSixc/kyR6SoVvmejcn/2lZBqg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "4.1.2",
+ "rxjs": "7.8.2",
+ "shell-quote": "1.12.0",
+ "supports-color": "8.1.1",
+ "tree-kill": "1.2.2",
+ "yargs": "17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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/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/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dexie": {
+ "version": "4.4.7",
+ "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.4.7.tgz",
+ "integrity": "sha512-wemFiCl3DTCnfzyqepUTX1ugbYbkTrw3aEZOG7YjAOUoEQB5g5PrEywPzk5JpbX/SxqQ9IvjUpxCDIEjoOWEfA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/dexie-react-hooks": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.7.tgz",
+ "integrity": "sha512-Lwv5W0Hk+uOW3kGnsU9GZoR1er1B7WQ5DSdonoNG+focTNeJbHW6vi6nBoX534VKI3/uwHebYzSw1fwY6a7mTw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@types/react": ">=16",
+ "dexie": "^3.2 || ^4.0.1-alpha",
+ "react": ">=16"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "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==",
+ "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/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.472",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.472.tgz",
+ "integrity": "sha512-s4+nfT29b0FKpVKoaIxl2pWsoNcn7A/nMPSrxayw4T3BsmPeV2lF9Y06BsAQeMGhCTQ4nYJ5XST7jkqe4ZEqFw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.1.tgz",
+ "integrity": "sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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==",
+ "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-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==",
+ "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==",
+ "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/esbuild": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
+ "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.3",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz",
+ "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.5",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.13",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.16.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/form-data": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data-encoder": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
+ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
+ "license": "MIT"
+ },
+ "node_modules/formdata-node": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
+ "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "1.0.0",
+ "web-streams-polyfill": "4.0.0-beta.3"
+ },
+ "engines": {
+ "node": ">= 12.20"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/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/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "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==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob": {
+ "version": "13.0.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
+ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minimatch": "^10.2.2",
+ "minipass": "^7.1.3",
+ "path-scurry": "^2.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "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-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "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==",
+ "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/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/italiano-vivo-app": {
+ "resolved": "app",
+ "link": true
+ },
+ "node_modules/italiano-vivo-server": {
+ "resolved": "server",
+ "link": true
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "11.5.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz",
+ "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.8"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.63",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.63.tgz",
+ "integrity": "sha512-6li2QanLsAXpIH2R6j77MzRyvOyrvdTqlKtO4CgBdHGNvN99ZZfyxszHc5o5+IZBWD5u6Hzwg83JoGXmqZNG9Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.2.tgz",
+ "integrity": "sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/openai": {
+ "version": "4.104.0",
+ "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz",
+ "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/node": "^18.11.18",
+ "@types/node-fetch": "^2.6.4",
+ "abort-controller": "^3.0.0",
+ "agentkeepalive": "^4.2.1",
+ "form-data-encoder": "1.7.2",
+ "formdata-node": "^4.3.2",
+ "node-fetch": "^2.6.7"
+ },
+ "bin": {
+ "openai": "bin/cli"
+ },
+ "peerDependencies": {
+ "ws": "^8.18.0",
+ "zod": "^3.23.8"
+ },
+ "peerDependenciesMeta": {
+ "ws": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/openai/node_modules/@types/node": {
+ "version": "18.19.130",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
+ "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/openai/node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "license": "MIT"
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+ "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==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz",
+ "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.20",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
+ "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.16.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.2.tgz",
+ "integrity": "sha512-VYdnB5S7/0IeXzBacUtwNAaM6d7Y/PgYnNC7bJCI6meqA/OgnsQbZMtVW8Dt2D1PHgbHGtYmhSoEIgvt7xZ2ZA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz",
+ "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz",
+ "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.28.0"
+ },
+ "peerDependencies": {
+ "react": "^19.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz",
+ "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "glob": "^13.0.3",
+ "package-json-from-dist": "^1.0.1"
+ },
+ "bin": {
+ "rimraf": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.67.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.67.4.tgz",
+ "integrity": "sha512-Lsv3oKcz5jgOQJej/p7nfwbVEdAbDT+z6OPf/SCdI+nF+q2ENp0pTKT35ZwXAmH8g45mfrIiJeEyAqKYSYoL9Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.10"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.67.4",
+ "@rollup/rollup-android-arm64": "4.67.4",
+ "@rollup/rollup-android-x64": "4.67.4",
+ "@rollup/rollup-darwin-arm64": "4.67.4",
+ "@rollup/rollup-darwin-x64": "4.67.4",
+ "@rollup/rollup-freebsd-arm64": "4.67.4",
+ "@rollup/rollup-freebsd-x64": "4.67.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.67.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.67.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.67.4",
+ "@rollup/rollup-linux-arm64-musl": "4.67.4",
+ "@rollup/rollup-linux-loong64-gnu": "4.67.4",
+ "@rollup/rollup-linux-loong64-musl": "4.67.4",
+ "@rollup/rollup-linux-ppc64-gnu": "4.67.4",
+ "@rollup/rollup-linux-ppc64-musl": "4.67.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.67.4",
+ "@rollup/rollup-linux-riscv64-musl": "4.67.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.67.4",
+ "@rollup/rollup-linux-x64-gnu": "4.67.4",
+ "@rollup/rollup-linux-x64-musl": "4.67.4",
+ "@rollup/rollup-openbsd-x64": "4.67.4",
+ "@rollup/rollup-openharmony-arm64": "4.67.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.67.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.67.4",
+ "@rollup/rollup-win32-x64-gnu": "4.67.4",
+ "@rollup/rollup-win32-x64-msvc": "4.67.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "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/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",
+ "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz",
+ "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shell-quote": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz",
+ "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "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==",
+ "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==",
+ "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==",
+ "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.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz",
+ "integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/tsx": {
+ "version": "4.23.15",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz",
+ "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.28.0"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz",
+ "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==",
+ "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/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.5.tgz",
+ "integrity": "sha512-4enwjFfi4IwtEh0w+jPzpLVOFQiPHmmzPxcHXQOyEur47o6u0jE573DmGpjA+7irXoMLYRZD5ZaFUEneS3abPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/web-streams-polyfill": {
+ "version": "4.0.0-beta.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
+ "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.16",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.16.tgz",
+ "integrity": "sha512-Uv7e327ihlCfayfja3hDc0M64Upev1LWBKpRobdK4NyovDDPeeX4imWXc/wzC1yc6fkYPoAY/ncKVPAs2jrtQw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ },
+ "server": {
+ "name": "italiano-vivo-server",
+ "version": "0.1.0",
+ "dependencies": {
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "openai": "^4.77.0",
+ "zod": "^3.24.1"
+ },
+ "devDependencies": {
+ "@types/cors": "^2.8.17",
+ "@types/express": "^5.0.0",
+ "@types/node": "^22.10.0",
+ "tsx": "^4.19.2",
+ "typescript": "^5.7.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0e37492
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "italiano-vivo",
+ "version": "0.1.0",
+ "private": true,
+ "description": "App de italiano situacional-first com voz livre e scoring de inteligibilidade",
+ "workspaces": [
+ "app",
+ "server"
+ ],
+ "scripts": {
+ "dev": "concurrently -n app,server -c blue,green \"npm run dev -w app\" \"npm run dev -w server\"",
+ "build": "npm run build -w server && npm run build -w app",
+ "typecheck": "npm run typecheck -w server && npm run typecheck -w app",
+ "clean": "rimraf app/dist server/dist"
+ },
+ "devDependencies": {
+ "concurrently": "^9.1.0",
+ "rimraf": "^6.0.1",
+ "typescript": "^5.7.0"
+ }
+}
diff --git a/server/package.json b/server/package.json
new file mode 100644
index 0000000..cdbb99d
--- /dev/null
+++ b/server/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "italiano-vivo-server",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "tsx watch src/index.ts",
+ "build": "tsc -p tsconfig.json",
+ "start": "node dist/index.js",
+ "typecheck": "tsc --noEmit -p tsconfig.json"
+ },
+ "dependencies": {
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "openai": "^4.77.0",
+ "zod": "^3.24.1"
+ },
+ "devDependencies": {
+ "@types/cors": "^2.8.17",
+ "@types/express": "^5.0.0",
+ "@types/node": "^22.10.0",
+ "tsx": "^4.19.2",
+ "typescript": "^5.7.0"
+ }
+}
diff --git a/server/src/index.ts b/server/src/index.ts
new file mode 100644
index 0000000..3e84e60
--- /dev/null
+++ b/server/src/index.ts
@@ -0,0 +1,78 @@
+/**
+ * Servidor Express do Italiano Vivo.
+ * Proxy seguro para LiteLLM — a chave NUNCA vai para o bundle do app.
+ */
+import "dotenv/config";
+import express from "express";
+import cors from "cors";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { judgeRouter } from "./routes/judge.js";
+import { transcribeRouter } from "./routes/transcribe.js";
+import { ttsRouter } from "./routes/tts.js";
+
+const app = express();
+const PORT = Number(process.env.PORT) || 8787;
+
+// Resolve repo root (works for both dist/index.js and src/index.ts via tsx)
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const REPO_ROOT = path.resolve(__dirname, "..", "..");
+
+// Middleware
+app.use(cors());
+app.use(express.json({ limit: "10mb" }));
+
+// Health check
+app.get("/health", (_req, res) => {
+ res.json({ status: "ok", timestamp: new Date().toISOString() });
+});
+
+// Static: /audio -> /audio (long cache, immutable mp3 files)
+app.use(
+ "/audio",
+ express.static(path.join(REPO_ROOT, "audio"), {
+ maxAge: "1y",
+ immutable: true,
+ fallthrough: true,
+ }),
+);
+
+// Static: /content -> /content (index.json + mission files)
+app.use(
+ "/content",
+ express.static(path.join(REPO_ROOT, "content"), {
+ maxAge: "1h",
+ etag: true,
+ fallthrough: true,
+ }),
+);
+
+// Rotas da API
+app.use("/api/judge", judgeRouter);
+app.use("/api/transcribe", transcribeRouter);
+app.use("/api/tts-proxy", ttsRouter);
+
+// Error handler padrão
+app.use(
+ (
+ err: Error & { status?: number },
+ _req: express.Request,
+ res: express.Response,
+ _next: express.NextFunction,
+ ) => {
+ const status = err.status ?? 500;
+ console.error(`[ERRO ${status}]`, err.message);
+ res.status(status).json({
+ error: err.message || "Erro interno do servidor",
+ ...(process.env.NODE_ENV !== "production" && { stack: err.stack }),
+ });
+ },
+);
+
+app.listen(PORT, () => {
+ console.log(`🇮🇹 Italiano Vivo server rodando em http://localhost:${PORT}`);
+ console.log(` LiteLLM base: ${process.env.LITELLM_BASE_URL || "(não configurado)"}`);
+});
+
+export { app };
diff --git a/server/src/lib/llm.ts b/server/src/lib/llm.ts
new file mode 100644
index 0000000..1b0b481
--- /dev/null
+++ b/server/src/lib/llm.ts
@@ -0,0 +1,100 @@
+/**
+ * Cliente singleton do LiteLLM via OpenAI SDK.
+ * - chatJSON: força resposta JSON, valida com Zod, retry 2x com backoff,
+ * fallback automático de modelo em caso de quota/429.
+ */
+import OpenAI from "openai";
+import type { z } from "zod";
+
+const client = new OpenAI({
+ baseURL: process.env.LITELLM_BASE_URL || "http://10.8.0.1:4000/v1",
+ apiKey: process.env.LITELLM_API_KEY || "sk-placeholder",
+});
+
+export function getClient(): OpenAI {
+ return client;
+}
+
+export const MODELS = {
+ chat: process.env.CHAT_MODEL || "qwen3.8-max",
+ fast: process.env.FAST_MODEL || "qwen3.8-flash",
+ omni: process.env.OMNI_MODEL || "qwen3-omni-flash",
+} as const;
+
+/** Palavras-chave que indicam erro de quota/rate-limit para fallback */
+const QUOTA_PATTERNS = ["quota", "429", "rate limit", "rate_limit", "exceeded", "insufficient"];
+
+function isQuotaError(error: unknown): boolean {
+ if (!(error instanceof Error)) return false;
+ const msg = error.message.toLowerCase();
+ return QUOTA_PATTERNS.some((p) => msg.includes(p));
+}
+
+/** Backoff leve: 500ms, 1500ms */
+async function sleep(ms: number): Promise {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+export interface ChatJSONOptions {
+ model?: string;
+ system: string;
+ user: string;
+ schema: T;
+ /** Se true, tenta fallback para FAST_MODEL em caso de quota */
+ enableFallback?: boolean;
+}
+
+/**
+ * Chama o LLM forçando JSON, valida com Zod, retry até 2x com backoff.
+ * Em caso de quota/429 e enableFallback=true, troca para FAST_MODEL.
+ */
+export async function chatJSON(
+ opts: ChatJSONOptions,
+): Promise> {
+ const { system, user, schema, enableFallback = true } = opts;
+ let model = opts.model ?? MODELS.chat;
+ const maxRetries = 2;
+ const backoffs = [500, 1500];
+
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
+ try {
+ const response = await client.chat.completions.create({
+ model,
+ messages: [
+ { role: "system", content: system },
+ { role: "user", content: user },
+ ],
+ response_format: { type: "json_object" },
+ temperature: 0.3,
+ });
+
+ const raw = response.choices[0]?.message?.content;
+ if (!raw) {
+ throw new Error("Resposta vazia do LLM");
+ }
+
+ const parsed = JSON.parse(raw);
+ const validated = schema.parse(parsed);
+ return validated;
+ } catch (error) {
+ // Fallback de modelo em caso de quota
+ if (isQuotaError(error) && enableFallback && model !== MODELS.fast) {
+ console.warn(`[LLM] Quota/429 em ${model}, fallback para ${MODELS.fast}`);
+ model = MODELS.fast;
+ continue;
+ }
+
+ // Retry com backoff
+ if (attempt < maxRetries) {
+ const delay = backoffs[attempt] ?? 1500;
+ console.warn(`[LLM] Tentativa ${attempt + 1} falhou, retry em ${delay}ms:`, error);
+ await sleep(delay);
+ continue;
+ }
+
+ throw error;
+ }
+ }
+
+ throw new Error("chatJSON: esgotou retries");
+}
diff --git a/server/src/routes/judge.ts b/server/src/routes/judge.ts
new file mode 100644
index 0000000..0c6f685
--- /dev/null
+++ b/server/src/routes/judge.ts
@@ -0,0 +1,166 @@
+/**
+ * POST /api/judge — Motor de feedback em 3 eixos.
+ * Avalia a fala do aluno com tolerância estilo criança:
+ * erro gramatical que não impede entendimento = sucesso comunicativo.
+ */
+import { Router } from "express";
+import { z } from "zod";
+import { chatJSON, MODELS } from "../lib/llm.js";
+
+export const judgeRouter = Router();
+
+// Schema de entrada
+const JudgeInputSchema = z.object({
+ missionId: z.string(),
+ scenario: z.string(),
+ learnerGoalPt: z.string(),
+ expectedIt: z.string().optional(),
+ utteranceText: z.string().optional(),
+ transcriptRaw: z.string().optional(),
+ audioBase64: z.string().optional(),
+});
+
+// Schema de saída estruturada
+// Schema leniente: LLM pode retornar campos vazios ou estrutura diferente
+// Usamos preprocess para normalizar a entrada
+const GrammarErrorSchema = z.preprocess(
+ (val) => {
+ if (typeof val !== "object" || val === null) return {};
+ const obj = val as Record;
+ return {
+ it_said: typeof obj.it_said === "string" ? obj.it_said : "",
+ problem_pt: typeof obj.problem_pt === "string" ? obj.problem_pt : "",
+ fix_it: typeof obj.fix_it === "string" ? obj.fix_it : "",
+ severity: ["blocks_understanding", "minor", "cosmetic"].includes(obj.severity as string)
+ ? obj.severity
+ : "minor",
+ };
+ },
+ z.object({
+ it_said: z.string(),
+ problem_pt: z.string(),
+ fix_it: z.string(),
+ severity: z.enum(["blocks_understanding", "minor", "cosmetic"]),
+ }),
+);
+
+const RescueCardSchema = z.object({
+ come_si_dice: z.string(),
+ frase_it: z.string(),
+ explicacao_pt: z.string(),
+});
+
+const JudgeOutputSchema = z.object({
+ meaning_conveyed: z.enum(["full", "partial", "failed"]),
+ naturalness: z.number().min(0).max(100),
+ natural_version: z.string(),
+ grammar: z.array(GrammarErrorSchema),
+ rescue_card: RescueCardSchema.nullable().optional(),
+ next_move: z.preprocess(
+ (val) => {
+ if (typeof val === "string") return { italian: val, portuguese: "" };
+ if (typeof val === "object" && val !== null) {
+ const obj = val as Record;
+ return {
+ italian: typeof obj.italian === "string" ? obj.italian : "",
+ portuguese: typeof obj.portuguese === "string" ? obj.portuguese : "",
+ };
+ }
+ return { italian: "", portuguese: "" };
+ },
+ z.object({
+ italian: z.string(),
+ portuguese: z.string(),
+ }),
+ ),
+ praise_or_push: z.string(),
+});
+
+/** System prompt do juiz — a regra de ouro do produto */
+const JUDGE_SYSTEM_PROMPT = `Você é o avaliador de italiano do app Italiano Vivo. Sua filosofia é TOLERÂNCIA ESTILO CRIANÇA: o aluno está aprendendo como uma criança aprende — errando mas se comunicando.
+
+## Regra de Ouro
+meaning_conveyed=full significa que a INTENÇÃO COMUNICATIVA foi atingida, MESMO com erros gramaticais graves. Penalize ABANDONAR A MENSAGEM, não errar conjugação. Um italiano na rua entende "io volere acqua" perfeitamente — isso é sucesso comunicativo.
+
+## Os 3 Eixos (sempre avalie todos separadamente)
+
+1. **meaning_conveyed**: "full" | "partial" | "failed"
+ - full = o interlocutor italiano entenderia a intenção sem ambiguidade
+ - partial = parte da mensagem passou, mas algo importante se perdeu
+ - failed = a mensagem não foi transmitida ou foi completamente mal-entendida
+
+2. **naturalness** (0-100) + natural_version
+ - Quão próximo soou de como um italiano nativo diria?
+ - natural_version: reescreva a frase COMO UM ITALIANO DIRIA, natural e coloquial quando apropriado
+
+3. **grammar**: array de erros encontrados
+ - severity "blocks_understanding": erro que GEROU ou GERARIA mal-entendido real
+ - severity "minor": erro gramatical que não impediu entendimento (conjugação, artigo, preposição)
+ - severity "cosmetic": sotaque, pronúncia leve, hesitação
+ - Se não houver erros, retorne array vazio []
+
+## Sensibilidade Especial
+- Geminadas italianas: pala/balla, nono/nonna, casa/cassa — marque como minor se o aluno confundiu, NUNCA como blocks_understanding a menos que o contexto mostre ambiguidade real
+- Sotaque brasileiro: seja generoso. O objetivo é inteligibilidade, não perfeição fonética
+- Circunlóquio e estratégias de comunicação ("non so la parola, è una cosa che...") são POSITIVOS — mostram competência comunicativa
+
+## rescue_card (opcional)
+Forneça SOMENTE quando o aluno claramente travou por falta de vocabulário (pausa longa, abandonou a frase, pediu ajuda). Ensine o salvavita aplicável:
+- come_si_dice: a expressão de resgate em italiano
+- frase_it: exemplo completo
+- explicacao_pt: quando usar, em PT-BR simples
+
+## next_move
+Como o interlocutor italiano responderia NATURALMENTE à fala do aluno. Escreva em italiano + tradução PT. Isso mantém o diálogo vivo.
+
+## praise_or_push
+UMA frase curta em PT-BR. NUNCA diga "Errado". Use padrões como:
+- "Quase lá! Um italiano diria: …"
+- "Boa tentativa! Na Itália você ouviria: …"
+- "Isso mesmo! Agora tente também: …"
+- "Você se fez entender! Para soar mais natural: …"
+
+Responda SEMPRE em JSON válido seguindo o schema.`;
+
+judgeRouter.post("/", async (req, res, next) => {
+ try {
+ const input = JudgeInputSchema.parse(req.body);
+
+ // Se veio áudio sem transcrição, transcrever primeiro via OMNI
+ let transcript = input.transcriptRaw ?? input.utteranceText ?? "";
+
+ if (!transcript && input.audioBase64) {
+ const { transcribeAudio } = await import("./transcribe.js");
+ const result = await transcribeAudio(input.audioBase64, "audio/webm");
+ transcript = result.text;
+ }
+
+ if (!transcript) {
+ res.status(400).json({ error: "Forneça utteranceText, transcriptRaw ou audioBase64" });
+ return;
+ }
+
+ const userPrompt = `## Contexto da Missão
+Missão: ${input.missionId}
+Cenário: ${input.scenario}
+Objetivo do aluno (PT): ${input.learnerGoalPt}
+${input.expectedIt ? `Frase esperada (IT): ${input.expectedIt}` : ""}
+
+## Fala do Aluno
+Transcrição: "${transcript}"
+
+Avalie seguindo os 3 eixos. Lembre-se: tolerância estilo criança.`;
+
+ const verdict = await chatJSON({
+ model: MODELS.chat,
+ system: JUDGE_SYSTEM_PROMPT,
+ user: userPrompt,
+ schema: JudgeOutputSchema,
+ enableFallback: true,
+ });
+
+ res.json(verdict);
+ } catch (error) {
+ next(error);
+ }
+});
diff --git a/server/src/routes/transcribe.ts b/server/src/routes/transcribe.ts
new file mode 100644
index 0000000..4f58be4
--- /dev/null
+++ b/server/src/routes/transcribe.ts
@@ -0,0 +1,83 @@
+/**
+ * POST /api/transcribe — ASR via OMNI_MODEL (qwen3-omni-flash).
+ * Formato OpenAI-compatible com input_audio.
+ */
+import { Router } from "express";
+import { z } from "zod";
+import type { ChatCompletionContentPartInputAudio, ChatCompletionContentPartText } from "openai/resources/chat/completions";
+import { getClient, MODELS } from "../lib/llm.js";
+
+export const transcribeRouter = Router();
+
+const TranscribeInputSchema = z.object({
+ audioBase64: z.string().min(1),
+ mime: z.string().default("audio/webm"),
+});
+
+const TranscribeOutputSchema = z.object({
+ text: z.string(),
+ words: z.array(z.string()).optional(),
+});
+
+/**
+ * Transcreve áudio base64 usando o modelo omni.
+ * Exportada para uso interno pelo /judge quando recebe áudio sem transcrição.
+ */
+export async function transcribeAudio(
+ audioBase64: string,
+ mime: string,
+): Promise<{ text: string; words?: string[] }> {
+ const client = getClient();
+
+ // Determinar formato do áudio para a API
+ // O SDK openai tipa format como 'wav' | 'mp3', mas o modelo omni via LiteLLM
+ // aceita outros formatos em runtime. Mapeamos para os tipos do SDK.
+ const formatMap: Record = {
+ "audio/webm": "mp3", // fallback: webm não é suportado pelo SDK, usa mp3 como proxy
+ "audio/mp3": "mp3",
+ "audio/mpeg": "mp3",
+ "audio/wav": "wav",
+ "audio/ogg": "mp3", // fallback
+ "audio/mp4": "mp3", // fallback
+ };
+ const format = formatMap[mime] ?? "mp3";
+
+ const audioPart: ChatCompletionContentPartInputAudio = {
+ type: "input_audio",
+ input_audio: {
+ data: audioBase64,
+ format,
+ },
+ };
+ const textPart: ChatCompletionContentPartText = {
+ type: "text",
+ text: "Trascrivi esattamente quello che senti in italiano. Se non riesci a capire qualcosa, scrivi [incomprensibile]. Rispondi SOLO con la trascrizione, niente altro.",
+ };
+
+ const response = await client.chat.completions.create({
+ model: MODELS.omni,
+ messages: [
+ {
+ role: "user",
+ content: [audioPart, textPart],
+ },
+ ],
+ temperature: 0.1,
+ });
+
+ const rawText = response.choices[0]?.message?.content ?? "";
+
+ return TranscribeOutputSchema.parse({
+ text: rawText.trim(),
+ });
+}
+
+transcribeRouter.post("/", async (req, res, next) => {
+ try {
+ const input = TranscribeInputSchema.parse(req.body);
+ const result = await transcribeAudio(input.audioBase64, input.mime);
+ res.json(result);
+ } catch (error) {
+ next(error);
+ }
+});
diff --git a/server/src/routes/tts.ts b/server/src/routes/tts.ts
new file mode 100644
index 0000000..48cba06
--- /dev/null
+++ b/server/src/routes/tts.ts
@@ -0,0 +1,69 @@
+/**
+ * GET /api/tts-proxy?text=...&voice=elsa|diego
+ * Serve arquivos de áudio pré-gerados do cache local.
+ * Os arquivos são gerados offline pelo pipeline Python (edge-tts).
+ * Nome do arquivo: hash(text+voz).mp3 dentro de server/cache/audio/
+ */
+import { Router } from "express";
+import { createHash } from "node:crypto";
+import { readFile, access } from "node:fs/promises";
+import { join, dirname } from "node:path";
+import { fileURLToPath } from "node:url";
+
+export const ttsRouter = Router();
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const CACHE_DIR = join(__dirname, "..", "..", "cache", "audio");
+
+type Voice = "elsa" | "diego";
+
+function isValidVoice(v: string): v is Voice {
+ return v === "elsa" || v === "diego";
+}
+
+function audioFilename(text: string, voice: Voice): string {
+ const hash = createHash("sha256")
+ .update(`${voice}:${text}`)
+ .digest("hex")
+ .slice(0, 16);
+ return `${hash}.mp3`;
+}
+
+ttsRouter.get("/", async (req, res, next) => {
+ try {
+ const text = typeof req.query.text === "string" ? req.query.text : "";
+ const voice = typeof req.query.voice === "string" ? req.query.voice : "elsa";
+
+ if (!text) {
+ res.status(400).json({ error: "Parâmetro 'text' é obrigatório" });
+ return;
+ }
+
+ if (!isValidVoice(voice)) {
+ res.status(400).json({ error: "Voz deve ser 'elsa' ou 'diego'" });
+ return;
+ }
+
+ const filename = audioFilename(text, voice);
+ const filepath = join(CACHE_DIR, filename);
+
+ // Verificar se o arquivo existe no cache
+ try {
+ await access(filepath);
+ } catch {
+ res.status(404).json({
+ error: "Áudio não encontrado no cache",
+ hint: "Gere os áudios offline com o pipeline Python (edge-tts)",
+ requestedFile: filename,
+ });
+ return;
+ }
+
+ const buffer = await readFile(filepath);
+ res.setHeader("Content-Type", "audio/mpeg");
+ res.setHeader("Cache-Control", "public, max-age=86400");
+ res.send(buffer);
+ } catch (error) {
+ next(error);
+ }
+});
diff --git a/server/tsconfig.json b/server/tsconfig.json
new file mode 100644
index 0000000..e43f6f7
--- /dev/null
+++ b/server/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "lib": ["ES2022"],
+ "outDir": "dist",
+ "rootDir": "src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/tools/gen-audio.mjs b/tools/gen-audio.mjs
new file mode 100644
index 0000000..50bb6e1
--- /dev/null
+++ b/tools/gen-audio.mjs
@@ -0,0 +1,259 @@
+#!/usr/bin/env node
+/**
+ * Generate MP3 audio files from content/index.json using edge-tts.
+ * Idempotent: skips files that already have >0 bytes.
+ * Concurrency: max 4 parallel processes.
+ * Retry: up to 2 retries per failure.
+ */
+
+import { readFileSync, statSync, renameSync, unlinkSync, copyFileSync } from 'node:fs';
+import { join, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { execFile } from 'node:child_process';
+import { tmpdir } from 'node:os';
+import { randomBytes } from 'node:crypto';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const ROOT = join(__dirname, '..');
+const CONTENT_PATH = join(ROOT, 'content', 'index.json');
+const AUDIO_DIR = join(ROOT, 'audio');
+
+const VOICE_ELSA = 'it-IT-ElsaNeural'; // chunks + learner
+const VOICE_DIEGO = 'it-IT-DiegoNeural'; // italian speaker
+
+const MAX_CONCURRENCY = 4;
+const MAX_RETRIES = 2;
+const RETRY_DELAY_MS = 1000;
+
+/**
+ * Extract all unique {audioKey, text, voice} entries from index.json.
+ */
+function extractEntries() {
+ const data = JSON.parse(readFileSync(CONTENT_PATH, 'utf8'));
+ const seen = new Map(); // audioKey -> {text, voice}
+
+ for (const mission of data.missions) {
+ // Chunks: always Elsa
+ if (mission.chunks) {
+ for (const chunk of mission.chunks) {
+ if (chunk.audioKey && !seen.has(chunk.audioKey)) {
+ seen.set(chunk.audioKey, {
+ text: chunk.it,
+ voice: VOICE_ELSA,
+ });
+ }
+ }
+ }
+
+ // Dialogues: voice depends on speaker
+ if (mission.dialogues) {
+ for (const dialogue of mission.dialogues) {
+ if (!Array.isArray(dialogue)) continue;
+ for (const line of dialogue) {
+ if (line.audioKey && !seen.has(line.audioKey)) {
+ const voice = line.speaker === 'italian' ? VOICE_DIEGO : VOICE_ELSA;
+ seen.set(line.audioKey, {
+ text: line.it,
+ voice,
+ });
+ }
+ }
+ }
+ }
+ }
+
+ return [...seen.entries()].map(([audioKey, { text, voice }]) => ({
+ audioKey,
+ text,
+ voice,
+ }));
+}
+
+/**
+ * Check if a file exists and has size > minBytes.
+ */
+function hasAudio(filePath, minBytes = 0) {
+ try {
+ const st = statSync(filePath);
+ return st.size > minBytes;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Generate one audio file via edge-tts.
+ * Returns { ok: true } or { ok: false, error: string }.
+ */
+async function generateOne(audioKey, text, voice) {
+ const outPath = join(AUDIO_DIR, `${audioKey}.mp3`);
+ const tmpName = `tts_${randomBytes(6).toString('hex')}.mp3`;
+ const tmpPath = join(tmpdir(), tmpName);
+
+ const args = [
+ '-m', 'edge_tts',
+ '--voice', voice,
+ '--text', text,
+ '--write-media', tmpPath,
+ ];
+
+ return new Promise((resolve) => {
+ execFile('python', args, { timeout: 30000 }, (err, _stdout, stderr) => {
+ if (err) {
+ resolve({ ok: false, error: stderr || err.message });
+ return;
+ }
+
+ // Verify tmp file was created with content
+ try {
+ const st = statSync(tmpPath);
+ if (st.size < 500) {
+ // Too small, likely failed silently
+ try { unlinkSync(tmpPath); } catch {}
+ resolve({ ok: false, error: `Output too small (${st.size} bytes)` });
+ return;
+ }
+ } catch {
+ resolve({ ok: false, error: 'Temp file not created' });
+ return;
+ }
+
+ // Move to final location (copy+delete for cross-device safety)
+ try {
+ try {
+ renameSync(tmpPath, outPath);
+ } catch (renameErr) {
+ if (renameErr.code === 'EXDEV') {
+ copyFileSync(tmpPath, outPath);
+ unlinkSync(tmpPath);
+ } else {
+ throw renameErr;
+ }
+ }
+ resolve({ ok: true });
+ } catch (moveErr) {
+ try { unlinkSync(tmpPath); } catch {}
+ resolve({ ok: false, error: `Move failed: ${moveErr.message}` });
+ }
+ });
+ });
+}
+
+/**
+ * Sleep helper.
+ */
+function sleep(ms) {
+ return new Promise((r) => setTimeout(r, ms));
+}
+
+/**
+ * Process entries with bounded concurrency.
+ */
+async function processAll(entries) {
+ let generated = 0;
+ let skipped = 0;
+ let failed = 0;
+ const failures = [];
+
+ // Filter to only those needing generation
+ const todo = [];
+ for (const entry of entries) {
+ const filePath = join(AUDIO_DIR, `${entry.audioKey}.mp3`);
+ if (hasAudio(filePath, 0)) {
+ skipped++;
+ } else {
+ todo.push(entry);
+ }
+ }
+
+ console.log(`Total keys: ${entries.length}`);
+ console.log(`Already exist (>0 bytes): ${skipped}`);
+ console.log(`To generate: ${todo.length}`);
+ console.log(`Concurrency: ${MAX_CONCURRENCY}`);
+ console.log('---');
+
+ // Bounded concurrency pool
+ let idx = 0;
+ const results = [];
+
+ async function worker() {
+ while (idx < todo.length) {
+ const currentIdx = idx++;
+ const entry = todo[currentIdx];
+ const filePath = join(AUDIO_DIR, `${entry.audioKey}.mp3`);
+
+ // Double-check (another worker may have done it)
+ if (hasAudio(filePath, 0)) {
+ skipped++;
+ continue;
+ }
+
+ let lastError = '';
+ let success = false;
+
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
+ if (attempt > 0) {
+ await sleep(RETRY_DELAY_MS * attempt);
+ }
+ const result = await generateOne(entry.audioKey, entry.text, entry.voice);
+ if (result.ok) {
+ success = true;
+ break;
+ }
+ lastError = result.error;
+ }
+
+ if (success) {
+ generated++;
+ if (generated % 20 === 0) {
+ console.log(` Progress: ${generated}/${todo.length} generated...`);
+ }
+ } else {
+ failed++;
+ failures.push({ key: entry.audioKey, error: lastError });
+ console.log(` FAIL: ${entry.audioKey} — ${lastError}`);
+ }
+ }
+ }
+
+ const workers = [];
+ for (let w = 0; w < Math.min(MAX_CONCURRENCY, todo.length); w++) {
+ workers.push(worker());
+ }
+ await Promise.all(workers);
+
+ return { generated, skipped, failed, failures };
+}
+
+// Main
+async function main() {
+ console.log('=== edge-tts Audio Generator ===');
+ console.log(`Content: ${CONTENT_PATH}`);
+ console.log(`Output: ${AUDIO_DIR}`);
+ console.log('');
+
+ const entries = extractEntries();
+ console.log(`Unique audio keys extracted: ${entries.length}`);
+ console.log('');
+
+ const stats = await processAll(entries);
+
+ console.log('');
+ console.log('=== Summary ===');
+ console.log(`Generated: ${stats.generated}`);
+ console.log(`Skipped (already exist): ${stats.skipped}`);
+ console.log(`Failed: ${stats.failed}`);
+
+ if (stats.failures.length > 0) {
+ console.log('');
+ console.log('Failed keys:');
+ for (const f of stats.failures) {
+ console.log(` ${f.key}: ${f.error}`);
+ }
+ }
+}
+
+main().catch((err) => {
+ console.error('Fatal error:', err);
+ process.exit(1);
+});