/* RAI Code — the authoring surface.
 *
 * Layers on top of rai.css and reuses its server-fed theme tokens
 * (--bg, --panel, --border, --accent …) so a theme swap from the host
 * repaints the editor along with everything else. Nothing here invents
 * a hue that rai.css already names.
 *
 * The syntax palette below is the one exception: it transcribes
 * RAILispHighlighter.Palette (RAILispHighlighter.swift), which maps to
 * macOS system colours. Those have no equivalent in the RAI theme
 * block, so they are declared here as their own tokens — themeable,
 * and in one place when the host eventually ships them on the wire.
 */

:root {
  /* Transcribed from RAILispHighlighter.Palette. */
  --syn-comment: #848c9a; /* secondaryLabelColor */
  --syn-string: #5cd98a; /* systemGreen */
  --syn-keyword: #c58af9; /* systemPurple */
  --syn-quoted: #f0a05c; /* systemOrange */
  --syn-literal: #6cb6ff; /* systemBlue */
  --syn-number: #f0a05c; /* systemOrange */
  --syn-special: #6cb6ff; /* systemBlue */
  --syn-verb: #4dd0d8; /* systemTeal */
  --syn-paren: #847e8c; /* was #59616e — 3.03:1 on #0e1116, under the 4.5 floor; parens are the structural cue in a Lisp (Sonar #74) */
  --syn-plain: var(--text);

  /* Decoration tints. The Mac uses systemRed @18% and systemTeal @28%. */
  --dec-failing: rgb(248 81 73 / 18%);
  --dec-paren-match: rgb(77 208 216 / 28%);

  --code-font: 13px / 1.5 ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  --gutter-w: 52px;
  --editor-pad: 12px;
}

/* ── Shell ──────────────────────────────────────────────────────── */

/* `hidden` is a UA rule at the lowest possible specificity, so any class
   here that sets `display` silently defeats it — which had Send and
   Cancel rendering stacked in the same grid cell, and would have done
   the same to the slash badge and the chip row. Restate it as a rule
   that can actually win. */
[hidden] {
  display: none !important;
}

html,
body.code-page {
  height: 100%;
}

body.code-page {
  margin: 0;
  overflow: hidden;
}

.code-shell {
  display: grid;
  grid-template-rows: auto 1fr auto;
  /* An explicit column that can be NARROWER than its content. With no
     column declared, the implicit one is `auto` = min-content, so the
     toolbar's longest row set the page width: a 287px status pill made
     the shell 1471px wide at a 1280 viewport and the whole surface
     scrolled sideways (Sonar, 2026-08-21). minmax(0, 1fr) lets the
     toolbar be the viewport's width and its shrinkable items (the pill)
     give way; the 620px floor below is the real minimum. */
  grid-template-columns: minmax(0, 1fr);
  height: 100vh;
  min-width: 620px;
}

/* ── Toolbar ────────────────────────────────────────────────────── */

.code-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px;
  min-width: 0;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}

.code-toolbar .sep {
  width: 1px;
  height: 18px;
  background: var(--border);
  margin: 0 2px;
}

.tbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 32px;
  height: 28px;
  padding: 0 9px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.tbtn:hover:not(:disabled) {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}

.tbtn:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Run is the verb the whole demo turns on; it had the same physical weight
   as Copy — colour alone, 4.82:1, passing AA and saying nothing about
   hierarchy (Sonar). Filled like Send-when-ready, so the two "go" controls
   read as one family. */
.tbtn.is-run {
  color: var(--accent-fg, #06190d);
  background: var(--accent);
  border-color: var(--accent);
  font-weight: 600;
}
.tbtn.is-run:hover:not(:disabled) {
  filter: brightness(1.08);
}

.tbtn.is-stop {
  color: var(--danger);
}

.tbtn.is-on {
  border-color: var(--accent);
  color: var(--accent);
}

.code-toolbar .spacer {
  flex: 1;
}

/* Run status pill — the Mac's `statusPill`.
   BOUNDED. Sonar measured (2026-08-21, cold read): the message "Restored
   your draft from the server" made a 287px pill, and with nowrap and no
   bound it set the page's minimum width — scrollWidth 1471 at a 1280 or
   1440 viewport, the whole surface (editor included) scrolling sideways
   and the pill itself leaving the screen. 1280 is a 13" Air. The text
   clips with an ellipsis and the full sentence rides on the title. */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--dim);
  font: var(--code-font);
  font-size: 12px;
  white-space: nowrap;
  max-width: min(42vw, 380px);
  min-width: 0;
  flex: 0 1 auto;
}
#status-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.status-pill .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--dim) 60%, transparent);
}

.status-pill.is-running .dot {
  background: var(--warn);
  animation: pulse 1.1s ease-in-out infinite;
}

.status-pill.is-error .dot {
  background: var(--danger);
}

@keyframes pulse {
  50% { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  .status-pill.is-running .dot { animation: none; }
}

/* ── Body: console | main ───────────────────────────────────────── */

.code-body {
  display: grid;
  grid-template-columns: 1fr;
  min-height: 0;
}

.code-body.with-console {
  grid-template-columns: minmax(220px, 280px) 1fr;
}

.code-console {
  display: none;
  flex-direction: column;
  min-height: 0;
  background: var(--panel);
  border-right: 1px solid var(--border);
}

.code-body.with-console .code-console {
  display: flex;
}

.console-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  color: var(--dim);
  font: var(--code-font);
  font-size: 12px;
}

.console-head .spacer {
  flex: 1;
}

.console-body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 10px;
  font: var(--code-font);
  font-size: 11px;
  /* Log lines are body text the user reads, not chrome — so a mix of
     --dim and --text at ~85%, not the --dim used for labels. The
     layout-eyes critic flagged the old --dim-on-panel as failing
     contrast, and it was right: these are output, not decoration. */
  color: color-mix(in srgb, var(--text) 85%, var(--dim));
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.console-result {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  color: var(--text);
  font-size: 12px;
}

.console-result.is-error {
  color: var(--danger);
}

/* ── Main: editor / splitter / chat ─────────────────────────────── */

.code-main {
  display: grid;
  grid-template-rows: 1fr auto auto;
  min-height: 0;
  min-width: 360px;
}

/* ── Editor ─────────────────────────────────────────────────────── */

.rai-editor {
  display: grid;
  grid-template-columns: var(--gutter-w) 1fr;
  min-height: 0;
  background: var(--bg);
  overflow: hidden;
}

.rai-gutter {
  overflow: hidden;
  padding: var(--editor-pad) 8px var(--editor-pad) 0;
  border-right: 1px solid var(--border);
  background: color-mix(in srgb, var(--panel) 60%, var(--bg));
  color: var(--syn-paren);
  font: var(--code-font);
  text-align: right;
  user-select: none;
}

.rai-gutter-line.is-failing {
  color: var(--danger);
  font-weight: 700;
}

.rai-surface {
  position: relative;
  min-height: 0;
  overflow: hidden;
}

/* The two aligned layers. Every property that affects glyph position
   must match exactly between them, which is why they share one font
   shorthand and one padding token rather than being styled apart. */
.rai-highlight,
.rai-input {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: var(--editor-pad);
  border: 0;
  font: var(--code-font);
  tab-size: 2;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.rai-highlight {
  overflow: hidden;
  color: var(--syn-plain);
  pointer-events: none;
}

.rai-highlight code {
  font: inherit;
}

.rai-input {
  resize: none;
  overflow: auto;
  background: transparent;
  /* Text is invisible; the caret and the selection are not. */
  color: transparent;
  caret-color: var(--text);
}

.rai-input::selection {
  background: color-mix(in srgb, var(--accent) 28%, transparent);
}

.rai-input:focus {
  outline: none;
}

/* Syntax */
.rai-tok-comment { color: var(--syn-comment); font-style: italic; }
.rai-tok-string { color: var(--syn-string); }
.rai-tok-keyword { color: var(--syn-keyword); }
.rai-tok-quoted { color: var(--syn-quoted); }
.rai-tok-literal { color: var(--syn-literal); font-weight: 700; }
.rai-tok-number { color: var(--syn-number); }
.rai-tok-special { color: var(--syn-special); font-weight: 700; }
.rai-tok-verb { color: var(--syn-verb); font-weight: 700; }
/* A real RAI word this platform cannot run. Deliberately NOT the plain
   colour — an unstyled word reads as a typo, and this is not a typo; it
   is a word that exists and is out of reach here. Verb hue, no weight,
   dashed underline: recognisably the verb family, visibly not available. */
.rai-tok-unavailable {
  color: var(--syn-verb);
  font-weight: 400;
  opacity: 0.55;
  text-decoration: underline dashed currentColor 1px;
  text-underline-offset: 3px;
}
.rai-tok-paren { color: var(--syn-paren); }
.rai-tok-plain { color: var(--syn-plain); }

/* Decorations */
.rai-dec-failing { background: var(--dec-failing); }
.rai-dec-paren-match { background: var(--dec-paren-match); border-radius: 2px; }

/* Ghost text — the Mac renders italic at secondaryLabel. */
.rai-dec-ghost {
  color: var(--dim) !important;
  font-style: italic;
  background: color-mix(in srgb, var(--info) 10%, transparent);
}

/* Completion popup */
.rai-completion {
  position: absolute;
  z-index: 10;
  max-height: 220px;
  margin: 2px 0 0;
  padding: 4px;
  overflow: auto;
  list-style: none;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 45%);
  font: var(--code-font);
}

.rai-completion li {
  padding: 3px 10px;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}

.rai-completion li:hover,
.rai-completion li.is-selected {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--text);
}

/* ── Splitter ───────────────────────────────────────────────────── */

.code-splitter {
  position: relative;
  height: 4px;
  background: var(--border);
  cursor: row-resize;
}

.code-splitter::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 1px;
  width: 32px;
  height: 2px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--dim) 70%, transparent);
  transform: translateX(-50%);
}

/* A 4px hit target is a mouse-only affordance; widen it invisibly. */
.code-splitter::before {
  content: "";
  position: absolute;
  inset: -4px 0;
}

/* ── Status bar ─────────────────────────────────────────────────── */

.code-status {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 4px 10px;
  background: var(--panel);
  border-top: 1px solid var(--border);
  font: var(--code-font);
  font-size: 12px;
  color: var(--dim);
}

.code-status .spacer {
  flex: 1;
}

.code-status .file {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--text);
}

.code-status .file.is-scratch {
  color: var(--dim);
}

.code-status .dirty {
  color: var(--warn);
  font-weight: 700;
}

.code-status .saved-at {
  color: color-mix(in srgb, var(--dim) 75%, transparent);
}

.auto-pill {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--warn) 18%, transparent);
  color: var(--warn);
  font-weight: 600;
}

/* ── Chat panel ─────────────────────────────────────────────────── */

.chat-panel {
  display: grid;
  grid-template-rows: 3px auto 1fr auto auto;
  height: 100%;
  min-height: 80px;
  /* A layer distinct from the near-black editor canvas above it. The
     Mac tints slate for the same reason: system colours alone don't
     separate the two enough on a dark appearance. */
  background: color-mix(in srgb, var(--panel) 82%, var(--bg));
}

/* Deliberate seam, not a 1px divider that vanishes against the editor. */
.chat-seam {
  background: var(--border);
}

.chat-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  color: var(--dim);
}

.chat-head .spacer { flex: 1; }

.chat-title {
  font-weight: 600;
  letter-spacing: 0.3px;
  color: var(--text);
}

.chat-auto {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  user-select: none;
}

.chat-model {
  height: 22px;
  padding: 0 4px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--panel-2);
  color: var(--text);
  font: var(--code-font);
  font-size: 11px;
}

.chat-new { height: 22px; padding: 0 8px; font-size: 11px; }

.chat-history {
  min-height: 0;
  overflow-y: auto;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* An overlay scrollbar is invisible until hovered, so a panel holding
     more than it shows looks complete — Sonar found the welcome's own
     second path ("open the starter templates") had never been seen by
     anyone. Keep the bar visible. */
  scrollbar-width: thin;
  scrollbar-gutter: stable;
}
.chat-history::-webkit-scrollbar { width: 8px; }
.chat-history::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--dim) 45%, transparent);
  border-radius: 4px;
}

/* ── Rows ──────────────────────────────────────────────────────── */

.chat-row {
  display: grid;
  grid-template-columns: 16px 1fr;
  gap: 8px;
  align-items: start;
}

.chat-glyph {
  color: var(--dim);
  font-size: 12px;
  line-height: 1.5;
  text-align: center;
}

.chat-body { min-width: 0; }

.chat-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: 1.5;
}

.chat-text.dim { color: var(--dim); }
.chat-text.err { color: var(--danger); }
.chat-text.mono { font: var(--code-font); font-size: 11px; }

.chat-row.is-user .chat-text { color: var(--text); font-weight: 500; }

/* A looked-up answer's sources: numbered as the answer cites them, each opens its page in a new window. */
.chat-sources { margin: 6px 0 0; padding-left: 22px; font-size: 12px; line-height: 1.6; color: var(--dim); }
.chat-sources a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; }
.chat-sources a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chat-row.is-user .chat-glyph { color: var(--accent); }
.chat-row.is-proposal .chat-glyph,
.chat-row.is-explanation .chat-glyph { color: var(--info); }
.chat-row.is-run-ok .chat-glyph { color: var(--ok); }
.chat-row.is-run-fail .chat-glyph,
.chat-row.is-error .chat-glyph { color: var(--danger); }
.chat-row.is-done .chat-glyph { color: var(--accent); }

/* Tier accents — yellow / orange / red, ascending severity. */
.err-head { font-weight: 700; margin-bottom: 2px; }
.err-head.is-parse { color: var(--warn); }
.err-head.is-primitive { color: #f0a05c; }
.err-head.is-runtime { color: var(--danger); }

/* ── Diff block ────────────────────────────────────────────────── */

.diff {
  margin: 6px 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
  font: var(--code-font);
  font-size: 11px;
}

.diff-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  background: var(--panel-2);
  color: var(--dim);
  border-bottom: 1px solid var(--border);
}

.diff-add { color: var(--ok); font-weight: 700; }
.diff-del { color: var(--danger); font-weight: 700; }
.diff-why { color: var(--dim); font-style: italic; margin-left: auto; }

.diff-line {
  display: grid;
  grid-template-columns: 34px 12px 1fr;
  padding: 0 8px 0 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.diff-num { color: var(--syn-paren); text-align: right; padding-right: 8px; user-select: none; }
.diff-sign { user-select: none; }

/* Colour carries the meaning; the +/− sign column carries it again, so
   the block still reads for anyone who can't separate red from green. */
.diff-line.is-added { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--text); }
.diff-line.is-added .diff-sign { color: var(--ok); }
.diff-line.is-removed { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--dim); }
.diff-line.is-removed .diff-sign { color: var(--danger); }
.diff-line.is-same { color: var(--dim); }

/* ── Actions ───────────────────────────────────────────────────── */

.chat-actions { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.chat-actions .tbtn { height: 24px; font-size: 12px; }
.act-accept { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); color: var(--accent); }
.act-reject { color: var(--dim); }
.key {
  margin-left: 4px;
  padding: 0 4px;
  border: 1px solid var(--border);
  border-radius: 3px;
  font: var(--code-font);
  font-size: 10px;
  color: var(--dim);
}

/* ── Welcome + chips ───────────────────────────────────────────── */

.chat-welcome { display: flex; flex-direction: column; gap: 6px; }
.chat-welcome-head { font-weight: 600; color: var(--accent); }
.chat-welcome-blurb { margin: 0; color: var(--dim); font-size: 13px; }
.chat-welcome-heading { color: var(--dim); font-size: 12px; margin-top: 2px; }

.chat-chips {
  display: flex;
  gap: 6px;
  padding: 6px 12px;
  overflow-x: auto;
  border-top: 1px solid var(--border);
  scrollbar-width: thin;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--text);
  font: var(--code-font);
  font-size: 11px;
  cursor: pointer;
  white-space: nowrap;
}

.chip:hover { border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
/* A suggestion chip PREFILLS the composer; it does not send. Without a
   chosen state the click looked like nothing happened (Sonar watched a
   still screen for three minutes). The chip marks itself, and Send
   lights up the moment the composer has text. */
.chip.is-chosen {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--panel-2));
}
.chat-send.is-ready {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-fg, #06190d);
}
.chip-wide { border-radius: 6px; align-self: flex-start; max-width: 100%; }
.chip-glyph { font-size: 10px; }

/* ── Input bar ─────────────────────────────────────────────────── */

.chat-input-bar {
  display: grid;
  /* attach | input | send — the attach button is a fixed-width
     affordance beside the field, not a row of its own. A child with
     no grid-area auto-places into a stretched implicit row (the 🖼
     shipped 1121px wide, icon marooned mid-row, caught by screenshot
     before anyone clicked it), so every child of this bar needs a
     named area. */
  grid-template-columns: auto auto 1fr auto;
  grid-template-areas:
    "badge  badge badge badge"
    "micst  micst micst micst"
    "attach mic   input send"
    "doors  doors doors doors";
  gap: 6px;
  padding: 8px 10px;
  border-top: 1px solid var(--border);
  align-items: end;
}

/* The two doors under the composer — Patterns, From a web page… — share
   the chips' look (they are ways to ask) and take the bar's own row so
   they never stretch into the input column (the 🖼 lesson above). */
.chat-doors {
  grid-area: doors;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

#chat-attach {
  grid-area: attach;
  height: 30px;
  width: 34px;
  padding: 0;
  font-size: 15px;
  line-height: 1;
}

/* The microphone sits beside the attach button because both are ways to put
   something INTO the message. Named area, per the 🖼 lesson directly above —
   this bar auto-places an unnamed child into a stretched implicit row. */
#chat-mic {
  grid-area: mic;
  height: 30px;
  width: 34px;
  padding: 0;
  font-size: 15px;
  line-height: 1;
}

#chat-mic[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Recording has to be unmistakable, and colour alone is not enough — someone
   who cannot distinguish the red is otherwise looking at an ordinary button
   while their microphone is live. The pulse carries the same fact. */
#chat-mic.is-recording {
  background: color-mix(in srgb, var(--error) 22%, transparent);
  color: var(--error);
  animation: chat-mic-pulse 1.4s ease-in-out infinite;
}

@keyframes chat-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--error) 45%, transparent); }
  50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--error) 0%, transparent); }
}

/* Respect a reduced-motion preference: keep the colour, drop the pulse. */
@media (prefers-reduced-motion: reduce) {
  #chat-mic.is-recording { animation: none; }
}

/* Transcribing and failed — the Mac's other two orb states. Each is a
   DIFFERENT colour and a DIFFERENT glyph, never colour alone: the glyph is
   what someone who cannot separate the hues still reads. */
#chat-mic.is-busy {
  background: color-mix(in srgb, var(--info) 20%, transparent);
  color: var(--info);
}

#chat-mic.is-failed {
  background: color-mix(in srgb, var(--warning, var(--error)) 20%, transparent);
  color: var(--warning, var(--error));
}

.chat-mic-status {
  grid-area: micst;
  font-size: 11px;
  color: var(--dim);
}

.chat-mic-status[data-kind="error"] { color: var(--error); }
.chat-mic-status[data-kind="live"]  { color: var(--info); }

.chat-badge {
  grid-area: badge;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
}

.badge-verb {
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--info) 20%, transparent);
  color: var(--info);
  font: var(--code-font);
  font-size: 11px;
  font-weight: 700;
}

.badge-hint { color: var(--dim); }

.chat-input {
  grid-area: input;
  resize: none;
  min-height: 30px;
  max-height: 140px;
  padding: 6px 9px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--bg);
  color: var(--text);
  font: 13px/1.45 -apple-system, "SF Pro Text", "Segoe UI", sans-serif;
  overflow-y: auto;
}

.chat-input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 60%, var(--border));
}

.chat-input::placeholder { color: color-mix(in srgb, var(--dim) 85%, transparent); }

.chat-send, .chat-cancel { grid-area: send; height: 30px; }

/* ── Phase 2: the live preview pane ─────────────────────────────── */
/* Third grid column; composes with the console's optional first.
   [hidden] must be restated — the pane's display rule would beat the
   UA default (the #auth-banner lesson). */

#preview[hidden] { display: none; }

/* The preview column is drag-resizable: its width tracks --preview-w
   (set from the splitter), clamped so neither side can be squeezed out.
   The :surface panel case below stays pinned to real dock width. */
.code-body.has-preview { grid-template-columns: 1fr clamp(300px, var(--preview-w, 44%), 82%); }
.code-body.with-console.has-preview {
  grid-template-columns: minmax(220px, 280px) 1fr clamp(300px, var(--preview-w, 44%), 78%);
}
/* :surface panel previews at real dock width, not a flattering one. */
.code-body.has-preview:has(#preview.as-panel) {
  grid-template-columns: 1fr 340px;
}
.code-body.with-console.has-preview:has(#preview.as-panel) {
  grid-template-columns: minmax(220px, 280px) 1fr 340px;
}

.code-preview-pane {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  border-left: 1px solid var(--border, #333);
  background: var(--bg, #101014);
}

/* Drag handle on the pane's inner edge — a vertical bar against the
   main column (wide), a horizontal one against the editor (stacked,
   see the phone query below). Mirrors .code-splitter's grip look. */
.preview-splitter {
  position: absolute;
  z-index: 5;
  top: 0;
  bottom: 0;
  left: -3px;
  width: 8px;
  cursor: col-resize;
  touch-action: none;
}
.preview-splitter::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 3px;
  width: 2px;
  height: 32px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--dim) 70%, transparent);
  transform: translateY(-50%);
}
.preview-splitter:focus-visible { outline: 2px solid var(--accent, #4cc9f0); outline-offset: -2px; }

.preview-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border, #333);
  font-size: 12px;
  font-weight: 600;
}

.preview-head .spacer { flex: 1; }
.preview-head .error { color: var(--danger, #ff6b6b); }

.preview-mount {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 10px;
}

/* Phone: the split collapses — preview stacks under the editor as a
   half-height sheet. */
@media (max-width: 900px) {
  .code-body.has-preview,
  .code-body.with-console.has-preview,
  .code-body.has-preview:has(#preview.as-panel),
  .code-body.with-console.has-preview:has(#preview.as-panel) {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr clamp(200px, var(--preview-h, 60vh), 88vh);
  }
  .code-preview-pane { border-left: none; border-top: 1px solid var(--border, #333); }
  /* Handle flips to the top edge — a horizontal drag bar. */
  .preview-splitter {
    top: -3px;
    bottom: auto;
    left: 0;
    right: 0;
    width: auto;
    height: 8px;
    cursor: row-resize;
  }
  .preview-splitter::after {
    top: 3px;
    left: 50%;
    width: 32px;
    height: 2px;
    transform: translateX(-50%);
  }
}

/* The pane when the app registered but wouldn't render. Was a
   monospace dump of the compiler message — a second copy of what the
   console already has open, in the space where the person expected to
   see their app. Now: a bug, one sentence, and a pointer to the place
   that does carry the detail. */
.preview-bug {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 100%;
  min-height: 140px;
  padding: 18px 14px;
  box-sizing: border-box;
  text-align: center;
}

.preview-bug-glyph {
  font-size: 40px;
  line-height: 1;
  /* A slow, small wobble. Enough to read as alive, not enough to be a
     thing you have to wait out or watch twice. */
  animation: preview-bug-wobble 2.4s ease-in-out infinite;
}

@keyframes preview-bug-wobble {
  0%, 100% { transform: rotate(-6deg) translateY(0); }
  50%      { transform: rotate(6deg) translateY(-2px); }
}

@media (prefers-reduced-motion: reduce) {
  .preview-bug-glyph { animation: none; }
}

.preview-bug-line {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.preview-bug-sub {
  font-size: 11.5px;
  color: var(--dim);
}

/* Kept: the runtime-log path still renders raw text through it. */
.preview-error {
  color: var(--danger, #ff6b6b);
  font: 12px/1.5 ui-monospace, monospace;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 0;
}

/* The pane when a preview fails. Deliberately NOT danger-red and not
   monospace: this is the agent talking about its own slip, not the
   engine reporting. The error text itself lives in the console — see
   bugvoice.js for why this surface never carries a second copy. */
.preview-bug {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 120px;
  padding: 24px 16px;
  text-align: center;
}

.preview-bug-glyph {
  font-size: 30px;
  line-height: 1;
}

.preview-bug-line {
  color: var(--text, #e8ecf1);
  font: 500 14px/1.4 system-ui, sans-serif;
  text-wrap: balance;
}

.preview-bug-sub {
  color: var(--dim, #8b93a1);
  font: 12px/1.4 system-ui, sans-serif;
}

/* ── Help sheet ─────────────────────────────────────────────────── */

.help-scrim {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgb(0 0 0 / 45%);
}

.help-sheet {
  display: flex;
  flex-direction: column;
  width: min(720px, 92vw);
  max-height: 82vh;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 20px 60px rgb(0 0 0 / 55%);
  overflow: hidden;
}

.help-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}

.help-head .spacer { flex: 1; }
.help-title { font-weight: 700; letter-spacing: 0.3px; }

.help-body {
  padding: 16px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.help-body h3 {
  margin: 0 0 8px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}

.help-body p { margin: 0 0 8px; font-size: 13px; }

.help-grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 4px 16px;
  font-size: 13px;
}

/* Templates sheet — one card per starter template (shares the help
   sheet's scrim/head/body chrome). */
.template-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
}
.template-card .help-actions { margin-top: 4px; }

.help-filter {
  width: 100%;
  height: 30px;
  margin-bottom: 10px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--bg);
  color: var(--text);
  font: var(--code-font);
  font-size: 12px;
}

.help-filter:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 60%, var(--border));
}

.help-prims {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
}

.help-prim {
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--panel-2);
  color: var(--syn-verb);
  font-size: 11px;
}

.help-body .mono { font: var(--code-font); font-size: 12px; }
.help-body .dim { color: var(--dim); }

/* ── Picker modal (Open) ────────────────────────────────────────── */

.picker-scrim {
  position: fixed;
  inset: 0;
  z-index: 55;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 12vh;
  background: rgb(0 0 0 / 45%);
}

.picker {
  display: flex;
  flex-direction: column;
  width: min(520px, 92vw);
  max-height: 60vh;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 20px 60px rgb(0 0 0 / 55%);
  overflow: hidden;
}

.picker-head {
  padding: 12px 16px;
  font-weight: 700;
  border-bottom: 1px solid var(--border);
}

.picker-filter {
  margin: 10px 12px 6px;
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--bg);
  color: var(--text);
  font: var(--code-font);
  font-size: 13px;
}

.picker-filter:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 60%, var(--border));
}

.picker-list {
  list-style: none;
  margin: 0;
  padding: 4px 8px 10px;
  overflow-y: auto;
}

.picker-item {
  padding: 7px 10px;
  border-radius: 6px;
  font: var(--code-font);
  font-size: 13px;
  cursor: pointer;
}

.picker-item.is-sel,
.picker-item:hover {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--text);
}

.picker-empty {
  padding: 12px 10px;
  color: var(--dim);
  font-size: 13px;
}

/* Connection-failure chips: the "meanwhile" note sits inline between
   the retry prompt and the self-serve action chips. */
.chip-note {
  align-self: center;
  font-size: 12px;
  margin: 0 4px;
}

/* ── The Code Optimizer's face and speech ────────────────────────────────
   Raina, 2026-08-25: "we are making this for people who cannot code at all
   ... show a cute geek girl making a suggestion." She replaces a bulleted
   list of critic jargon with one question a non-programmer can answer.

   COLOURS COME FROM THE PALETTE, never literals, so she reads correctly in
   whatever theme is live — a face is exactly the kind of thing that gets
   hard-coded and then glows in dark mode. */
.chat-row.is-optimizer .chat-body {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}

.chat-opt-face { flex: 0 0 40px; line-height: 0; margin-top: 1px; }
.chat-opt-face svg { display: block; }

/* SKIN MUST BE LIGHT, not a tint of the panel. The first pass mixed --warn
   into --panel and she came out olive: the glasses read, the face did not. */
.opt-face-bg    { fill: color-mix(in srgb, var(--accent) 12%, var(--panel)); }
.opt-face-skin  { fill: color-mix(in srgb, var(--warn) 42%, var(--text)); }
.opt-face-hair  { fill: color-mix(in srgb, var(--info) 78%, var(--bg)); }
.opt-face-eye   { fill: color-mix(in srgb, var(--bg) 80%, var(--text)); }
.opt-face-blush { fill: color-mix(in srgb, var(--danger) 42%, transparent); }
/* Frames and smile are dark ON THE LIGHT FACE, so they take their colour from
   the background rather than the text — the contrast that matters here is
   against skin, not against the page. */
.opt-face-frame, .opt-face-smile {
  fill: none;
  stroke: color-mix(in srgb, var(--bg) 82%, var(--text));
  stroke-linecap: round;
}
.opt-face-frame { stroke-width: 1.6; }
/* 1.9, not 1.5: at 40px the mouth was the first thing to disappear and it is
   her only expressive feature (Sonar, rendered side by side). */
.opt-face-smile { stroke-width: 1.9; }

.chat-opt-speech { min-width: 0; flex: 1 1 auto; }
.chat-opt-speech > * + * { margin-top: 6px; }

.chat-opt-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* The engineer's view, folded away. Present for anyone who wants it and in
   nobody's way if they don't — the detail is not hidden, it is second. */
.chat-opt-detail > summary {
  cursor: pointer;
  color: var(--dim);
  font-size: 12px;
  user-select: none;
}
.chat-opt-detail > summary:hover { color: var(--text); }
.chat-opt-detail[open] > summary { margin-bottom: 6px; }

/* A declined row must still be READABLE — dimming it to near-invisible would
   hide the disclosures (unreviewed, checked-my-own-work) from the one person
   who just decided they did not trust it. */
.chat-row.is-declined .chat-opt-face { opacity: 0.55; }

/* Her face IS this row's glyph, so the 16px glyph gutter would be an empty
   column pushing her off the others' alignment. */
.chat-row.is-optimizer { grid-template-columns: 1fr; }
.chat-row.is-optimizer .chat-glyph { display: none; }

/* ── The plan, waiting ───────────────────────────────────────────────────
   Raina, 2026-08-25: "a person can chat in english about what they want to
   make before they make it." The steps are the only artefact here a
   non-programmer can read and judge before any code exists, so they get
   room rather than being squeezed into a note. */
.chat-row.is-plan { grid-template-columns: 1fr; }
.chat-row.is-plan .chat-glyph { display: none; }
.chat-plan > * + * { margin-top: 6px; }

.chat-plan-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.chat-plan-list li {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 8px;
  align-items: baseline;
  line-height: 1.5;
}
.chat-plan-n {
  color: var(--dim);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
