{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "polyglot",
  "type": "registry:ui",
  "title": "Polyglot",
  "description": "An API quickstart with language tabs. The values every language shares glide to their new places when you switch, you can edit them right in the code, and Run streams the response in underneath.",
  "author": "KeyframeUI <https://keyframeui.com>",
  "categories": [
    "code",
    "developer",
    "ai"
  ],
  "docs": "Live preview, examples and props: https://keyframeui.com/components/polyglot",
  "dependencies": [
    "motion",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "components/ui/polyglot.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { animate, motion, useReducedMotion, type AnimationPlaybackControls } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Keep:\n * - A switch is interruptible: each shared value glides from wherever it is on screen now, as a\n *   translate written by a plain 0 → 1 spring, so nothing resets it while its stagger delay runs.\n * - A switch runs in order: the old code fades out with a slight blur, the values glide in reading\n *   order, the new code fades in line by line behind them, and the box's height springs from its\n *   pinned old height to the new line count.\n * - Editing a value changes every language at once, escaped for its own quotes, and cancels a\n *   run; switching tabs leaves the stream alone.\n * - A run lives in its own store read through `useRun` selectors, and the wait counts up by\n *   writing `textContent` each frame; the reply is announced once, when it's complete.\n * - Reduced motion is a crossfade: nothing glides or flies and the panel opens at once, and\n *   `reduced` waits for hydration so the server markup matches.\n */\nconst TIMING = {\n  /** The old code fading out with a slight blur, in seconds, and its curve. */\n  fadeOut: { duration: 0.12, ease: \"easeOut\" },\n  /** A shared value's move, as a spring (the `glideDuration` and `glideBounce` props). */\n  glide: { type: \"spring\", visualDuration: 0.42, bounce: 0 },\n  /** After the switch before the first value moves, in seconds. */\n  glideDelay: 0.03,\n  /** Between values, in reading order, in seconds (the `glideStagger` prop). */\n  glideStagger: 0.025,\n  /** A gliding value's tint brightening, then settling as it lands: seconds at the default glide\n   * (it stretches with `glideDuration`), keyframe times and curve. */\n  lit: { duration: 0.7, times: [0, 0.15, 0.6, 1], ease: \"easeOut\" },\n  /** After the switch before the first new line fades in, in seconds. */\n  lineDelay: 0.08,\n  /** Between new lines, in seconds. */\n  lineStagger: 0.018,\n  /** Each new line fading in, in seconds, and its curve. */\n  lineFade: { duration: 0.24, ease: \"easeOut\" },\n  /** The block's height following the new line count, as a spring. */\n  height: { type: \"spring\", visualDuration: 0.4, bounce: 0 },\n  /** The tab pill sliding over, as a spring. */\n  pill: { type: \"spring\", visualDuration: 0.3, bounce: 0 },\n  /** The whole switch with reduced motion, in seconds, and its curve. */\n  crossfade: { duration: 0.15, ease: \"easeOut\" },\n  /** How long the echoed value pulses on Run, in seconds (the glow takes twice this), and its curve. */\n  pulse: 0.36,\n  pulseEase: \"easeOut\",\n  /** The copy of the echoed value flying into the response panel, as a spring. */\n  flight: { type: \"spring\", visualDuration: 0.55, bounce: 0 },\n  /** After the pulse starts before the copy leaves, in seconds. */\n  flightDelay: 0.1,\n  /** The copy fading out once it lands, in seconds, and its curve. */\n  flightFade: { duration: 0.2, ease: \"easeOut\" },\n  /** The response panel's height following its content, as a spring. */\n  panel: { type: \"spring\", visualDuration: 0.35, bounce: 0 },\n  /** Tokens per second for `response` (the `speed` prop). */\n  speed: 60,\n  /** Before the first token of `response`, in ms (the `latency` prop). */\n  latency: 350,\n  /** A token that ends a line or clause waits this many times as long as the others. */\n  clausePause: 1.8,\n  /** Each wait, as a random multiple of the even pace, from `min` to `min + range`. */\n  burst: { min: 0.45, range: 1.1 },\n  /** The status line changing, in seconds, and its curve. */\n  status: { duration: 0.18, ease: \"easeOut\" },\n  /** The option menu opening, in seconds, and its curve (a cubic bezier). */\n  menu: { duration: 0.16, ease: [0.22, 1, 0.36, 1] },\n  /** How long the copy button shows its check, in ms. */\n  copied: 1600,\n  /** A tab's text color, as a Tailwind class. */\n  tab: \"duration-200\",\n  /** Gutter numbers past the new code fading out, as a Tailwind class. */\n  gutter: \"duration-150\",\n  /** An option value's chevron turning, as a Tailwind class. */\n  chevron: \"duration-200\",\n  /** A value's resting tint, as a Tailwind class. */\n  tint: \"duration-200\",\n  /** A secret and its dots crossfading, as a Tailwind class. */\n  secret: \"duration-200\",\n  /** The Copy and Run buttons' colors, icons and labels changing over, as a Tailwind class. */\n  button: \"duration-200\",\n  /** The echo line showing once the copy lands, as a Tailwind class. */\n  echo: \"duration-150\",\n  /** Each streamed chunk fading in, as Tailwind classes. */\n  token: \"duration-300 ease-out\",\n} as const;\n\n/* ─────────────────────────────────────────────────────────\n * POLYGLOT STORYBOARD\n *\n *  rest     a code block with language tabs. The values every\n *           language shares (model, prompt, key) carry a faint\n *           tint: they're the parts you can change\n *  switch   ~0.5s, interruptible\n *    0ms    the tab pill slides over (spring, no bounce) and the\n *           old code fades out over 120ms with a slight blur\n *   30ms    each shared value glides from its old place to its new\n *           one, in reading order, 25ms apart. Its tint brightens\n *           while it moves and settles once it lands\n *   80ms    the new code fades in line by line behind the values,\n *           and the block's height follows the new line count\n *           A switch mid-flight starts every value from wherever\n *           it is on screen\n *  edit     click an editable value and type: every language\n *           changes at once, escaped for its own quotes. A value\n *           with options opens a small menu. A secret shows its\n *           prefix and dots until hovered\n *  run      Run turns into Stop. The echoed value pulses and a copy\n *           of it flies down into the response panel as its first\n *           line while the panel opens. The status counts the wait\n *           up to the first token, tokens fade in one at a time\n *           behind a cursor, and it ends on \"200 OK · 1.2 s · 64\n *           tokens\" with Run again. Switching tabs doesn't touch the\n *           stream; editing a value cancels it\n *  sync     blocks with the same syncKey switch together, and the\n *           choice is remembered on this device\n *  reduced  a 150ms crossfade between languages; nothing moves or\n *           flies, and the panel opens at once\n * ───────────────────────────────────────────────────────── */\n\nconst POLY = {\n  blur: 2, // px of blur on the old code as it goes\n} as const;\n\n/* ── Public types ───────────────────────────────────────── */\n\nexport interface PolyglotSlot {\n  /** The starting value, written into every snippet wherever `{{name}}` appears. */\n  value: string;\n  /** Click the value in the code to type a new one. */\n  editable?: boolean;\n  /** Turns the value into a small dropdown with these choices. */\n  options?: string[];\n  /** Shows the prefix and dots until hovered or focused. Copy still uses the real value. */\n  secret?: boolean;\n  /** Accessible name for the value, e.g. \"Prompt\". Defaults to the slot's key. */\n  label?: string;\n}\n\nexport interface PolyglotSnippet {\n  /** The tab's label, e.g. \"cURL\". Blocks with the same `syncKey` match tabs by label. */\n  label: string;\n  /** Highlighting: `bash`, `python`, `js`, `ts`, `go`, `ruby` or `json`. Anything else is plain. */\n  lang: string;\n  /** The code, with `{{name}}` wherever a slot's value goes. */\n  code: string;\n}\n\nexport type PolyglotValues = Record<string, string>;\n\nexport interface PolyglotProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n  slots: Record<string, PolyglotSlot>;\n  snippets: PolyglotSnippet[];\n  /** The reply Run streams, as a string or built from the current values. Adds the Run footer. */\n  response?: string | ((values: PolyglotValues) => string);\n  /** Call a real API instead: yield the reply in chunks. Stop and edits abort `signal`. */\n  onRun?: (values: PolyglotValues, signal: AbortSignal) => AsyncIterable<string>;\n  /** Tokens per second for `response`. */\n  speed?: number | ((values: PolyglotValues) => number);\n  /** Milliseconds before the first token of `response`. */\n  latency?: number | ((values: PolyglotValues) => number);\n  /** Blocks with the same key switch language together and remember the choice. */\n  syncKey?: string;\n  /** The tab shown first, by label. */\n  defaultLabel?: string;\n  lineNumbers?: boolean;\n  /** `split` puts the response beside the code when the block is wide. */\n  layout?: \"stack\" | \"split\";\n  /** The slot whose value flies into the response as its first line. Defaults to the first editable one. */\n  echo?: string | false;\n  /** Highlighting for the response. */\n  responseLang?: string;\n  /** Shown when a run finishes, before the time and token count. */\n  statusText?: string;\n  /** Seconds a shared value takes to glide to its new place when you switch languages. */\n  glideDuration?: number;\n  /** Seconds between shared values setting off, in reading order. */\n  glideStagger?: number;\n  /** How much a gliding value overshoots its new place and settles, from 0 (none) up. */\n  glideBounce?: number;\n}\n\n/* ── Highlighting ───────────────────────────────────────── */\n\ntype TokenType =\n  | \"plain\"\n  | \"keyword\"\n  | \"string\"\n  | \"number\"\n  | \"comment\"\n  | \"function\"\n  | \"property\"\n  | \"type\"\n  | \"variable\"\n  | \"punctuation\";\n\ntype Token = { type: TokenType; text: string };\n\n// One map to retheme the highlighter. Neutrals come from the theme's tokens.\nconst TOKEN_CLASS: Record<TokenType, string> = {\n  plain: \"\",\n  keyword: \"text-rose-600 dark:text-rose-400\",\n  string: \"text-emerald-700 dark:text-emerald-300\",\n  number: \"text-blue-600 dark:text-sky-300\",\n  comment: \"text-muted-foreground italic\",\n  function: \"text-violet-600 dark:text-violet-300\",\n  property: \"text-amber-700 dark:text-amber-200\",\n  type: \"text-cyan-700 dark:text-cyan-300\",\n  variable: \"text-sky-700 dark:text-sky-300\",\n  punctuation: \"text-muted-foreground\",\n};\n\ntype Lang = \"bash\" | \"python\" | \"js\" | \"go\" | \"ruby\" | \"json\" | \"text\";\n\nconst LANG_ALIASES: Record<string, Lang> = {\n  bash: \"bash\",\n  sh: \"bash\",\n  shell: \"bash\",\n  zsh: \"bash\",\n  curl: \"bash\",\n  python: \"python\",\n  py: \"python\",\n  js: \"js\",\n  jsx: \"js\",\n  ts: \"js\",\n  tsx: \"js\",\n  javascript: \"js\",\n  typescript: \"js\",\n  node: \"js\",\n  go: \"go\",\n  golang: \"go\",\n  ruby: \"ruby\",\n  rb: \"ruby\",\n  json: \"json\",\n};\n\nconst toLang = (lang: string): Lang => LANG_ALIASES[lang.toLowerCase()] ?? \"text\";\n\ntype Grammar = {\n  comment: string;\n  block?: [string, string];\n  quotes: string[];\n  raw?: string;\n  prefix?: RegExp;\n  keywords: Set<string>;\n  constants: Set<string>;\n  types?: Set<string>;\n  kwarg?: boolean; // name= → property (Python keyword arguments)\n  colonKey?: boolean; // name: → property (object keys, struct fields, Ruby hashes)\n  symbols?: boolean; // :name → number color (Ruby)\n  decorators?: boolean;\n};\n\nconst words = (list: string) => new Set(list.split(\" \"));\n\nconst GRAMMARS: Record<\"python\" | \"js\" | \"go\" | \"ruby\", Grammar> = {\n  python: {\n    comment: \"#\",\n    quotes: ['\"\"\"', \"'''\", '\"', \"'\"],\n    prefix: /[rRbBuUfF]{1,2}(?=[\"'])/y,\n    keywords: words(\n      \"and as assert async await break class continue def del elif else except finally for from global if import in is lambda nonlocal not or pass raise return try while with yield\",\n    ),\n    constants: words(\"True False None self\"),\n    kwarg: true,\n    decorators: true,\n  },\n  js: {\n    comment: \"//\",\n    block: [\"/*\", \"*/\"],\n    quotes: ['\"', \"'\", \"`\"],\n    keywords: words(\n      \"as async await break case catch class const continue default delete do else enum export extends finally for from function if import in instanceof interface let new of return satisfies static switch throw try type typeof var void while yield\",\n    ),\n    constants: words(\"true false null undefined this\"),\n    types: words(\"string number boolean any unknown never void\"),\n    colonKey: true,\n  },\n  go: {\n    comment: \"//\",\n    block: [\"/*\", \"*/\"],\n    quotes: ['\"', \"`\", \"'\"],\n    raw: \"`\",\n    keywords: words(\n      \"break case chan const continue default defer else fallthrough for func go goto if import interface map package range return select struct switch type var\",\n    ),\n    constants: words(\"true false nil iota\"),\n    types: words(\"string int int64 error bool byte rune float64 any\"),\n    colonKey: true,\n  },\n  ruby: {\n    comment: \"#\",\n    quotes: ['\"', \"'\"],\n    keywords: words(\n      \"alias and begin break case class def do else elsif end ensure for if in module next not or puts require require_relative rescue return then unless until when while yield\",\n    ),\n    constants: words(\"true false nil self\"),\n    colonKey: true,\n    symbols: true,\n  },\n};\n\nconst BASH_KEYWORDS = words(\"if then else elif fi for in do done while case esac export function return local\");\n\nfunction createTokens() {\n  const out: Token[] = [];\n  const push = (type: TokenType, text: string) => {\n    if (!text) return;\n    const last = out[out.length - 1];\n    if (last && last.type === type) last.text += text;\n    else out.push({ type, text });\n  };\n  return { out, push };\n}\n\ntype Push = (type: TokenType, text: string) => void;\n\nconst isSpace = (c: string | undefined) => c === \" \" || c === \"\\t\" || c === \"\\n\" || c === \"\\r\";\nconst isWordStart = (c: string | undefined) => !!c && /[A-Za-z_$]/.test(c);\n\nfunction nextNonSpace(code: string, from: number) {\n  let i = from;\n  while (i < code.length && (code[i] === \" \" || code[i] === \"\\t\")) i++;\n  return i;\n}\n\n/** Reads a quoted string from `i`, honoring backslash escapes unless `raw`. Unclosed strings run to the line's end (or the input's). */\nfunction readString(code: string, i: number, quote: string, raw: boolean) {\n  let j = i + quote.length;\n  while (j < code.length) {\n    if (!raw && code[j] === \"\\\\\") {\n      j += 2;\n      continue;\n    }\n    if (code.startsWith(quote, j)) return j + quote.length;\n    if (code[j] === \"\\n\" && quote.length === 1 && quote !== \"`\") return j;\n    j++;\n  }\n  return code.length;\n}\n\nfunction lexJson(code: string, push: Push) {\n  let i = 0;\n  while (i < code.length) {\n    const c = code[i];\n    if (isSpace(c)) {\n      let j = i;\n      while (isSpace(code[j])) j++;\n      push(\"plain\", code.slice(i, j));\n      i = j;\n    } else if (c === '\"') {\n      const j = readString(code, i, '\"', false);\n      push(code[nextNonSpace(code, j)] === \":\" ? \"property\" : \"string\", code.slice(i, j));\n      i = j;\n    } else if (/[-\\d]/.test(c)) {\n      const m = /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y;\n      m.lastIndex = i;\n      const hit = m.exec(code);\n      const text = hit ? hit[0] : c;\n      push(hit ? \"number\" : \"punctuation\", text);\n      i += text.length;\n    } else if (/[a-z]/i.test(c)) {\n      let j = i;\n      while (j < code.length && /[a-z]/i.test(code[j])) j++;\n      const word = code.slice(i, j);\n      push(word === \"true\" || word === \"false\" || word === \"null\" ? \"number\" : \"plain\", word);\n      i = j;\n    } else {\n      push(\"punctuation\", c);\n      i++;\n    }\n  }\n}\n\nfunction lexBash(code: string, push: Push) {\n  let i = 0;\n  let command = true; // the next word is a command\n  while (i < code.length) {\n    const c = code[i];\n    if (c === \"\\n\") {\n      push(\"plain\", c);\n      i++;\n      command = true;\n    } else if (c === \" \" || c === \"\\t\") {\n      let j = i;\n      while (code[j] === \" \" || code[j] === \"\\t\") j++;\n      push(\"plain\", code.slice(i, j));\n      i = j;\n    } else if (c === \"\\\\\" && code[i + 1] === \"\\n\") {\n      push(\"punctuation\", \"\\\\\");\n      push(\"plain\", \"\\n\");\n      i += 2; // a continued line keeps its command\n    } else if (c === \"#\" && (i === 0 || isSpace(code[i - 1]))) {\n      const end = code.indexOf(\"\\n\", i);\n      const j = end < 0 ? code.length : end;\n      push(\"comment\", code.slice(i, j));\n      i = j;\n    } else if (c === \"'\") {\n      const close = code.indexOf(\"'\", i + 1);\n      const j = close < 0 ? code.length : close + 1;\n      const inner = code.slice(i + 1, close < 0 ? code.length : close);\n      if (/^\\s*[{[]/.test(inner)) {\n        // A JSON body (curl -d '{…}') gets JSON highlighting inside its quotes.\n        push(\"string\", \"'\");\n        lexJson(inner, push);\n        if (close >= 0) push(\"string\", \"'\");\n      } else {\n        push(\"string\", code.slice(i, j));\n      }\n      i = j;\n      command = false;\n    } else if (c === '\"') {\n      const j = readString(code, i, '\"', false);\n      push(\"string\", code.slice(i, j));\n      i = j;\n      command = false;\n    } else if (c === \"$\") {\n      const m = /\\$(?:\\{[^}]*\\}|\\w+|\\(|)/y;\n      m.lastIndex = i;\n      const text = m.exec(code)?.[0] || \"$\";\n      push(\"variable\", text);\n      i += text.length;\n      if (text === \"$(\") command = true;\n    } else if (\"|&;\".includes(c)) {\n      push(\"punctuation\", c);\n      i++;\n      command = true;\n    } else if (\"<>=()\".includes(c)) {\n      push(\"punctuation\", c);\n      i++;\n    } else {\n      let j = i;\n      while (j < code.length && !isSpace(code[j]) && !\"'\\\"$|&;<>=()\\\\\".includes(code[j])) j++;\n      if (j === i) j++;\n      const word = code.slice(i, j);\n      if (code[j] === \"=\") push(\"variable\", word);\n      else if (command && BASH_KEYWORDS.has(word)) push(\"keyword\", word);\n      else if (command) push(\"function\", word);\n      else if (word.startsWith(\"-\")) push(\"type\", word);\n      else push(\"plain\", word);\n      if (!(command && (word === \"sudo\" || word === \"then\" || word === \"do\" || word === \"else\"))) {\n        command = command && BASH_KEYWORDS.has(word) && word !== \"export\" ? command : false;\n      }\n      i = j;\n    }\n  }\n}\n\nfunction lexCode(code: string, g: Grammar, push: Push) {\n  let i = 0;\n  let last = \"\"; // the last character that wasn't whitespace\n  const emit = (type: TokenType, text: string) => {\n    push(type, text);\n    const trimmed = text.trimEnd();\n    if (trimmed) last = trimmed[trimmed.length - 1];\n  };\n  while (i < code.length) {\n    const c = code[i];\n    if (isSpace(c)) {\n      let j = i;\n      while (isSpace(code[j])) j++;\n      push(\"plain\", code.slice(i, j));\n      i = j;\n      continue;\n    }\n    if (code.startsWith(g.comment, i)) {\n      const end = code.indexOf(\"\\n\", i);\n      const j = end < 0 ? code.length : end;\n      emit(\"comment\", code.slice(i, j));\n      i = j;\n      continue;\n    }\n    if (g.block && code.startsWith(g.block[0], i)) {\n      const end = code.indexOf(g.block[1], i + g.block[0].length);\n      const j = end < 0 ? code.length : end + g.block[1].length;\n      emit(\"comment\", code.slice(i, j));\n      i = j;\n      continue;\n    }\n    let start = i;\n    if (g.prefix) {\n      g.prefix.lastIndex = i;\n      const m = g.prefix.exec(code);\n      if (m) start = i + m[0].length;\n    }\n    const quote = g.quotes.find((q) => code.startsWith(q, start));\n    if (quote) {\n      const j = readString(code, start, quote, quote === g.raw);\n      const after = nextNonSpace(code, j);\n      const key = code[after] === \":\" && code[after + 1] !== \":\" && code[after + 1] !== \"=\";\n      emit(key ? \"property\" : \"string\", code.slice(i, j));\n      i = j;\n      continue;\n    }\n    if (/\\d/.test(c) || (c === \".\" && /\\d/.test(code[i + 1] ?? \"\"))) {\n      const m = /(?:0[xob][\\da-f_]+|\\d[\\d_]*(?:\\.\\d+)?(?:e[+-]?\\d+)?|\\.\\d+)n?/iy;\n      m.lastIndex = i;\n      const text = m.exec(code)?.[0] || c;\n      emit(\"number\", text);\n      i += text.length;\n      continue;\n    }\n    if (g.decorators && c === \"@\" && isWordStart(code[i + 1])) {\n      let j = i + 1;\n      while (j < code.length && /[\\w.]/.test(code[j])) j++;\n      emit(\"function\", code.slice(i, j));\n      i = j;\n      continue;\n    }\n    if (g.symbols && c === \":\" && isWordStart(code[i + 1]) && code[i - 1] !== \":\") {\n      let j = i + 1;\n      while (j < code.length && /\\w/.test(code[j])) j++;\n      emit(\"number\", code.slice(i, j));\n      i = j;\n      continue;\n    }\n    if (isWordStart(c)) {\n      let j = i;\n      while (j < code.length && /[\\w$]/.test(code[j])) j++;\n      const word = code.slice(i, j);\n      const after = nextNonSpace(code, j);\n      let type: TokenType = \"plain\";\n      if (g.keywords.has(word) && last !== \".\") type = \"keyword\";\n      else if (g.constants.has(word)) type = \"number\";\n      else if (code[j] === \"(\" || (code[after] === \"(\" && last !== \".\")) type = \"function\";\n      else if (g.kwarg && code[j] === \"=\" && code[j + 1] !== \"=\") type = \"property\";\n      else if (g.colonKey && code[after] === \":\" && code[after + 1] !== \":\" && code[after + 1] !== \"=\")\n        type = \"property\";\n      else if (last === \".\") type = \"property\";\n      else if (/^[A-Z]/.test(word) || g.types?.has(word)) type = \"type\";\n      emit(type, word);\n      i = j;\n      continue;\n    }\n    emit(\"punctuation\", c);\n    i++;\n  }\n}\n\nfunction tokenize(code: string, lang: Lang): Token[] {\n  const { out, push } = createTokens();\n  if (lang === \"bash\") lexBash(code, push);\n  else if (lang === \"json\") lexJson(code, push);\n  else if (lang === \"text\") push(\"plain\", code);\n  else lexCode(code, GRAMMARS[lang], push);\n  return out;\n}\n\n/* ── Escaping ───────────────────────────────────────────── */\n\n// Where a placeholder sits: inside which quotes, and for shell, inside a\n// single-quoted body (with JSON strings of its own).\ntype Context = { quote: string | null; shell: boolean; json: boolean };\n\nfunction contextAt(code: string, end: number, lang: Lang): Context {\n  let quote: string | null = null;\n  let shell = false;\n  let json = false;\n  const line = lang === \"bash\" || lang === \"python\" || lang === \"ruby\" ? \"#\" : lang === \"js\" || lang === \"go\" ? \"//\" : null;\n  const skipLine = (i: number) => {\n    const n = code.indexOf(\"\\n\", i);\n    return n < 0 || n > end ? end : n;\n  };\n  for (let i = 0; i < end; i++) {\n    const c = code[i];\n    if (lang === \"bash\") {\n      if (shell) {\n        if (c === \"'\") shell = json = false;\n        else if (c === \"\\\\\" && json) i++;\n        else if (c === '\"') json = !json;\n      } else if (quote) {\n        if (c === \"\\\\\") i++;\n        else if (c === '\"') quote = null;\n      } else if (c === \"#\" && (i === 0 || isSpace(code[i - 1]))) i = skipLine(i);\n      else if (c === \"'\") shell = true;\n      else if (c === '\"') quote = '\"';\n      continue;\n    }\n    if (quote) {\n      if (c === \"\\\\\" && !(lang === \"go\" && quote === \"`\")) i++;\n      else if (code.startsWith(quote, i)) {\n        i += quote.length - 1;\n        quote = null;\n      }\n      continue;\n    }\n    if (line && code.startsWith(line, i)) i = skipLine(i);\n    else if ((lang === \"js\" || lang === \"go\") && code.startsWith(\"/*\", i)) {\n      const close = code.indexOf(\"*/\", i + 2);\n      i = close < 0 ? end : close + 1;\n    } else if (lang === \"python\" && (code.startsWith('\"\"\"', i) || code.startsWith(\"'''\", i))) {\n      quote = code.slice(i, i + 3);\n      i += 2;\n    } else if (c === '\"' || c === \"'\" || (c === \"`\" && lang !== \"python\" && lang !== \"ruby\")) quote = c;\n  }\n  return { quote, shell, json };\n}\n\n// The escaped text, plus which raw character each escaped character came from,\n// so an edit made to the escaped text can be mapped back to the raw value.\ntype Escaped = { text: string; map: number[] };\n\nfunction escapeValue(raw: string, ctx: Context, lang: Lang): Escaped {\n  let text = \"\";\n  const map: number[] = [];\n  for (let k = 0; k < raw.length; k++) {\n    const ch = raw[k];\n    let s = ch;\n    if (ch === \"\\n\" || ch === \"\\r\") s = ctx.quote || ctx.json ? (ch === \"\\n\" ? \"\\\\n\" : \"\\\\r\") : \" \";\n    else if (lang === \"bash\") {\n      if (ctx.shell) {\n        if (ctx.json && (ch === '\"' || ch === \"\\\\\")) s = \"\\\\\" + ch;\n        else if (ch === \"'\") s = \"'\\\\''\";\n      } else if (ctx.quote === '\"' && '\"\\\\$`'.includes(ch)) s = \"\\\\\" + ch;\n    } else if (ctx.quote && !(lang === \"go\" && ctx.quote === \"`\")) {\n      if (ch === \"\\\\\" || ch === ctx.quote[0]) s = \"\\\\\" + ch;\n      else if (ctx.quote === \"`\" && ch === \"$\" && raw[k + 1] === \"{\") s = \"\\\\$\";\n      else if (lang === \"ruby\" && ctx.quote === '\"' && ch === \"#\" && raw[k + 1] === \"{\") s = \"\\\\#\";\n    }\n    text += s;\n    for (let n = 0; n < s.length; n++) map.push(k);\n  }\n  return { text, map };\n}\n\n/** Applies an edit made to the escaped text back onto the raw value. */\nfunction applyEdit(raw: string, esc: Escaped, next: string, caret: number) {\n  const old = esc.text;\n  const max = Math.min(old.length, next.length);\n  let pre = 0;\n  while (pre < max && pre < caret && old[pre] === next[pre]) pre++;\n  let suf = 0;\n  const sufMax = Math.min(old.length - pre, next.length - Math.max(pre, caret));\n  while (suf < sufMax && old[old.length - 1 - suf] === next[next.length - 1 - suf]) suf++;\n  const inserted = next.slice(pre, next.length - suf);\n  const delEnd = old.length - suf;\n  const start = pre < old.length ? esc.map[pre] : raw.length;\n  const end = delEnd > pre ? esc.map[delEnd - 1] + 1 : start;\n  return { raw: raw.slice(0, start) + inserted + raw.slice(end), caret: start + inserted.length };\n}\n\n/* ── Building a snippet ─────────────────────────────────── */\n\ntype TextSeg = { kind: \"text\"; tokens: Token[] };\ntype SlotSeg = { kind: \"slot\"; name: string; occ: number; tokens: Token[]; esc: Escaped; ctx: Context; lang: Lang };\ntype Seg = TextSeg | SlotSeg;\ntype Built = { code: string; lines: Seg[][] };\n\nconst PLACEHOLDER = /\\{\\{\\s*([A-Za-z_][\\w-]*)\\s*\\}\\}/g;\n\nconst cleanCode = (code: string) => code.replace(/^\\s*\\n/, \"\").trimEnd();\n\nfunction buildSnippet(snippet: PolyglotSnippet, slots: Record<string, PolyglotSlot>, values: PolyglotValues): Built {\n  const lang = toLang(snippet.lang);\n  const template = cleanCode(snippet.code);\n  let code = \"\";\n  let cursor = 0;\n  const ranges: { name: string; occ: number; start: number; end: number; esc: Escaped; ctx: Context }[] = [];\n  const seen: Record<string, number> = {};\n  for (const m of template.matchAll(PLACEHOLDER)) {\n    const name = m[1];\n    if (!(name in slots)) continue;\n    code += template.slice(cursor, m.index);\n    const ctx = contextAt(template, m.index, lang);\n    const esc = escapeValue(values[name] ?? \"\", ctx, lang);\n    const occ = (seen[name] = (seen[name] ?? -1) + 1);\n    ranges.push({ name, occ, start: code.length, end: code.length + esc.text.length, esc, ctx });\n    code += esc.text;\n    cursor = m.index + m[0].length;\n  }\n  code += template.slice(cursor);\n\n  // Every character's token type, so slots and lines can cut tokens anywhere.\n  const types: TokenType[] = [];\n  for (const token of tokenize(code, lang)) for (let k = 0; k < token.text.length; k++) types.push(token.type);\n  const runs = (from: number, to: number) => {\n    const out: Token[] = [];\n    for (let k = from; k < to; k++) {\n      const last = out[out.length - 1];\n      if (last && last.type === types[k]) last.text += code[k];\n      else out.push({ type: types[k] ?? \"plain\", text: code[k] });\n    }\n    return out;\n  };\n\n  const lines: Seg[][] = [[]];\n  const text = (from: number, to: number) => {\n    let k = from;\n    while (k < to) {\n      const nl = code.indexOf(\"\\n\", k);\n      const stop = nl < 0 || nl >= to ? to : nl;\n      if (stop > k) lines[lines.length - 1].push({ kind: \"text\", tokens: runs(k, stop) });\n      if (stop < to) lines.push([]);\n      k = stop + 1;\n    }\n  };\n  let p = 0;\n  for (const r of ranges) {\n    text(p, r.start);\n    lines[lines.length - 1].push({ kind: \"slot\", name: r.name, occ: r.occ, tokens: runs(r.start, r.end), esc: r.esc, ctx: r.ctx, lang });\n    p = r.end;\n  }\n  text(p, code.length);\n  return { code, lines };\n}\n\n/* ── Remembered language (syncKey) ──────────────────────── */\n\nconst syncValues = new Map<string, string | null>();\nconst syncListeners = new Map<string, Set<() => void>>();\nconst STORAGE_PREFIX = \"polyglot:\";\n\nfunction readSync(key: string) {\n  if (!syncValues.has(key)) {\n    let stored: string | null = null;\n    try {\n      stored = window.localStorage.getItem(STORAGE_PREFIX + key);\n    } catch {}\n    syncValues.set(key, stored);\n  }\n  return syncValues.get(key) ?? null;\n}\n\nfunction writeSync(key: string, label: string) {\n  syncValues.set(key, label);\n  try {\n    window.localStorage.setItem(STORAGE_PREFIX + key, label);\n  } catch {}\n  syncListeners.get(key)?.forEach((listener) => listener());\n}\n\nfunction subscribeSync(key: string, listener: () => void) {\n  const set = syncListeners.get(key) ?? new Set();\n  syncListeners.set(key, set);\n  set.add(listener);\n  return () => {\n    set.delete(listener);\n  };\n}\n\nconst noopSubscribe = () => () => {};\n\n/* ── Run state ──────────────────────────────────────────── */\n\ntype Phase = \"idle\" | \"waiting\" | \"streaming\" | \"done\" | \"stopped\" | \"cancelled\" | \"error\";\n\ntype Flight = { runId: number; text: string; from: { x: number; y: number }; to: { x: number; y: number } };\n\ntype RunState = {\n  runId: number;\n  phase: Phase;\n  chunks: string[];\n  echo: string | null;\n  landed: boolean;\n  flight: Flight | null;\n  startedAt: number;\n  firstAt: number;\n  endedAt: number;\n  error: string | null;\n};\n\nfunction createRunStore() {\n  let state: RunState = {\n    runId: 0,\n    phase: \"idle\",\n    chunks: [],\n    echo: null,\n    landed: false,\n    flight: null,\n    startedAt: 0,\n    firstAt: 0,\n    endedAt: 0,\n    error: null,\n  };\n  const listeners = new Set<() => void>();\n  return {\n    get: () => state,\n    set(patch: Partial<RunState>) {\n      state = { ...state, ...patch };\n      listeners.forEach((listener) => listener());\n    },\n    subscribe(listener: () => void) {\n      listeners.add(listener);\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n  };\n}\n\ntype RunStore = ReturnType<typeof createRunStore>;\n\nconst isActive = (phase: Phase) => phase === \"waiting\" || phase === \"streaming\";\n\nfunction useRun<T>(store: RunStore, select: (state: RunState) => T) {\n  return React.useSyncExternalStore(\n    store.subscribe,\n    () => select(store.get()),\n    () => select(store.get()),\n  );\n}\n\n/** Splits a reply into token-sized pieces: words, with long ones cut into chunks. */\nfunction toTokens(text: string) {\n  const out: string[] = [];\n  for (const [piece] of text.matchAll(/\\s*\\S+|\\s+/g)) {\n    const lead = /^\\s*/.exec(piece)?.[0] ?? \"\";\n    const word = piece.slice(lead.length);\n    if (word.length <= 7) {\n      out.push(piece);\n      continue;\n    }\n    const parts = word.match(/.{1,4}/g) ?? [word];\n    parts[0] = lead + parts[0];\n    out.push(...parts);\n  }\n  return out;\n}\n\nfunction sleep(ms: number, signal: AbortSignal) {\n  return new Promise<void>((resolve, reject) => {\n    if (signal.aborted) return reject(signal.reason);\n    const timer = window.setTimeout(resolve, ms);\n    signal.addEventListener(\n      \"abort\",\n      () => {\n        window.clearTimeout(timer);\n        reject(signal.reason);\n      },\n      { once: true },\n    );\n  });\n}\n\nasync function* simulate(text: string, speed: number, latency: number, signal: AbortSignal) {\n  await sleep(latency, signal);\n  const each = 1000 / Math.max(1, speed);\n  for (const token of toTokens(text)) {\n    yield token;\n    // Real streams are bursty, and pause a little at the end of a line or clause.\n    const pause = /[.,;:!?\\n]\\s*$/.test(token) ? TIMING.clausePause : 1;\n    await sleep(each * pause * (TIMING.burst.min + Math.random() * TIMING.burst.range), signal);\n  }\n}\n\n/* ── Polyglot ───────────────────────────────────────────── */\n\ntype Layer = { id: number; index: number };\ntype Editing = { name: string; occ: number } | null;\ntype Menu = { name: string; occ: number; x: number; y: number } | null;\n\ntype SlotApi = {\n  slots: Record<string, PolyglotSlot>;\n  values: PolyglotValues;\n  editing: Editing;\n  menu: Menu;\n  setValue: (name: string, value: string) => void;\n  startEdit: (name: string, occ: number) => void;\n  endEdit: () => void;\n  openMenu: (name: string, occ: number, anchor: HTMLElement) => void;\n  closeMenu: () => void;\n};\n\nconst SlotContext = React.createContext<SlotApi | null>(null);\n\nexport function Polyglot({\n  slots,\n  snippets,\n  response,\n  onRun,\n  speed = TIMING.speed,\n  latency = TIMING.latency,\n  syncKey,\n  defaultLabel,\n  lineNumbers = false,\n  layout = \"stack\",\n  echo,\n  responseLang = \"text\",\n  statusText = \"200 OK\",\n  glideDuration = TIMING.glide.visualDuration,\n  glideStagger = TIMING.glideStagger,\n  glideBounce = TIMING.glide.bounce,\n  className,\n  onKeyDown,\n  ...props\n}: PolyglotProps) {\n  const uid = React.useId();\n  const hydrated = React.useSyncExternalStore(noopSubscribe, () => true, () => false);\n  const prefersReduced = useReducedMotion() ?? false;\n  const reduced = prefersReduced && hydrated;\n\n  /* language */\n  const subscribe = React.useCallback(\n    (listener: () => void) => (syncKey ? subscribeSync(syncKey, listener) : () => {}),\n    [syncKey],\n  );\n  const synced = React.useSyncExternalStore(\n    subscribe,\n    () => (syncKey ? readSync(syncKey) : null),\n    () => null,\n  );\n  const [picked, setPicked] = React.useState<string | null>(null);\n  const indexOf = (label: string | null | undefined) =>\n    label == null ? -1 : snippets.findIndex((s) => s.label === label);\n  const wanted = syncKey ? synced : picked;\n  const active = Math.max(0, [wanted, defaultLabel].map(indexOf).find((i) => i >= 0) ?? 0);\n\n  const select = (index: number) => {\n    const label = snippets[index]?.label;\n    if (label == null) return;\n    if (syncKey) writeSync(syncKey, label);\n    else setPicked(label);\n  };\n\n  /* values */\n  const [values, setValues] = React.useState<PolyglotValues>(() =>\n    Object.fromEntries(Object.entries(slots).map(([name, slot]) => [name, slot.value])),\n  );\n  const built = React.useMemo(\n    () => snippets.map((snippet) => buildSnippet(snippet, slots, values)),\n    [snippets, slots, values],\n  );\n\n  /* layers: the live code plus any still fading out */\n  const [layers, setLayers] = React.useState<Layer[]>([{ id: 0, index: active }]);\n  const [editing, setEditing] = React.useState<Editing>(null);\n  const [menu, setMenu] = React.useState<Menu>(null);\n  const live = layers[layers.length - 1];\n  if (live.index !== active) {\n    setLayers([...layers.slice(-2), { id: live.id + 1, index: active }]);\n    setEditing(null);\n    setMenu(null);\n  }\n\n  const rootRef = React.useRef<HTMLDivElement>(null);\n  const boxRef = React.useRef<HTMLDivElement>(null);\n  const scrollerRef = React.useRef<HTMLDivElement>(null);\n  const contentRef = React.useRef<HTMLDivElement>(null);\n  const shownId = React.useRef(live.id);\n  const wasHydrated = React.useRef(hydrated);\n  const lastHeight = React.useRef(0);\n  const heightAnim = React.useRef<AnimationPlaybackControls | null>(null);\n  const moving = React.useRef<AnimationPlaybackControls[]>([]);\n\n  const drop = React.useCallback((id: number) => {\n    setLayers((current) => (current.some((l) => l.id === id) ? current.filter((l) => l.id !== id) : current));\n  }, []);\n\n  // The box's resting height, kept for the next switch to animate from.\n  React.useEffect(() => {\n    const box = boxRef.current;\n    if (!box) return;\n    const observer = new ResizeObserver(() => {\n      if (!heightAnim.current) lastHeight.current = box.offsetHeight;\n    });\n    observer.observe(box);\n    return () => observer.disconnect();\n  }, []);\n\n  // Runs on a language switch. A change to the glide props alone lands here too and returns at once.\n  React.useLayoutEffect(() => {\n    const prevId = shownId.current;\n    shownId.current = live.id;\n    if (prevId === live.id) return;\n    const content = contentRef.current;\n    const box = boxRef.current;\n    const prev = content?.querySelector<HTMLElement>(`[data-layer=\"${prevId}\"]`);\n    const next = content?.querySelector<HTMLElement>(`[data-layer=\"${live.id}\"]`);\n    if (!content || !box || !next) return;\n\n    // The language the visitor picked on an earlier visit arrives right after\n    // hydration; show it as if it had been there all along.\n    if (!wasHydrated.current) {\n      if (prev) prev.style.visibility = \"hidden\";\n      requestAnimationFrame(() => drop(prevId));\n      return;\n    }\n\n    if (reduced) {\n      animate(next, { opacity: [0, 1] }, TIMING.crossfade);\n      if (prev) animate(prev, { opacity: 0 }, TIMING.crossfade).then(() => drop(prevId));\n      return;\n    }\n\n    // Pin the box at its old height before measuring anything: the new code\n    // has already changed it, and a block centered in its parent moves with it.\n    const fromHeight = heightAnim.current ? box.getBoundingClientRect().height : lastHeight.current;\n    heightAnim.current?.stop();\n    heightAnim.current = null;\n    box.style.height = \"\";\n    const toHeight = box.offsetHeight;\n    lastHeight.current = toHeight;\n    if (fromHeight) box.style.height = `${fromHeight}px`;\n\n    // Where every value is on screen right now, mid-flight or not.\n    const from = new Map<string, { el: HTMLElement; rect: DOMRect }>();\n    prev?.querySelectorAll<HTMLElement>(\"[data-slot]\").forEach((el) => {\n      from.set(`${el.dataset.slot}:${el.dataset.occ}`, { el, rect: el.getBoundingClientRect() });\n    });\n    moving.current.forEach((controls) => controls.stop());\n    moving.current = [];\n\n    // Height: from wherever it was to the new line count.\n    if (fromHeight && Math.abs(fromHeight - toHeight) > 0.5) {\n      const controls = animate(box, { height: [fromHeight, toHeight] }, TIMING.height);\n      heightAnim.current = controls;\n      controls.then(() => {\n        if (heightAnim.current !== controls) return;\n        heightAnim.current = null;\n        box.style.height = \"\";\n      });\n    } else {\n      box.style.height = \"\";\n    }\n\n    // The old code fades out with a slight blur.\n    if (prev) {\n      animate(prev, { opacity: 0, filter: `blur(${POLY.blur}px)` }, TIMING.fadeOut).then(\n        () => drop(prevId),\n      );\n    }\n\n    // Values glide from their old places, in reading order.\n    const lineOf = (el: HTMLElement) => Number(el.closest<HTMLElement>(\"[data-line]\")?.dataset.line ?? 0);\n    const late = new Map<number, HTMLElement[]>();\n    let order = 0;\n    next.querySelectorAll<HTMLElement>(\"[data-slot]\").forEach((el) => {\n      const key = `${el.dataset.slot}:${el.dataset.occ}`;\n      const old = from.get(key);\n      if (!old) {\n        const line = lineOf(el);\n        late.set(line, [...(late.get(line) ?? []), el]);\n        return;\n      }\n      old.el.style.visibility = \"hidden\";\n      const to = el.getBoundingClientRect();\n      const dx = old.rect.left - to.left;\n      const dy = old.rect.top - to.top;\n      if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return;\n      const delay = TIMING.glideDelay + order++ * glideStagger;\n      // A plain 0 → 1 spring writes the transform, so nothing resets it to\n      // the destination while the stagger delay runs. With `glideBounce` it\n      // runs past 1, and the value carries on past its place before settling.\n      const place = (p: number) => {\n        el.style.transform = p === 1 ? \"\" : `translate(${dx * (1 - p)}px, ${dy * (1 - p)}px)`;\n      };\n      place(0);\n      moving.current.push(\n        animate(0, 1, {\n          type: TIMING.glide.type,\n          visualDuration: glideDuration,\n          bounce: glideBounce,\n          delay,\n          onUpdate: place,\n          onComplete: () => place(1),\n        }),\n      );\n      // The tint brightens while it moves, picking up from the old glow if it was mid-flight. It holds\n      // until the value lands, so it stretches with the glide.\n      const glow = el.querySelector<HTMLElement>(\"[data-glow]\");\n      const oldGlow = old.el.querySelector<HTMLElement>(\"[data-glow]\");\n      const lit = oldGlow ? Number(getComputedStyle(oldGlow).opacity) || 0 : 0;\n      if (glow) {\n        glow.style.opacity = String(lit);\n        moving.current.push(\n          animate(\n            glow,\n            { opacity: [lit, 1, 1, 0] },\n            {\n              duration: TIMING.lit.duration * (glideDuration / TIMING.glide.visualDuration),\n              times: [...TIMING.lit.times],\n              delay,\n              ease: TIMING.lit.ease,\n            },\n          ),\n        );\n      }\n    });\n\n    // The new code fades in line by line, just behind the values.\n    next.querySelectorAll<HTMLElement>(\"[data-line]\").forEach((line, i) => {\n      const parts = [...line.querySelectorAll<HTMLElement>(\":scope > [data-seg]\"), ...(late.get(i) ?? [])];\n      if (!parts.length) return;\n      parts.forEach((part) => (part.style.opacity = \"0\"));\n      animate(parts, { opacity: [0, 1] }, { ...TIMING.lineFade, delay: TIMING.lineDelay + i * TIMING.lineStagger });\n    });\n  }, [live.id, reduced, drop, glideDuration, glideStagger, glideBounce]);\n\n  React.useLayoutEffect(() => {\n    wasHydrated.current = hydrated;\n  });\n\n  /* run */\n  const [run] = React.useState(createRunStore);\n  const runnable = response !== undefined || onRun !== undefined;\n  const echoSlot = echo === false ? null : (echo ?? Object.keys(slots).find((name) => slots[name].editable) ?? null);\n  const abortRef = React.useRef<AbortController | null>(null);\n  const valuesRef = React.useRef(values);\n  const latest = React.useRef({ response, onRun, speed, latency, echoSlot });\n  React.useLayoutEffect(() => {\n    valuesRef.current = values;\n    latest.current = { response, onRun, speed, latency, echoSlot };\n  });\n\n  const stop = React.useCallback(\n    (phase: \"stopped\" | \"cancelled\") => {\n      if (!isActive(run.get().phase)) return;\n      abortRef.current?.abort();\n      run.set({ phase, endedAt: performance.now(), flight: null, landed: true });\n    },\n    [run],\n  );\n\n  const start = React.useCallback(() => {\n    abortRef.current?.abort();\n    const controller = new AbortController();\n    abortRef.current = controller;\n    const { signal } = controller;\n    const vals = valuesRef.current;\n    const opts = latest.current;\n    run.set({\n      runId: run.get().runId + 1,\n      phase: \"waiting\",\n      chunks: [],\n      echo: opts.echoSlot ? (vals[opts.echoSlot] ?? null) : null,\n      landed: false,\n      flight: null,\n      startedAt: performance.now(),\n      firstAt: 0,\n      endedAt: 0,\n      error: null,\n    });\n    const resolve = <T,>(v: T | ((values: PolyglotValues) => T)) =>\n      typeof v === \"function\" ? (v as (values: PolyglotValues) => T)(vals) : v;\n    (async () => {\n      try {\n        const source = opts.onRun\n          ? opts.onRun(vals, signal)\n          : simulate(resolve(opts.response ?? \"\"), resolve(opts.speed), resolve(opts.latency), signal);\n        for await (const chunk of source) {\n          if (signal.aborted) return;\n          const s = run.get();\n          run.set({ phase: \"streaming\", chunks: [...s.chunks, chunk], firstAt: s.firstAt || performance.now() });\n        }\n        if (!signal.aborted) run.set({ phase: \"done\", endedAt: performance.now() });\n      } catch (error) {\n        if (signal.aborted) return;\n        run.set({\n          phase: \"error\",\n          endedAt: performance.now(),\n          error: error instanceof Error ? error.message : String(error),\n        });\n      }\n    })();\n  }, [run]);\n\n  React.useEffect(() => () => abortRef.current?.abort(), []);\n\n  const toggleRun = () => {\n    if (isActive(run.get().phase)) stop(\"stopped\");\n    else start();\n  };\n\n  // Run: the echoed value pulses and a copy of it flies into the panel.\n  const runId = useRun(run, (s) => s.runId);\n  React.useLayoutEffect(() => {\n    if (!runId) return;\n    const state = run.get();\n    if (state.landed || state.flight) return;\n    const root = rootRef.current;\n    const target = root?.querySelector<HTMLElement>(\"[data-echo-target]\");\n    const source = echoSlot\n      ? contentRef.current?.querySelector<HTMLElement>(`[data-layer=\"${shownId.current}\"] [data-slot=\"${echoSlot}\"]`)\n      : null;\n    if (!root || !target || !source || reduced || state.echo == null) {\n      run.set({ landed: true });\n      return;\n    }\n    const glow = source.querySelector<HTMLElement>(\"[data-glow]\");\n    if (glow) animate(glow, { opacity: [0, 1, 0] }, { duration: TIMING.pulse * 2, ease: TIMING.pulseEase });\n    const box = root.getBoundingClientRect();\n    const view = scrollerRef.current?.getBoundingClientRect();\n    const s = source.querySelector<HTMLElement>(\"[data-text]\")?.getBoundingClientRect() ?? source.getBoundingClientRect();\n    const t = target.getBoundingClientRect();\n    const left = view ? Math.min(Math.max(s.left, view.left), view.right - 24) : s.left;\n    run.set({\n      flight: {\n        runId,\n        text: state.echo,\n        from: { x: left - box.left, y: s.top - box.top },\n        to: { x: t.left - box.left, y: t.top - box.top },\n      },\n    });\n  }, [runId, run, echoSlot, reduced]);\n\n  /* slot actions */\n  const setValue = React.useCallback(\n    (name: string, value: string) => {\n      if (valuesRef.current[name] === value) return;\n      valuesRef.current = { ...valuesRef.current, [name]: value };\n      setValues((current) => ({ ...current, [name]: value }));\n      stop(\"cancelled\");\n    },\n    [stop],\n  );\n  const slotApi = React.useMemo<SlotApi>(\n    () => ({\n      slots,\n      values,\n      editing,\n      menu,\n      setValue,\n      startEdit: (name, occ) => setEditing({ name, occ }),\n      endEdit: () => setEditing(null),\n      openMenu: (name, occ, anchor) => {\n        const root = rootRef.current;\n        if (!root) return;\n        const r = anchor.getBoundingClientRect();\n        const b = root.getBoundingClientRect();\n        setMenu({ name, occ, x: r.left - b.left, y: r.bottom - b.top + 6 });\n      },\n      closeMenu: () => setMenu(null),\n    }),\n    [slots, values, editing, menu, setValue],\n  );\n\n  // Close the menu when the code scrolls sideways under it.\n  React.useEffect(() => {\n    const scroller = scrollerRef.current;\n    if (!menu || !scroller) return;\n    const close = () => setMenu(null);\n    scroller.addEventListener(\"scroll\", close, { once: true });\n    return () => scroller.removeEventListener(\"scroll\", close);\n  }, [menu]);\n\n  const gutter = Math.max(...layers.map((layer) => built[layer.index]?.lines.length ?? 0));\n  const split = layout === \"split\";\n  const tabId = (i: number) => `${uid}-tab-${i}`;\n  const panelId = `${uid}-panel`;\n\n  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n    onKeyDown?.(event);\n    if (event.defaultPrevented || !runnable) return;\n    if (event.key === \"Enter\" && (event.metaKey || event.ctrlKey)) {\n      event.preventDefault();\n      toggleRun();\n    }\n  };\n\n  const panel = runnable ? (\n    split ? (\n      <SplitPanel run={run} lang={toLang(responseLang)} />\n    ) : (\n      <StackPanel run={run} lang={toLang(responseLang)} reduced={reduced} />\n    )\n  ) : null;\n\n  return (\n    <div\n      ref={rootRef}\n      onKeyDown={handleKeyDown}\n      className={cn(\n        \"@container relative w-full rounded-xl border border-border bg-background text-foreground shadow-[0_1px_2px_rgb(0_0_0/0.04),0_12px_32px_-16px_rgb(0_0_0/0.14)]\",\n        className,\n      )}\n      {...props}\n    >\n      <div className={cn(split && runnable && \"grid @3xl:grid-cols-[minmax(0,1.35fr)_minmax(0,1fr)]\")}>\n        <div className=\"flex min-w-0 flex-col\">\n          {/* header */}\n          <div\n            className={cn(\n              \"flex h-11 items-center gap-2 rounded-t-xl border-b border-border bg-muted/40 pr-1.5 pl-1.5\",\n              split && runnable && \"@3xl:rounded-tr-none\",\n            )}\n          >\n            <div\n              role=\"tablist\"\n              aria-label=\"Language\"\n              className=\"flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto [scrollbar-width:none]\"\n            >\n              {snippets.map((snippet, i) => (\n                <button\n                  key={snippet.label}\n                  type=\"button\"\n                  role=\"tab\"\n                  id={tabId(i)}\n                  aria-selected={i === active}\n                  aria-controls={panelId}\n                  tabIndex={i === active ? 0 : -1}\n                  onClick={() => select(i)}\n                  onKeyDown={(event) => {\n                    const last = snippets.length - 1;\n                    const to =\n                      event.key === \"ArrowRight\"\n                        ? i === last ? 0 : i + 1\n                        : event.key === \"ArrowLeft\"\n                          ? i === 0 ? last : i - 1\n                          : event.key === \"Home\"\n                            ? 0\n                            : event.key === \"End\"\n                              ? last\n                              : null;\n                    if (to === null) return;\n                    event.preventDefault();\n                    select(to);\n                    document.getElementById(tabId(to))?.focus();\n                  }}\n                  className={cn(\n                    `relative h-7 shrink-0 cursor-pointer rounded-md px-2.5 text-[13px] font-medium whitespace-nowrap outline-none transition-colors ${TIMING.tab} focus-visible:ring-2 focus-visible:ring-foreground/30`,\n                    i === active ? \"text-foreground\" : \"text-muted-foreground hover:text-foreground\",\n                  )}\n                >\n                  {i === active ? (\n                    <motion.span\n                      layoutId={`${uid}-pill`}\n                      transition={reduced ? { duration: 0 } : TIMING.pill}\n                      className=\"absolute inset-0 rounded-md bg-background shadow-[0_1px_2px_rgb(0_0_0/0.08)] ring-1 ring-border\"\n                    />\n                  ) : null}\n                  <span className=\"relative z-10\">{snippet.label}</span>\n                </button>\n              ))}\n            </div>\n            <CopyButton text={built[active]?.code ?? \"\"} />\n          </div>\n\n          {/* code */}\n          <div ref={boxRef} className=\"relative flex overflow-hidden\">\n            {lineNumbers ? (\n              // Zero height so only the code sets the box's height: the gutter\n              // keeps the outgoing code's extra numbers while it fades, and they\n              // must not hold the box open. Numbers past the new code fade out.\n              <div\n                aria-hidden\n                className=\"h-0 shrink-0 py-3.5 pr-1 pl-4 text-right font-mono text-[13px] leading-6 text-muted-foreground/45 tabular-nums select-none\"\n              >\n                {Array.from({ length: gutter }, (_, i) => (\n                  <span\n                    key={i}\n                    className={cn(\n                      `block min-w-[2ch] transition-opacity ${TIMING.gutter}`,\n                      i >= (built[live.index]?.lines.length ?? 0) && \"opacity-0\",\n                    )}\n                  >\n                    {i + 1}\n                  </span>\n                ))}\n              </div>\n            ) : null}\n            <div\n              ref={scrollerRef}\n              id={panelId}\n              role=\"tabpanel\"\n              aria-labelledby={tabId(active)}\n              tabIndex={0}\n              className=\"min-w-0 flex-1 overflow-x-auto overflow-y-hidden outline-none [scrollbar-width:thin] focus-visible:ring-2 focus-visible:ring-foreground/20 focus-visible:ring-inset\"\n            >\n              <div ref={contentRef} className=\"relative w-max min-w-full\">\n                <SlotContext.Provider value={slotApi}>\n                  {layers.map((layer) => (\n                    <CodeLayer\n                      key={layer.id}\n                      id={layer.id}\n                      model={built[layer.index] ?? built[0]}\n                      live={layer.id === live.id}\n                      padded={lineNumbers}\n                    />\n                  ))}\n                </SlotContext.Provider>\n              </div>\n            </div>\n          </div>\n\n          {runnable ? (\n            <Footer\n              run={run}\n              onToggle={toggleRun}\n              statusText={statusText}\n              hint={Object.values(slots).some((slot) => slot.editable || slot.options)}\n              hydrated={hydrated}\n              className={cn(!split && \"rounded-b-xl\", split && \"mt-auto\")}\n            />\n          ) : null}\n        </div>\n        {panel}\n      </div>\n\n      {menu ? (\n        <OptionMenu\n          key={`${menu.name}:${menu.occ}`}\n          menu={menu}\n          options={slots[menu.name]?.options ?? []}\n          value={values[menu.name] ?? \"\"}\n          label={slots[menu.name]?.label ?? menu.name}\n          reduced={reduced}\n          onPick={(value) => setValue(menu.name, value)}\n          onClose={(refocus) => {\n            setMenu(null);\n            if (refocus) {\n              contentRef.current\n                ?.querySelector<HTMLElement>(`[data-layer=\"${live.id}\"] [data-slot=\"${menu.name}\"][data-occ=\"${menu.occ}\"] button`)\n                ?.focus();\n            }\n          }}\n        />\n      ) : null}\n      <Flyer run={run} />\n      <Announcer run={run} />\n    </div>\n  );\n}\n\n/* ── Code ───────────────────────────────────────────────── */\n\nconst CodeLayer = React.memo(function CodeLayer({\n  id,\n  model,\n  live,\n  padded,\n}: {\n  id: number;\n  model: Built;\n  live: boolean;\n  padded: boolean;\n}) {\n  return (\n    <pre\n      data-layer={id}\n      aria-hidden={live ? undefined : true}\n      inert={!live}\n      className={cn(\n        \"m-0 w-max min-w-full bg-transparent py-3.5 pr-5 font-mono text-[13px] leading-6 [tab-size:4]\",\n        padded ? \"pl-3\" : \"pl-4\",\n        !live && \"pointer-events-none absolute top-0 left-0 select-none\",\n      )}\n    >\n      <code className=\"font-[inherit]\">\n        {model.lines.map((line, i) => {\n          let t = 0;\n          return (\n            <React.Fragment key={i}>\n              <span data-line={i}>\n                {line.map((seg) =>\n                  seg.kind === \"text\" ? (\n                    <span key={`t${t++}`} data-seg=\"\">\n                      <Tokens tokens={seg.tokens} />\n                    </span>\n                  ) : (\n                    <Slot key={`s:${seg.name}:${seg.occ}`} seg={seg} live={live} />\n                  ),\n                )}\n              </span>\n              {i < model.lines.length - 1 ? \"\\n\" : null}\n            </React.Fragment>\n          );\n        })}\n      </code>\n    </pre>\n  );\n});\n\nfunction Tokens({ tokens }: { tokens: Token[] }) {\n  return tokens.map((token, i) =>\n    TOKEN_CLASS[token.type] ? (\n      <span key={i} className={TOKEN_CLASS[token.type]}>\n        {token.text}\n      </span>\n    ) : (\n      <React.Fragment key={i}>{token.text}</React.Fragment>\n    ),\n  );\n}\n\nconst TINT =\n  \"pointer-events-none absolute -inset-x-[3px] inset-y-[2px] rounded-[4px] ring-1 ring-inset\";\n\nfunction Slot({ seg, live }: { seg: SlotSeg; live: boolean }) {\n  const api = React.useContext(SlotContext);\n  const def = api?.slots[seg.name];\n  const label = def?.label ?? seg.name;\n  const interactive = live && !!api && !!def;\n  const editing = interactive && api.editing?.name === seg.name && api.editing.occ === seg.occ;\n  const open = interactive && api.menu?.name === seg.name && api.menu.occ === seg.occ;\n\n  let body: React.ReactNode;\n  if (editing) {\n    body = <SlotInput seg={seg} label={label} />;\n  } else if (interactive && def.options?.length) {\n    body = (\n      <button\n        type=\"button\"\n        aria-haspopup=\"listbox\"\n        aria-expanded={open}\n        aria-label={`${label}: ${api.values[seg.name] ?? \"\"}`}\n        onClick={(event) => (open ? api.closeMenu() : api.openMenu(seg.name, seg.occ, event.currentTarget))}\n        className=\"relative inline-flex cursor-pointer items-baseline rounded-[3px] outline-none focus-visible:ring-2 focus-visible:ring-sky-500/60\"\n      >\n        <span data-text=\"\">\n          <Tokens tokens={seg.tokens} />\n        </span>\n        <svg\n          aria-hidden\n          viewBox=\"0 0 10 10\"\n          className={cn(\n            `ml-[3px] size-2.5 self-center text-muted-foreground transition-transform ${TIMING.chevron}`,\n            open && \"rotate-180\",\n          )}\n        >\n          <path d=\"M2 3.75 5 6.75l3-3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n        </svg>\n      </button>\n    );\n  } else if (interactive && def.editable) {\n    body = (\n      <span\n        role=\"button\"\n        tabIndex={0}\n        aria-label={`Edit ${label}: ${api.values[seg.name] ?? \"\"}`}\n        data-text=\"\"\n        onClick={(event) => {\n          if (!window.getSelection()?.isCollapsed) return;\n          const caret = caretFromPoint(event.currentTarget, event.clientX, event.clientY);\n          pendingCaret.set(`${seg.name}:${seg.occ}`, caret ?? seg.esc.text.length);\n          api.startEdit(seg.name, seg.occ);\n        }}\n        onKeyDown={(event) => {\n          if (event.key !== \"Enter\" && event.key !== \" \" && event.key !== \"F2\") return;\n          if (event.metaKey || event.ctrlKey) return;\n          event.preventDefault();\n          pendingCaret.set(`${seg.name}:${seg.occ}`, seg.esc.text.length);\n          api.startEdit(seg.name, seg.occ);\n        }}\n        className=\"inline-block min-w-[1ch] cursor-text rounded-[3px] underline decoration-sky-500/40 decoration-dotted underline-offset-[5px] outline-none focus-visible:ring-2 focus-visible:ring-sky-500/60\"\n      >\n        <Tokens tokens={seg.tokens} />\n      </span>\n    );\n  } else if (def?.secret) {\n    body = <Secret seg={seg} label={label} interactive={interactive} />;\n  } else {\n    body = (\n      <span data-text=\"\">\n        <Tokens tokens={seg.tokens} />\n      </span>\n    );\n  }\n\n  return (\n    <span data-slot={seg.name} data-occ={seg.occ} className=\"group/slot relative inline-block\">\n      <span\n        aria-hidden\n        className={cn(\n          TINT,\n          `bg-sky-500/[0.07] ring-sky-500/15 transition-opacity ${TIMING.tint} dark:bg-sky-400/[0.08] dark:ring-sky-400/15`,\n          interactive && (def?.editable || def?.options) && \"group-hover/slot:bg-sky-500/[0.12] dark:group-hover/slot:bg-sky-400/[0.14]\",\n          (editing || open) && \"bg-sky-500/[0.12] ring-sky-500/40 dark:bg-sky-400/[0.14] dark:ring-sky-400/40\",\n        )}\n      />\n      <span\n        aria-hidden\n        data-glow=\"\"\n        className={cn(TINT, \"bg-sky-500/20 opacity-0 ring-sky-500/55 dark:bg-sky-400/20 dark:ring-sky-400/55\")}\n      />\n      <span className=\"relative\">{body}</span>\n    </span>\n  );\n}\n\nfunction caretFromPoint(container: HTMLElement, x: number, y: number) {\n  let node: Node | null = null;\n  let offset = 0;\n  if (typeof document.caretPositionFromPoint === \"function\") {\n    const position = document.caretPositionFromPoint(x, y);\n    if (position) {\n      node = position.offsetNode;\n      offset = position.offset;\n    }\n  } else if (typeof document.caretRangeFromPoint === \"function\") {\n    const range = document.caretRangeFromPoint(x, y);\n    if (range) {\n      node = range.startContainer;\n      offset = range.startOffset;\n    }\n  }\n  if (!node || !container.contains(node)) return null;\n  const range = document.createRange();\n  range.selectNodeContents(container);\n  range.setEnd(node, offset);\n  return range.toString().length;\n}\n\n// Where the caret goes when a value turns into an input, keyed by slot.\nconst pendingCaret = new Map<string, number>();\n\nfunction SlotInput({ seg, label }: { seg: SlotSeg; label: string }) {\n  const api = React.useContext(SlotContext)!;\n  const ref = React.useRef<HTMLInputElement>(null);\n  const raw = api.values[seg.name] ?? \"\";\n  const original = React.useRef(raw);\n  const caret = React.useRef<number | null>(null);\n  const key = `${seg.name}:${seg.occ}`;\n  // Read once: Strict Mode runs the mount effect twice.\n  const [startAt] = React.useState(() => pendingCaret.get(key) ?? seg.esc.text.length);\n\n  React.useLayoutEffect(() => {\n    const input = ref.current;\n    if (!input) return;\n    pendingCaret.delete(key);\n    input.focus({ preventScroll: true });\n    input.setSelectionRange(startAt, startAt);\n  }, [key, startAt]);\n\n  React.useLayoutEffect(() => {\n    if (caret.current === null || !ref.current) return;\n    ref.current.setSelectionRange(caret.current, caret.current);\n    caret.current = null;\n  }, [seg.esc.text]);\n\n  const tone = TOKEN_CLASS[seg.tokens[0]?.type ?? \"plain\"];\n\n  return (\n    <span className=\"inline-grid align-baseline\">\n      <span aria-hidden className=\"invisible col-start-1 row-start-1 pr-[2px] whitespace-pre\">\n        {seg.esc.text || \" \"}\n      </span>\n      <input\n        ref={ref}\n        value={seg.esc.text}\n        size={1}\n        spellCheck={false}\n        autoComplete=\"off\"\n        aria-label={label}\n        onChange={(event) => {\n          const input = event.currentTarget;\n          const edit = applyEdit(raw, seg.esc, input.value, input.selectionStart ?? input.value.length);\n          caret.current = escapeValue(edit.raw.slice(0, edit.caret), seg.ctx, seg.lang).text.length;\n          api.setValue(seg.name, edit.raw);\n        }}\n        onKeyDown={(event) => {\n          if (event.metaKey || event.ctrlKey) return;\n          if (event.key === \"Enter\") {\n            event.preventDefault();\n            event.currentTarget.blur();\n          } else if (event.key === \"Escape\") {\n            event.preventDefault();\n            api.setValue(seg.name, original.current);\n            event.currentTarget.blur();\n          }\n        }}\n        onBlur={() => api.endEdit()}\n        className={cn(\n          \"col-start-1 row-start-1 m-0 w-full min-w-0 border-0 bg-transparent p-0 font-[inherit] text-[length:inherit] leading-[inherit] outline-none\",\n          tone,\n        )}\n      />\n    </span>\n  );\n}\n\nfunction maskOf(text: string) {\n  const prefix = /^(?:[a-z]+_(?:live|test)_|[a-z]{2,8}[-_])/i.exec(text)?.[0] ?? text.slice(0, 3);\n  return prefix + \"•\".repeat(Math.max(4, text.length - prefix.length));\n}\n\nfunction Secret({ seg, label, interactive }: { seg: SlotSeg; label: string; interactive: boolean }) {\n  const [hover, setHover] = React.useState(false);\n  const [focus, setFocus] = React.useState(false);\n  const [pinned, setPinned] = React.useState(false);\n  const pointer = React.useRef(\"mouse\");\n  const shown = interactive && (hover || focus || pinned);\n  const tone = TOKEN_CLASS[seg.tokens[0]?.type ?? \"plain\"];\n  return (\n    <span\n      data-text=\"\"\n      tabIndex={interactive ? 0 : undefined}\n      aria-label={interactive ? `${label}, hidden. Focus to show it.` : undefined}\n      onPointerDown={(event) => (pointer.current = event.pointerType)}\n      onPointerEnter={(event) => event.pointerType === \"mouse\" && setHover(true)}\n      onPointerLeave={() => setHover(false)}\n      onFocus={() => setFocus(true)}\n      onBlur={() => {\n        setFocus(false);\n        setPinned(false);\n      }}\n      onClick={() => pointer.current !== \"mouse\" && setPinned((p) => !p)}\n      className=\"inline-grid cursor-default rounded-[3px] align-baseline outline-none focus-visible:ring-2 focus-visible:ring-sky-500/60\"\n    >\n      <span\n        aria-hidden={!shown}\n        className={cn(`col-start-1 row-start-1 transition-opacity ${TIMING.secret}`, shown ? \"opacity-100\" : \"opacity-0\")}\n      >\n        <Tokens tokens={seg.tokens} />\n      </span>\n      <span\n        aria-hidden\n        className={cn(\n          `col-start-1 row-start-1 transition-opacity ${TIMING.secret} select-none`,\n          tone,\n          shown ? \"opacity-0\" : \"opacity-100\",\n        )}\n      >\n        {maskOf(seg.esc.text)}\n      </span>\n    </span>\n  );\n}\n\nfunction OptionMenu({\n  menu,\n  options,\n  value,\n  label,\n  reduced,\n  onPick,\n  onClose,\n}: {\n  menu: NonNullable<Menu>;\n  options: string[];\n  value: string;\n  label: string;\n  reduced: boolean;\n  onPick: (value: string) => void;\n  onClose: (refocus: boolean) => void;\n}) {\n  const ref = React.useRef<HTMLDivElement>(null);\n  const [focused, setFocused] = React.useState(() => Math.max(0, options.indexOf(value)));\n\n  React.useLayoutEffect(() => {\n    ref.current?.querySelectorAll<HTMLElement>(\"[role=option]\")[focused]?.focus({ preventScroll: true });\n  }, [focused]);\n\n  React.useEffect(() => {\n    // The anchor toggles the menu itself, so a press on it is left alone.\n    const away = (event: PointerEvent) => {\n      const target = event.target as Element | null;\n      if (ref.current?.contains(target) || target?.closest?.(`[data-slot=\"${menu.name}\"]`)) return;\n      onClose(false);\n    };\n    document.addEventListener(\"pointerdown\", away, true);\n    return () => document.removeEventListener(\"pointerdown\", away, true);\n  }, [menu.name, onClose]);\n\n  return (\n    <motion.div\n      ref={ref}\n      role=\"listbox\"\n      aria-label={label}\n      initial={reduced ? { opacity: 0 } : { opacity: 0, y: -4, scale: 0.97 }}\n      animate={{ opacity: 1, y: 0, scale: 1 }}\n      transition={TIMING.menu}\n      style={{ left: menu.x, top: menu.y, transformOrigin: \"top left\" }}\n      onKeyDown={(event) => {\n        if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n          event.preventDefault();\n          const step = event.key === \"ArrowDown\" ? 1 : -1;\n          setFocused((i) => (i + step + options.length) % options.length);\n        } else if (event.key === \"Home\" || event.key === \"End\") {\n          event.preventDefault();\n          setFocused(event.key === \"Home\" ? 0 : options.length - 1);\n        } else if (event.key === \"Escape\") {\n          event.preventDefault();\n          onClose(true);\n        } else if (event.key === \"Tab\") {\n          onClose(false);\n        }\n      }}\n      className=\"absolute z-20 min-w-40 rounded-lg border border-border bg-background p-1 shadow-[0_12px_32px_-12px_rgb(0_0_0/0.25)]\"\n    >\n      {options.map((option, i) => (\n        <button\n          key={option}\n          type=\"button\"\n          role=\"option\"\n          aria-selected={option === value}\n          tabIndex={i === focused ? 0 : -1}\n          onClick={() => {\n            onPick(option);\n            onClose(true);\n          }}\n          onPointerMove={() => setFocused(i)}\n          className=\"flex w-full cursor-pointer items-center justify-between gap-4 rounded-md px-2 py-1.5 text-left font-mono text-[12.5px] outline-none focus:bg-muted\"\n        >\n          {option}\n          <svg aria-hidden viewBox=\"0 0 12 12\" className={cn(\"size-3\", option === value ? \"opacity-100\" : \"opacity-0\")}>\n            <path d=\"m2.5 6.25 2.25 2.25 4.75-5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n          </svg>\n        </button>\n      ))}\n    </motion.div>\n  );\n}\n\nfunction CopyButton({ text }: { text: string }) {\n  const [copied, setCopied] = React.useState(false);\n  const timer = React.useRef(0);\n  React.useEffect(() => () => window.clearTimeout(timer.current), []);\n  return (\n    <button\n      type=\"button\"\n      onClick={() => {\n        navigator.clipboard?.writeText(text).catch(() => {});\n        setCopied(true);\n        window.clearTimeout(timer.current);\n        timer.current = window.setTimeout(() => setCopied(false), TIMING.copied);\n      }}\n      aria-label={copied ? \"Copied\" : \"Copy code\"}\n      className=\"inline-flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-2 text-[12px] font-medium text-muted-foreground outline-none transition-colors hover:bg-foreground/5 hover:text-foreground focus-visible:ring-2 focus-visible:ring-foreground/30\"\n    >\n      <span className=\"relative size-3.5\">\n        <svg\n          aria-hidden\n          viewBox=\"0 0 16 16\"\n          className={cn(`absolute inset-0 transition-all ${TIMING.button}`, copied ? \"scale-50 opacity-0\" : \"scale-100 opacity-100\")}\n        >\n          <rect x=\"5.25\" y=\"5.25\" width=\"8\" height=\"8\" rx=\"1.75\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\n          <path d=\"M10.75 3.25v-.5c0-.83-.67-1.5-1.5-1.5h-5.5c-.83 0-1.5.67-1.5 1.5v5.5c0 .83.67 1.5 1.5 1.5h.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" />\n        </svg>\n        <svg\n          aria-hidden\n          viewBox=\"0 0 16 16\"\n          className={cn(\n            `absolute inset-0 text-emerald-600 transition-all ${TIMING.button} dark:text-emerald-400`,\n            copied ? \"scale-100 opacity-100\" : \"scale-50 opacity-0\",\n          )}\n        >\n          <path d=\"m3.5 8.5 3 3 6-7\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n        </svg>\n      </span>\n      <span className=\"hidden @sm:inline\">{copied ? \"Copied\" : \"Copy\"}</span>\n    </button>\n  );\n}\n\n/* ── Run footer ─────────────────────────────────────────── */\n\nconst isMac = () => /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);\n\nfunction Footer({\n  run,\n  onToggle,\n  statusText,\n  hint,\n  hydrated,\n  className,\n}: {\n  run: RunStore;\n  onToggle: () => void;\n  statusText: string;\n  hint: boolean;\n  hydrated: boolean;\n  className?: string;\n}) {\n  const phase = useRun(run, (s) => s.phase);\n  const runId = useRun(run, (s) => s.runId);\n  const mac = React.useSyncExternalStore(noopSubscribe, isMac, () => true);\n  const active = isActive(phase);\n  const label = active ? \"Stop\" : runId ? \"Run again\" : \"Run\";\n\n  return (\n    <div className={cn(\"flex min-h-12 items-center gap-3 border-t border-border px-2 py-2\", className)}>\n      <button\n        type=\"button\"\n        onClick={onToggle}\n        aria-keyshortcuts=\"Meta+Enter Control+Enter\"\n        className={cn(\n          `inline-flex h-8 shrink-0 cursor-pointer items-center gap-2 rounded-md pr-1.5 pl-2.5 text-[13px] font-medium outline-none transition-colors ${TIMING.button} focus-visible:ring-2 focus-visible:ring-foreground/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background`,\n          active ? \"bg-foreground/10 text-foreground hover:bg-foreground/15\" : \"bg-foreground text-background hover:bg-foreground/90\",\n        )}\n      >\n        <span className=\"relative size-3\">\n          <svg aria-hidden viewBox=\"0 0 12 12\" className={cn(`absolute inset-0 transition-all ${TIMING.button}`, active ? \"scale-50 opacity-0\" : \"opacity-100\")}>\n            {runId && !active ? (\n              <path d=\"M10 6a4 4 0 1 1-1.17-2.83M10 1.75v2.5H7.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n            ) : (\n              <path d=\"M3.5 2.2v7.6a.5.5 0 0 0 .76.43l6.1-3.8a.5.5 0 0 0 0-.86l-6.1-3.8a.5.5 0 0 0-.76.43Z\" fill=\"currentColor\" />\n            )}\n          </svg>\n          <svg aria-hidden viewBox=\"0 0 12 12\" className={cn(`absolute inset-0 transition-all ${TIMING.button}`, active ? \"opacity-100\" : \"scale-50 opacity-0\")}>\n            <rect x=\"2.5\" y=\"2.5\" width=\"7\" height=\"7\" rx=\"1.25\" fill=\"currentColor\" />\n          </svg>\n        </span>\n        <span className=\"inline-grid\">\n          {[\"Run\", \"Stop\", \"Run again\"].map((text) => (\n            <span\n              key={text}\n              aria-hidden={text !== label}\n              className={cn(\n                `col-start-1 row-start-1 text-left transition-all ${TIMING.button}`,\n                text === label ? \"translate-y-0 opacity-100\" : \"pointer-events-none translate-y-1 opacity-0\",\n              )}\n            >\n              {text}\n            </span>\n          ))}\n        </span>\n        <kbd\n          className={cn(\n            \"rounded px-1 font-sans text-[11px] leading-5 font-medium [@media(pointer:coarse)]:hidden\",\n            active ? \"bg-foreground/10 text-muted-foreground\" : \"bg-background/15 text-background/70\",\n          )}\n        >\n          {hydrated && !mac ? \"Ctrl ↵\" : \"⌘↵\"}\n        </kbd>\n      </button>\n      <Status run={run} statusText={statusText} hint={hint} />\n    </div>\n  );\n}\n\nfunction Status({ run, statusText, hint }: { run: RunStore; statusText: string; hint: boolean }) {\n  const phase = useRun(run, (s) => s.phase);\n  const count = useRun(run, (s) => s.chunks.length);\n  const startedAt = useRun(run, (s) => s.startedAt);\n  const firstAt = useRun(run, (s) => s.firstAt);\n  const endedAt = useRun(run, (s) => s.endedAt);\n  const error = useRun(run, (s) => s.error);\n  const seconds = `${((endedAt - startedAt) / 1000).toFixed(1)} s`;\n  const tokens = `${count} ${count === 1 ? \"token\" : \"tokens\"}`;\n\n  let dot = \"bg-muted-foreground/40\";\n  let text: React.ReactNode = hint ? \"Highlighted values are editable\" : \"\";\n  if (phase === \"waiting\") {\n    dot = \"bg-amber-500 animate-pulse\";\n    text = (\n      <>\n        waiting <Ticker since={startedAt} />\n      </>\n    );\n  } else if (phase === \"streaming\") {\n    dot = \"bg-emerald-500 animate-pulse\";\n    text = `streaming · ${tokens}`;\n  } else if (phase === \"done\") {\n    dot = \"bg-emerald-500\";\n    text = `${statusText} · ${seconds} · ${tokens}`;\n  } else if (phase === \"stopped\") {\n    text = `Stopped · ${seconds} · ${tokens}`;\n  } else if (phase === \"cancelled\") {\n    text = \"Cancelled · a value changed\";\n  } else if (phase === \"error\") {\n    dot = \"bg-rose-500\";\n    text = `Error · ${error}`;\n  }\n\n  return (\n    <span className=\"flex min-w-0 items-center gap-2 font-mono text-[12px] text-muted-foreground tabular-nums\">\n      {text ? <span aria-hidden className={cn(\"size-1.5 shrink-0 rounded-full\", dot)} /> : null}\n      <motion.span\n        key={phase}\n        initial={phase === \"idle\" ? false : { opacity: 0, y: 3 }}\n        animate={{ opacity: 1, y: 0 }}\n        transition={TIMING.status}\n        className=\"truncate\"\n        title={phase === \"streaming\" && firstAt ? `first token after ${Math.round(firstAt - startedAt)} ms` : undefined}\n      >\n        {text}\n      </motion.span>\n    </span>\n  );\n}\n\n/** Counts milliseconds up from `since` without re-rendering. */\nfunction Ticker({ since }: { since: number }) {\n  const ref = React.useRef<HTMLSpanElement>(null);\n  React.useEffect(() => {\n    let frame = 0;\n    const tick = () => {\n      if (ref.current) ref.current.textContent = `${Math.max(0, Math.round(performance.now() - since))} ms`;\n      frame = requestAnimationFrame(tick);\n    };\n    tick();\n    return () => cancelAnimationFrame(frame);\n  }, [since]);\n  return <span ref={ref}>0 ms</span>;\n}\n\n/* ── Response ───────────────────────────────────────────── */\n\nfunction Output({ run, lang }: { run: RunStore; lang: Lang }) {\n  const chunks = useRun(run, (s) => s.chunks);\n  const phase = useRun(run, (s) => s.phase);\n  const echo = useRun(run, (s) => s.echo);\n  const landed = useRun(run, (s) => s.landed);\n  const runId = useRun(run, (s) => s.runId);\n\n  // Highlight what has arrived, cut at chunk edges so each chunk keeps its own\n  // span and fades in once, even when its color changes as more arrives.\n  const pieces = React.useMemo(() => {\n    const text = chunks.join(\"\");\n    const tokens = tokenize(text, lang);\n    const out: { key: string; type: TokenType; text: string }[] = [];\n    let chunk = 0;\n    let chunkEnd = chunks[0]?.length ?? 0;\n    let pos = 0;\n    let part = 0;\n    for (const token of tokens) {\n      let k = 0;\n      while (k < token.text.length) {\n        while (pos >= chunkEnd && chunk < chunks.length - 1) {\n          chunk++;\n          chunkEnd += chunks[chunk].length;\n          part = 0;\n        }\n        const take = Math.min(token.text.length - k, chunkEnd - pos);\n        if (take <= 0) break;\n        out.push({ key: `${chunk}:${part++}`, type: token.type, text: token.text.slice(k, k + take) });\n        k += take;\n        pos += take;\n      }\n    }\n    return out;\n  }, [chunks, lang]);\n\n  const active = isActive(phase);\n  return (\n    <div key={runId} className=\"font-mono text-[13px] leading-6 break-words whitespace-pre-wrap\">\n      {echo != null ? (\n        <div className=\"mb-2 flex gap-2 text-muted-foreground\">\n          <span aria-hidden className=\"select-none\">\n            ›\n          </span>\n          <span\n            data-echo-target=\"\"\n            className={cn(`min-w-0 transition-opacity ${TIMING.echo}`, landed ? \"opacity-100\" : \"opacity-0\")}\n          >\n            {echo}\n          </span>\n        </div>\n      ) : null}\n      <div aria-live=\"off\">\n        {(landed ? pieces : []).map((piece) => (\n          <span\n            key={piece.key}\n            className={cn(`transition-opacity ${TIMING.token} starting:opacity-0`, TOKEN_CLASS[piece.type])}\n          >\n            {piece.text}\n          </span>\n        ))}\n        {active ? (\n          <span\n            aria-hidden\n            className={cn(\n              \"ml-px inline-block h-[1.05em] w-[0.5ch] translate-y-[0.18em] rounded-[1px] bg-foreground/70\",\n              phase === \"waiting\" && \"animate-pulse\",\n            )}\n          />\n        ) : null}\n      </div>\n      {phase === \"stopped\" || phase === \"cancelled\" ? (\n        <div className=\"mt-2 text-[12px] text-muted-foreground\">{phase === \"stopped\" ? \"— stopped\" : \"— cancelled\"}</div>\n      ) : null}\n      {phase === \"error\" ? <div className=\"mt-2 text-[12px] text-rose-600 dark:text-rose-400\">— request failed</div> : null}\n    </div>\n  );\n}\n\n/** Keeps a scroll box pinned to its bottom while the reply grows, unless the reader scrolled up. */\nfunction useStickToBottom(run: RunStore, ref: React.RefObject<HTMLDivElement | null>, mounted: boolean) {\n  const count = useRun(run, (s) => s.chunks.length);\n  const runId = useRun(run, (s) => s.runId);\n  const pinned = React.useRef(true);\n  React.useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const onScroll = () => {\n      pinned.current = el.scrollHeight - el.scrollTop - el.clientHeight < 32;\n    };\n    el.addEventListener(\"scroll\", onScroll, { passive: true });\n    return () => el.removeEventListener(\"scroll\", onScroll);\n  }, [ref, mounted]);\n  React.useLayoutEffect(() => {\n    pinned.current = true;\n  }, [runId]);\n  React.useLayoutEffect(() => {\n    const el = ref.current;\n    if (el && pinned.current) el.scrollTop = el.scrollHeight;\n  }, [count, ref]);\n}\n\nfunction StackPanel({ run, lang, reduced }: { run: RunStore; lang: Lang; reduced: boolean }) {\n  const runId = useRun(run, (s) => s.runId);\n  const phase = useRun(run, (s) => s.phase);\n  const outerRef = React.useRef<HTMLDivElement>(null);\n  const scrollRef = React.useRef<HTMLDivElement>(null);\n  const innerRef = React.useRef<HTMLDivElement>(null);\n  const held = React.useRef(0);\n  const open = runId > 0;\n  useStickToBottom(run, scrollRef, open);\n\n  // A new run keeps the panel at its last height until the reply outgrows it,\n  // so Run again doesn't collapse and regrow it.\n  React.useLayoutEffect(() => {\n    const inner = innerRef.current;\n    if (!inner) return;\n    if (isActive(phase)) {\n      if (!held.current) held.current = inner.offsetHeight;\n      inner.style.minHeight = `${held.current}px`;\n    } else {\n      held.current = 0;\n      inner.style.minHeight = \"\";\n    }\n  }, [phase, runId]);\n\n  // The panel's height follows its content on a spring: opening, growing, settling.\n  React.useEffect(() => {\n    const outer = outerRef.current;\n    const scroll = scrollRef.current;\n    if (!outer || !scroll) return;\n    let controls: AnimationPlaybackControls | null = null;\n    const observer = new ResizeObserver(() => {\n      const to = scroll.offsetHeight;\n      controls?.stop();\n      if (reduced) outer.style.height = `${to}px`;\n      else controls = animate(outer, { height: to }, TIMING.panel);\n    });\n    observer.observe(scroll);\n    return () => {\n      observer.disconnect();\n      controls?.stop();\n    };\n  }, [reduced, open]);\n\n  if (!open) return null;\n  return (\n    <div ref={outerRef} style={{ height: 0 }} className=\"overflow-hidden rounded-b-xl\">\n      <div ref={scrollRef} className=\"max-h-80 overflow-y-auto border-t border-border bg-muted/30 [scrollbar-width:thin]\">\n        <div ref={innerRef} aria-busy={isActive(phase)} className=\"px-4 py-3.5\">\n          <Output run={run} lang={lang} />\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SplitPanel({ run, lang }: { run: RunStore; lang: Lang }) {\n  const runId = useRun(run, (s) => s.runId);\n  const phase = useRun(run, (s) => s.phase);\n  const scrollRef = React.useRef<HTMLDivElement>(null);\n  useStickToBottom(run, scrollRef, true);\n  return (\n    <div className=\"flex min-h-40 min-w-0 flex-col rounded-b-xl border-t border-border bg-muted/30 @3xl:rounded-tr-xl @3xl:rounded-bl-none @3xl:border-t-0 @3xl:border-l\">\n      <div className=\"flex h-11 shrink-0 items-center border-b border-border px-4 text-[12px] font-medium text-muted-foreground\">\n        Response\n      </div>\n      <div className=\"relative min-h-0 flex-1\">\n        <div\n          ref={scrollRef}\n          aria-busy={isActive(phase)}\n          className=\"max-h-80 overflow-y-auto px-4 py-3.5 [scrollbar-width:thin] @3xl:absolute @3xl:inset-0 @3xl:max-h-none\"\n        >\n          {runId ? (\n            <Output run={run} lang={lang} />\n          ) : (\n            <p className=\"flex h-full min-h-20 items-center justify-center text-center text-[13px] text-muted-foreground/70\">\n              Press Run to send the request\n            </p>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n\n/** The copy of the echoed value that flies from the code into the panel. */\nfunction Flyer({ run }: { run: RunStore }) {\n  const flight = useRun(run, (s) => s.flight);\n  const ref = React.useRef<HTMLSpanElement>(null);\n\n  React.useLayoutEffect(() => {\n    const el = ref.current;\n    if (!el || !flight) return;\n    const dx = flight.to.x - flight.from.x;\n    const dy = flight.to.y - flight.from.y;\n    const current = () => run.get().runId === flight.runId;\n    let landed = false;\n    let fade: AnimationPlaybackControls | null = null;\n    // It counts as landed on arrival, not when the spring's long tail settles,\n    // so the echo line takes over right as the copy gets there.\n    const controls = animate(0, 1, {\n      ...TIMING.flight,\n      delay: TIMING.flightDelay,\n      onUpdate: (p) => {\n        el.style.transform = `translate(${dx * p}px, ${dy * p}px)`;\n        el.style.scale = String(1 + 0.05 * Math.sin(Math.PI * Math.min(p, 1))); // lifts off, settles on landing\n        if (landed || p < 0.97 || !current()) return;\n        landed = true;\n        run.set({ landed: true });\n        const out = animate(el, { opacity: [1, 0] }, TIMING.flightFade);\n        out.then(() => current() && run.set({ flight: null }));\n        fade = out;\n      },\n    });\n    return () => {\n      controls.stop();\n      fade?.stop();\n    };\n  }, [flight, run]);\n\n  if (!flight) return null;\n  return (\n    <span\n      ref={ref}\n      aria-hidden\n      style={{ left: flight.from.x, top: flight.from.y }}\n      className=\"pointer-events-none absolute z-10 -mx-[3px] max-w-[min(32rem,80%)] truncate rounded-[4px] bg-sky-500/15 px-[3px] font-mono text-[13px] leading-6 whitespace-pre text-foreground shadow-[0_6px_16px_-8px_rgb(14_165_233/0.5)] ring-1 ring-sky-500/45 ring-inset dark:bg-sky-400/15 dark:ring-sky-400/45\"\n    >\n      {flight.text}\n    </span>\n  );\n}\n\n/** Reads the reply out once, when it's complete, instead of token by token. */\nfunction Announcer({ run }: { run: RunStore }) {\n  const phase = useRun(run, (s) => s.phase);\n  const chunks = useRun(run, (s) => s.chunks);\n  const error = useRun(run, (s) => s.error);\n  let message = \"\";\n  if (phase === \"done\") message = `Response: ${chunks.join(\"\")}`;\n  else if (phase === \"error\") message = `Request failed: ${error}`;\n  return (\n    <div role=\"status\" className=\"sr-only\">\n      {message}\n    </div>\n  );\n}\n"
    }
  ]
}