// Narration rail — shows what the transformer is "thinking" at each stage, // with the real-life analogy that matches the current step. function Narration({ stage, tokens, activeIdx, nextToken, speed }) { const cur = tokens[activeIdx]?.text?.replace(/ /g, '␣') ?? '—'; const steps = [ { title: 'Reading the prompt', body: `Splitting your text into sub-word pieces. "${cur}" gets a numeric id like a library call number — the model only speaks in numbers.`, hint: 'text → ids', }, { title: 'Looking up the fingerprint', body: `Each id grabs one row from a giant lookup table — a "personality sheet" of ~4096 numbers that encodes what this token means.`, hint: 'id → vector', }, { title: 'Asking every past token for context', body: `The current token sends a question (Q) to every previous token's advertisement (K), then mixes their contents (V) weighted by how well they match. That's attention.`, hint: 'Q · Kᵀ → softmax → · V', }, { title: 'Thinking on it', body: `The vector is expanded to 4× its width, passed through a nonlinearity, then squeezed back. Stack this 32+ times — that's most of the model's parameters.`, hint: 'MLP × N', }, { title: 'Guessing what comes next', body: `A final projection produces one score per vocab entry. Temperature warps the distribution — lower = safer, higher = weirder. A random draw picks the winner.`, hint: 'logits → softmax → sample', }, ]; const s = steps[clamp(stage, 0, steps.length - 1)]; return (