/* ============================================================================
   THE MARVIN CONSOLE — design system (Epic 9, Story 9.1 foundation)
   A calm, precise instrument panel for a thinking being. Dark-first, neutral ink,
   signal-lime. Serif display (the "person") against monospace data (the
   "machine"). Theme-aware. CSP-safe: system font stacks, no external assets.
   9.2-9.4 extend these tokens + components; do not fork them.
   Story 73.1 (2026-09-04): repalletted to the Marvin Design & Style Rulebook v1. The token NAMES
   stopped being colour words in the same pass — `--amber` meaning "the brand colour" survived one
   rename already (it has never been amber on light: it was #9c6410, a brown) and the name is what
   let a text token and a fill token stay welded together for four releases.
   ============================================================================ */

:root {
  /* type — the signature pairing */
  --font-display: "Iowan Old Style", "Palatino Linotype", "Book Antiqua",
    Palatino, "URW Palladio L", Georgia, "Times New Roman", serif;
  --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code",
    "Roboto Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace;

  /* dark "cockpit at night" — the default. Values are the rulebook's own (§04 "Web migration"),
     not re-derived: its table was written against marvin.css v1.54.0 and its per-token counts still
     match this file — 10 of 12 exact, --panel -1 and --line -2 after four releases of drift. */
  --ink: #0A0A0B;
  --panel: #141417;
  --panel-2: #1D1D21;
  --line: #2A2A30;
  --line-soft: #1F1F24;
  --text: #FCFCFC;
  --muted: #9A9AA2;
  --dim: #6A6A72;

  /* 🔴 THE ACCENT SPLITS IN THREE, AND THE SPLIT IS WHAT MAKES LIME SAFE.
     `--amber` carried eight roles at once: text, fills, borders and `accent-color`. Amber was
     legible on BOTH themes, so one token doing two jobs never showed. Lime is not — measured on
     this file's own light grounds, #8CF50E as text is 1.38:1 on --panel and 1.24:1 on --ink, at or
     below the 1.38:1 the rulebook quotes when it bans it. TEXT gets a theme-dependent accent (dark,
     Lime Ink on light) while a FILL keeps true lime in both themes with dark ink on top, because
     a fill is not text and Rule 1 is about text. */
  --accent: #8CF50E;          /* text, borders, rules — theme-dependent; see the light block */
  --accent-fill: #8CF50E;     /* filled buttons, active tabs, selected chips — BOTH themes */
  --on-accent: #0A0A0B;       /* what sits ON a fill — 14.31:1 */
  --accent-dim: #5F9E08;
  --accent-glow: rgba(140, 245, 14, 0.16);
  /* 🔴 --warn IS NEW, AND IT IS THE POINT OF THIS STORY.
     `--amber` was the brand colour AND the warn colour simultaneously — five triads put it between
     --teal (ok) and --red (err): .pulse, .verdict, .op-row .tag, .wizard-key-chip, .stat-tile .val.
     Renaming --amber to --accent without splitting warn out would have made LIME mean "warning"
     while sitting beside teal meaning "ok" — Rule 2, precisely ("lime cannot also carry a status
     meaning"), and a warning painted the same colour as every button and every active nav item is
     not a warning at all. #F5C518 is the rulebook's own warn value. */
  --teal: #4FD1B0;    /* rulebook "good"  — name kept, see D-73-COLOURNAMES */
  --warn: #F5C518;    /* rulebook "warn" */
  --red: #FF6B5E;     /* rulebook "bad"   — name kept, see D-73-COLOURNAMES */
  --on-red: #0A0A0B;  /* 7.08:1 on the dark --red; #fff would be 2.79:1 — see the light block */
  /* Story 20.4 + 73.1: NOT decoration. This marks a finding Marvin wrote about HIMSELF — the
     system talking about itself rather than about the owner — a meaning orthogonal to both status
     and brand. Renamed off a colour word so it cannot be quietly flattened into the palette the
     way `--amber` was silently holding two jobs. */
  --selfobs: #C9A9FF;
  --selfobs-dim: #8f74bd;
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.02), 0 18px 40px -24px rgba(0, 0, 0, 0.8);

  --rail-w: 232px;
  --bar-h: 52px;
  --radius: 3px;
  --tap: 0.14s cubic-bezier(0.2, 0, 0, 1);
}

:root[data-theme="light"] {
  /* light "daylight almanac" — neutral paper */
  --ink: #F2F2F4;
  --panel: #FFFFFF;
  --panel-2: #F5F5F7;
  --line: #D9D9DF;
  --line-soft: #E8E8EC;
  --text: #141417;
  --muted: #55555E;
  --dim: #76767F;
  /* 🔴 RULE 1 LIVES HERE. Lime as TEXT on these grounds measures 1.38:1 / 1.24:1 — unreadable.
     Lime Ink is 6.10:1 on --panel, 5.60:1 on --panel-2 and 5.46:1 on --ink, so it clears AA for
     body text on all three rather than only the flattering one. The FILL
     stays true lime with dark ink on it. Note the OLD value here was #9c6410, a brown: `--amber`
     has never actually been amber on this theme, which is exactly why the name had to go. */
  --accent: #3F6E00;          /* Lime Ink — text, borders, rules on light */
  --accent-fill: #8CF50E;
  --on-accent: #0A0A0B;
  /* 🔴 EVERY SEMANTIC HUE NEEDS ITS OWN LIGHT VALUE, and that is a finding about the RULEBOOK, not
     about this file: its palette is authored for a near-black ground and says nothing about a light
     theme, which the console has had since 9.1. Straight-lifted, its own values measure #F5C518 at
     1.63:1 and #5F9E08 at 3.29:1 as text on white — both unreadable. Each value below is the
     LIGHTEST scaling of the rulebook's hue that still clears 4.5:1 against #F2F2F4, the DARKEST of
     the three light grounds text sits on, so all three pass rather than just the flattering one. */
  --accent-dim: #4A7B06;   /* 4.55:1 — the dark value 3.29:1, and it is used as TEXT in 3 rules */
  --accent-glow: rgba(63, 110, 0, 0.12);
  --teal: #2F7B68;         /* 4.52:1 */
  --warn: #846A0D;         /* 4.64:1 */
  --red: #B54C43;          /* 4.60:1 */
  --on-red: #FFFFFF;       /* 5.15:1 here, where #0A0A0B would be 3.84:1 — the exact inverse of
                              the dark theme, which is why `color: #fff` hardcoded in three danger
                              rules was wrong on one theme no matter which theme you looked at. */
  --selfobs: #6d4fa8;
  --selfobs-dim: #8a6dc0;
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.5), 0 16px 34px -26px rgba(20, 20, 23, 0.5);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    /* Story 73.1: these MUST track the [data-theme="light"] block above. A swap that fixed one and
       not the other would leave every reader on system-light with a half-migrated palette — and
       Rule 1 bites hardest exactly there. Test-enforced, not comment-enforced. */
    --ink: #F2F2F4; --panel: #FFFFFF; --panel-2: #F5F5F7; --line: #D9D9DF;
    --line-soft: #E8E8EC; --text: #141417; --muted: #55555E; --dim: #76767F;
    --accent: #3F6E00; --accent-fill: #8CF50E; --on-accent: #0A0A0B;
    --accent-dim: #4A7B06; --accent-glow: rgba(63,110,0,0.12);
    --teal: #2F7B68; --warn: #846A0D; --red: #B54C43; --on-red: #FFFFFF;
    --selfobs: #6d4fa8; --selfobs-dim: #8a6dc0;
    --shadow: 0 1px 0 rgba(255,255,255,0.5), 0 16px 34px -26px rgba(20,20,23,0.5);
  }
}

* { box-sizing: border-box; }
/* Story 18.9: the `hidden` attribute MUST win over component display rules (e.g. `.pending { display: flex }`),
   otherwise JS toggling the `hidden` attribute has no visual effect — the "thinking…" pending strip was never
   actually cleared after a turn. An author-stylesheet reset (with !important) fixes this for every hidden-attr
   element in one rule. Elements that toggle via a `.hidden` CLASS (.gate, .restart-overlay) are unaffected. */
[hidden] { display: none !important; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background-color: var(--ink);
  /* atmosphere: a faint amber vignette + a hairline dot-grid (the instrument texture) */
  background-image:
    radial-gradient(120% 80% at 82% -10%, var(--accent-glow), transparent 60%),
    radial-gradient(var(--line-soft) 0.6px, transparent 0.6px);
  background-size: auto, 22px 22px;
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* grain overlay — subtle machined texture */
body::after {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 999;
  opacity: 0.025; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- primitives ---------------------------------------------------------- */
.mono-label {
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--muted); font-weight: 500;
}
.serif { font-family: var(--font-display); }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* the live "heartbeat" — the thinking machine's pulse */
.pulse {
  width: 8px; height: 8px; border-radius: 50%; background: var(--dim);
  box-shadow: 0 0 0 0 transparent; flex: none;
}
/* 🔴 EACH STATE HALOES IN ITS OWN COLOUR. The `beat` keyframe used to hardcode the BRAND glow,
   so a healthy `ok` dot pulsed with a lime halo and so did a warning — Rule 2 surviving in the
   animation layer, one level below where a selector-based check looks for it. The halo now comes
   from a per-state custom property the keyframe reads off the element. */
.pulse.ok { background: var(--teal); --pulse-glow: color-mix(in srgb, var(--teal) 30%, transparent);
  animation: beat 2.6s ease-in-out infinite; }
.pulse.warn { background: var(--warn); --pulse-glow: color-mix(in srgb, var(--warn) 30%, transparent);
  animation: beat 1.4s ease-in-out infinite; }
.pulse.err { background: var(--red); --pulse-glow: color-mix(in srgb, var(--red) 30%, transparent); }
@keyframes beat {
  0%, 100% { box-shadow: 0 0 0 0 var(--pulse-glow); }
  50% { box-shadow: 0 0 0 5px transparent; }
}
@media (prefers-reduced-motion: reduce) { .pulse { animation: none !important; } }

/* ---- app shell ----------------------------------------------------------- */
.app { display: grid; grid-template-columns: var(--rail-w) 1fr;
  grid-template-rows: var(--bar-h) 1fr; height: 100vh; }

.topbar {
  grid-column: 1 / -1; display: flex; align-items: center; gap: 18px;
  padding: 0 20px; border-bottom: 1px solid var(--line);
  background: linear-gradient(var(--panel), var(--ink));
}
.wordmark { font-family: var(--font-display); font-size: 20px; letter-spacing: 0.01em;
  color: var(--text); font-weight: 600; }
.wordmark b { color: var(--accent); font-weight: 600; }
.topbar .spacer { flex: 1; }
.status-chip { display: flex; align-items: center; gap: 8px; }
.status-chip .txt { font-size: 11px; color: var(--muted); letter-spacing: 0.04em; }

.rail {
  border-right: 1px solid var(--line); background: var(--panel);
  padding: 18px 12px; display: flex; flex-direction: column; gap: 2px; overflow-y: auto;
}
.rail .group { margin: 14px 8px 6px; }
.nav {
  display: flex; align-items: center; gap: 11px; padding: 9px 12px;
  border-radius: var(--radius); color: var(--muted); cursor: pointer;
  border: 1px solid transparent; transition: background var(--tap), color var(--tap);
  font-size: 13px; letter-spacing: 0.02em; user-select: none;
}
.nav:hover { background: var(--panel-2); color: var(--text); }
.nav.active { background: var(--panel-2); color: var(--text); border-color: var(--line);
  box-shadow: inset 2px 0 0 var(--accent); }
.nav .k { font-family: var(--font-mono); font-size: 15px; width: 18px; text-align: center;
  color: var(--accent-dim); }
.nav.active .k { color: var(--accent); }
.nav .soon { margin-left: auto; font-size: 9px; letter-spacing: 0.12em;
  color: var(--dim); text-transform: uppercase; }

.main { overflow-y: auto; padding: 30px 34px 48px; }
.main-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 4px; }
.main-head h1 { font-family: var(--font-display); font-weight: 600; font-size: 27px;
  margin: 0; letter-spacing: 0.005em; }
.main-head .sub { color: var(--muted); font-size: 12px; }
.lede { color: var(--muted); max-width: 62ch; margin: 10px 0 26px;
  font-size: 13.5px; line-height: 1.6; }

/* ---- panels + rows ------------------------------------------------------- */
.panel {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); overflow: hidden;
}
.panel + .panel { margin-top: 18px; }
.panel-head { display: flex; align-items: center; gap: 10px; padding: 13px 16px;
  border-bottom: 1px solid var(--line-soft); background: var(--panel-2); }
.panel-head .mono-label { color: var(--muted); }
.panel-body { padding: 6px 16px 14px; }

/* Story 24.2: the read-only "who does what" model-routing table (grouped by vendor). */
.routing-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 13px; }
.routing-table th, .routing-table td {
  text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
}
.routing-table thead th { color: var(--muted); font-weight: 600; font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.04em; }
.routing-table th.routing-vendor {
  background: var(--panel-2); color: var(--text); font-weight: 700; font-size: 12px;
  padding-top: 10px;
}
.routing-table td.routing-role { font-weight: 600; white-space: nowrap; }
.routing-table td.routing-purpose { color: var(--muted); }
/* P5 Edge F2: a custom/experimental model id can be long — wrap it within the cell rather than
   clip it (the panel is overflow:hidden), so no id is ever truncated out of view. */
.routing-table td.routing-model { word-break: break-word; }
/* Story 24.4: the per-role model picker (select + reset + state) inside the model cell. */
.routing-pick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.routing-select { max-width: 260px; font-size: 12px; padding: 3px 6px; }
.routing-reset { font-size: 11px; padding: 2px 8px; }
.routing-state { font-size: 11px; color: var(--muted); }
.routing-state.overridden { color: var(--warn); font-weight: 600; }
/* Story 24.5: the smoke-validate verdict badge (✓/✗/⚠) shown when a model is picked. */
.routing-apply-anyway { font-size: 11px; padding: 2px 8px; }
.routing-validity { font-size: 11px; font-weight: 600; }
.routing-validity.pending { color: var(--muted); font-weight: 400; }
.routing-validity.ok { color: var(--teal); }
.routing-validity.warn { color: var(--warn); }
.routing-validity.fail { color: var(--red); }
.routing-validity[hidden], .routing-apply-anyway[hidden] { display: none; }
/* Story 24.6: the unobtrusive "newer available" stay-current badge (honest — "available", not
   "recommended") + its Adopt affordance, which reuses the 24.5 validate gate. */
.routing-newer { font-size: 11px; color: var(--muted); font-style: italic; max-width: 260px; overflow-wrap: anywhere; word-break: break-word; }

/* Story 24.3: the read-only "available models per provider" view (grouped by vendor + freshness). */
.models-bar { margin: 8px 0 4px; }
.models-list { display: flex; flex-direction: column; gap: 12px; margin-top: 8px; }
.models-vendor { border: 1px solid var(--line-soft); border-radius: 8px; padding: 10px 12px; }
.models-vendor-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.models-vendor-name { font-weight: 700; font-size: 13px; color: var(--text); }
.models-count { color: var(--muted); font-size: 12px; }
.models-source { margin-left: auto; color: var(--muted); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.04em; }
.models-empty { font-size: 12px; }
.models-ids { display: flex; flex-wrap: wrap; gap: 6px; }
.model-chip {
  font-family: var(--font-mono); font-size: 11px;
  padding: 2px 7px; border-radius: 5px; background: var(--panel-2);
  border: 1px solid var(--line-soft); color: var(--muted); word-break: break-all;
}
.model-chip.chat { color: var(--text); border-color: var(--accent, var(--line)); }

.row {
  display: flex; align-items: center; gap: 14px; padding: 12px 0;
  border-bottom: 1px solid var(--line-soft);
}
.row:last-child { border-bottom: 0; }
.row .name { font-size: 13px; min-width: 130px; color: var(--text); }
.row .probe { font-family: var(--font-mono); font-size: 11.5px; color: var(--muted);
  flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .verdict { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em;
  text-transform: uppercase; }
.verdict.ok { color: var(--teal); } .verdict.warn { color: var(--warn); }
.verdict.err { color: var(--red); } .verdict.pending { color: var(--dim); }

/* ---- controls ------------------------------------------------------------ */
.btn {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--on-accent); background: var(--accent-fill);
  border: 1px solid var(--accent-fill); border-radius: var(--radius);
  padding: 9px 16px; cursor: pointer; transition: filter var(--tap), transform var(--tap);
}
.btn:hover { filter: brightness(1.08); }
.btn:active { transform: translateY(1px); }
.btn.ghost { background: transparent; color: var(--text); border-color: var(--line); }
.btn.ghost:hover { border-color: var(--muted); filter: none; }
.input {
  font-family: var(--font-mono); font-size: 13px; color: var(--text);
  background: var(--ink); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 12px; width: 100%; transition: border-color var(--tap), box-shadow var(--tap);
}
.input:focus { outline: none; border-color: var(--accent-dim);
  box-shadow: 0 0 0 3px var(--accent-glow); }
.input::placeholder { color: var(--dim); }

/* ---- token gate (Fork B: token-paste) ------------------------------------ */
.gate { position: fixed; inset: 0; z-index: 500; display: grid; place-items: center;
  background: color-mix(in srgb, var(--ink) 88%, transparent); backdrop-filter: blur(4px); }
.gate.hidden { display: none; }
.gate .card { width: min(460px, 92vw); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 30px 30px 26px; }
.gate h2 { font-family: var(--font-display); font-weight: 600; margin: 6px 0 4px; font-size: 22px; }
.gate p { color: var(--muted); font-size: 12.5px; line-height: 1.6; margin: 0 0 18px; }
.gate .field { display: flex; gap: 8px; margin-top: 4px; }
.gate .err { color: var(--red); font-size: 11.5px; margin-top: 10px; min-height: 14px;
  font-family: var(--font-mono); }
.gate .note { color: var(--dim); font-size: 10.5px; margin-top: 16px; line-height: 1.6; }

/* ---- boot log (the console reveal) --------------------------------------- */
.bootlog { font-family: var(--font-mono); font-size: 11.5px; color: var(--dim);
  margin: 0 0 22px; white-space: pre-wrap; min-height: 1.2em; }
.bootlog .l { display: block; opacity: 0; animation: fadein 0.4s forwards; }
/* staggered reveal via nth-child (NOT inline JS styles → strict CSP, no 'unsafe-inline') */
.bootlog .l:nth-child(1) { animation-delay: 0s; }
.bootlog .l:nth-child(2) { animation-delay: 0.09s; }
.bootlog .l:nth-child(3) { animation-delay: 0.18s; }
.bootlog .l:nth-child(4) { animation-delay: 0.27s; }
.bootlog .l:nth-child(5) { animation-delay: 0.36s; }
.bootlog .l:nth-child(6) { animation-delay: 0.45s; }
@keyframes fadein { to { opacity: 1; } }

/* utility classes (replace inline style attrs → strict style-src 'self') */
.self-center { align-self: center; }
.btn-sm { padding: 6px 12px; }

.theme-toggle { cursor: pointer; color: var(--muted); background: none; border: 0;
  font-family: var(--font-mono); font-size: 15px; padding: 4px 6px; line-height: 1; }
.theme-toggle:hover { color: var(--accent); }

/* ---- chat surface (Story 9.2) -------------------------------------------- */
.chat-wrap { display: grid; grid-template-columns: 210px 1fr; gap: 20px;
  height: calc(100vh - var(--bar-h) - 92px); }
.sessions { border-right: 1px solid var(--line-soft); padding-right: 16px; overflow-y: auto; }
/* MPDA-14: three buttons are wider than the 210 px column, and the column scrolls, so the last
   one ("Reach out now") was cut off. The label takes its own line and the buttons wrap. */
.sessions-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-bottom: 10px; }
.sessions-head .mono-label { flex: 1 0 100%; }
.session-list { display: flex; flex-direction: column; gap: 3px; }
.session-item { padding: 9px 11px; border: 1px solid transparent; border-radius: var(--radius);
  cursor: pointer; transition: background var(--tap), border-color var(--tap); }
.session-item:hover { background: var(--panel-2); }
/* Story 63.1: the per-session delete control. `position: relative` on the row anchors it so the
   button sits in the row's corner rather than in the flow — adding it cannot reflow the
   title/summary/meta stack Story 22.2 tuned. REVEALED ON HOVER AND FOCUS: a destructive,
   irreversible control should not sit permanently under the pointer on every row, but hover-only
   would make it unreachable without a mouse, so :focus-within and :focus surface it too. */
.session-item { position: relative; }
.session-del { position: absolute; top: 6px; right: 6px; opacity: 0;
  background: none; border: 1px solid transparent; border-radius: var(--radius);
  color: var(--muted); font-size: 15px; line-height: 1; padding: 2px 6px; cursor: pointer;
  transition: opacity .12s ease, color .12s ease, border-color .12s ease; }
.session-item:hover .session-del, .session-item:focus-within .session-del,
.session-del:focus { opacity: 1; }
.session-del:hover, .session-del:focus { color: var(--red); border-color: var(--red); }
.session-item.active { background: var(--panel-2); border-color: var(--line);
  box-shadow: inset 2px 0 0 var(--accent); }
.session-item .sid { font-family: var(--font-mono); font-size: 11.5px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Story 22.2: the topic title (prominent) + the summary subtitle. The id fallback re-uses the
   mono face via `.title.mono` so an un-titled session still reads as a store id. */
.session-item .title { font-size: 12.5px; color: var(--text); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.session-item .title.mono { font-family: var(--font-mono); font-size: 11.5px; font-weight: 400; }
.session-item .summary { font-size: 11px; color: var(--dim); margin-top: 2px; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.session-item .meta { font-size: 10px; color: var(--dim); margin-top: 3px; letter-spacing: 0.04em; }
/* Story 80.8: a Marvin-INITIATED chat is set apart from a user chat — an accent left-edge on the
   row and a small accent badge leading it ("✦ Marvin reached out"), so the operator can tell at a
   glance which conversations Marvin started on his own. */
.session-item.marvin-initiated { border-left: 2px solid var(--accent); padding-left: 9px; }
.session-item .session-badge { font-size: 10px; font-weight: 600; color: var(--accent);
   letter-spacing: 0.03em; margin-bottom: 3px; }
.session-list .empty { color: var(--dim); font-size: 11px; padding: 8px 4px; }

.chat-col { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
/* Story 19.1: min-height:0 lets the flex/grid chain bound the transcript so overflow-y:auto scrolls
   INTERNALLY (else it grows to full content, the page overflows, and marvin.js's scrollTop=scrollHeight
   is a no-op → long chats don't auto-scroll to the newest message). */
.transcript { flex: 1; min-height: 0; overflow-y: auto; padding: 4px 4px 12px; display: flex;
  flex-direction: column; gap: 14px; }
.empty-note { color: var(--muted); font-size: 15px; margin: auto; max-width: 44ch;
  text-align: center; line-height: 1.6; }
.msg { max-width: 82%; padding: 11px 15px; border-radius: 9px; font-size: 13.5px;
  line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; }
.msg.user { align-self: flex-end; background: var(--accent-fill); color: var(--on-accent);
  border-bottom-right-radius: 2px; font-family: var(--font-mono); font-size: 13px; }
.msg.marvin { align-self: flex-start; background: var(--panel); border: 1px solid var(--line);
  color: var(--text); border-bottom-left-radius: 2px; font-family: var(--font-display);
  font-size: 15px; }
.msg .who { display: block; font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--dim); margin-bottom: 5px; }
/* 🔴 P5 EDGE CASE, ARMED BUT NOT YET LIVE. The user bubble is a LIME FILL, and the global rule
   `a { color: var(--accent) }` is lime too — an anchor inside this bubble computes 1.00:1 on dark
   and 4.41:1 on light. It is unreachable TODAY: message bodies are built with `textContent` via
   el(), never innerHTML, so a URL a user types renders as plain text. This rule exists so that
   whoever adds URL auto-linking does not ship an invisible link. Anything sitting on the fill
   takes the fill's ink. */
.msg.user a { color: var(--on-accent); text-decoration: underline; }
.msg.user .who { color: color-mix(in srgb, var(--on-accent) 74%, transparent); }
/* Story 22.3: the optional compact per-message time (+ response duration on replies) — small,
   muted, mono, consistent with .who. Only present when the chat_show_timestamps toggle is ON. */
.msg .msg-meta { display: block; font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: 0.08em; color: var(--dim); margin-top: 5px; }
.msg.user .msg-meta { color: color-mix(in srgb, var(--on-accent) 74%, transparent); }
/* Story 49.3: the neutral "⏳ awaiting approval" note (CLI-free; the Approve/Deny buttons are the
   actionable control on a live turn, this line keeps the signal on history). */
.msg .pending-note { display: block; margin-top: 6px; font-size: 12px; color: var(--muted); }

.findings-banner { border: 1px solid var(--accent-dim); background: var(--accent-glow);
  border-radius: var(--radius); padding: 9px 13px; margin-bottom: 12px; font-size: 12px;
  color: var(--text); display: flex; align-items: center; gap: 10px;
  overflow-wrap: anywhere; word-break: break-word; }
.findings-banner .tag { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--accent); }

.pending { display: flex; align-items: center; gap: 10px; padding: 8px 2px; }
/* Story 63.3: the composer is a COLUMN — a tool row above, the message row below. It used to be a
   single flex row holding the textarea AND eight controls, so each epic that added a button took
   width from the input until the operator was typing into a sliver. */
.composer { display: flex; flex-direction: column; gap: 8px; padding-top: 12px;
  border-top: 1px solid var(--line-soft); }
/* WRAPS rather than overflowing: at narrow widths the tools take a second line and the message box
   keeps its full width, which is the whole point of the change. `align-items: center` so the
   temporary-chat LABEL (a checkbox in a <label>, not a button) sits on the button baseline. */
.composer-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-height: 0; }
/* Only SEND shares the input's row. `flex: 1` on the textarea is what actually gives the operator
   the width back. */
.composer-entry { display: flex; gap: 10px; align-items: flex-end; }
.composer-entry .input { flex: 1; min-width: 0; }
.composer .input { resize: none; max-height: 160px; font-family: var(--font-mono); }
/* Kept: buttons must not stretch, in EITHER row now. */
.composer .btn { flex: none; }
/* Epic 49 (operator ask): the Stop-speaking control — red-tinted so it reads as an interrupt while
   Marvin is talking; only shown (JS unhides it) for the duration of an utterance. */
/* 🔴 Epic 73 P6.5 — `background: transparent` IS THE FIX, and it was missing all along.
   These set only `color` and inherited `.btn`'s FILLED background, so the text sat on the accent
   fill: MEASURED IN A REAL BROWSER at 2.02:1 (dark) for all three of Live Air, Auto-read and Stop.
   Pre-existing — before 73.1 the fill was amber and these were just as unreadable — but lime is
   brighter and made it unmissable. Their evident design is a TINTED OUTLINE control, which is what
   `.btn.ghost` already is; they simply never opted out of the fill. */
.stop-speak { background: transparent; color: var(--red); border-color: var(--red); }
.stop-speak:hover { background: var(--red); color: var(--on-red); }
/* Story 50.1: the Auto-read toggle. ON is stated by COLOUR AND by aria-pressed + the label text —
   Epic 15's P5 caught a colour inversion, and a control whose only "on" signal is a tint is a
   control an operator misreads. Disabled carries its reason in the title attribute, never silence. */
.voice-toggle { flex: none; background: transparent; color: var(--muted); }
.voice-toggle[aria-pressed="true"] { color: var(--teal); border-color: var(--teal); }
.voice-toggle:disabled { opacity: 0.45; cursor: not-allowed; }
/* Story 100-21 (AC2): a voice control this Marvin cannot serve yet. Dimmed like a disabled one, but
   still clickable — the click opens the reason and "Switch voice on" (#voice-off-panel). */
.voice-unavailable { opacity: 0.45; cursor: help; }
.voice-off-panel { margin-top: 8px; padding: 10px 12px; border: 1px solid var(--line-soft);
  border-radius: 8px; background: var(--panel); }
.voice-off-panel[hidden] { display: none; }
.voice-off-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.temp-toggle { flex: none; display: flex; align-items: center; gap: 5px; cursor: pointer;
  color: var(--muted); user-select: none; white-space: nowrap; }
.temp-toggle input { accent-color: var(--accent-fill); cursor: pointer; }
.chat-foot { min-height: 14px; margin-top: 8px; color: var(--dim); }
.chat-foot .err { color: var(--red); }
/* Story 22.4: the staged-image attach preview under the composer. */
#attach-wrap { flex: none; }
#attach-btn { font-size: 16px; line-height: 1; padding: 6px 10px; }
.upload-preview { display: flex; align-items: center; gap: 8px; margin-top: 8px; color: var(--muted); }
.upload-preview[hidden] { display: none; }
.upload-thumb { width: 44px; height: 44px; object-fit: cover; border-radius: 6px;
  border: 1px solid var(--line-soft); }
.upload-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 60%; }
.upload-remove { flex: none; padding: 2px 9px; line-height: 1; }

/* ---- operate surfaces (Story 9.3) ---------------------------------------- */
.op-toolbar { display: flex; gap: 9px; margin: 4px 0 16px; align-items: center; flex-wrap: wrap; }
.op-toolbar .input { flex: 1; min-width: 140px; }
.input.sel { flex: none; width: auto; min-width: 130px; }
.op-list { display: flex; flex-direction: column; gap: 1px; }
.op-row { display: flex; align-items: center; gap: 14px; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--line-soft); border-radius: var(--radius);
  transition: border-color var(--tap); }
.op-row:hover { border-color: var(--line); }
.op-row .rid { font-family: var(--font-mono); font-size: 12px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.op-row .rmeta { font-size: 10.5px; color: var(--dim); letter-spacing: 0.03em; }
.op-row .grow { flex: 1; min-width: 0; }
.op-row .tag { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.12em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 2px; border: 1px solid var(--line);
  color: var(--muted); }
.op-row .tag.accent { color: var(--accent); border-color: var(--accent-dim); }
.op-row .tag.red { color: var(--red); border-color: var(--red); }
.op-row .tag.teal { color: var(--teal); border-color: var(--teal); }
/* Story 20.4: the self-observation source badge — a maintenance / reasoning-quality row reads as
   Marvin's own operational finding, distinct from ordinary findings. Theme-aware via --selfobs. */
.op-row .tag.selfobs { color: var(--selfobs); border-color: var(--selfobs-dim); }
.op-actions { display: flex; gap: 6px; flex: none; }
.op-actions .btn { padding: 6px 11px; font-size: 10.5px; }
.op-list .empty { color: var(--dim); font-size: 12px; padding: 14px 6px; font-style: italic; }
.op-detail { margin-top: 16px; }
.op-detail .md { font-family: var(--font-display); font-size: 15px; line-height: 1.65;
  white-space: pre-wrap; color: var(--text); }
.op-detail .obs { font-family: var(--font-mono); font-size: 12px; color: var(--muted);
  padding: 5px 0; border-bottom: 1px solid var(--line-soft); }

/* Story 26.3: legible finding rows (title + urgency + summary + affects) that expand INLINE. */
.finding-item { display: flex; flex-direction: column; }
.finding-item .op-row { align-items: flex-start; cursor: pointer; }   /* multi-line → top-align */
.finding-item.unread .op-row { border-left: 2px solid var(--accent); } /* unread cue on the row */
.op-row .ftitle-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.op-row .ftitle { font-family: var(--font-display); font-size: 14px; color: var(--text);
  font-weight: 600; }
.finding-item:not(.unread) .op-row .ftitle { font-weight: 500; color: var(--muted); } /* read=quieter */
.op-row .fsummary { font-size: 12.5px; color: var(--muted); margin-top: 3px; line-height: 1.5; }
/* urgency badge — only HIGH draws the eye; MEDIUM (the default) stays neutral so it isn't noise. */
.op-row .urg { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.1em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 2px; border: 1px solid var(--line);
  color: var(--muted); }
.op-row .urg-high { color: var(--red); border-color: var(--red); }
.op-row .urg-medium { color: var(--muted); border-color: var(--line); }
.op-row .urg-low { color: var(--dim); border-color: var(--line-soft); }
.op-row .faffects { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.op-row .faff-label { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--dim); }
.op-row .chip { font-family: var(--font-mono); font-size: 10px; padding: 2px 7px; border-radius: 10px;
  background: var(--line-soft); color: var(--muted); }
.finding-detail { margin: 2px 0 8px; padding: 12px 14px; background: var(--panel);
  border: 1px solid var(--line-soft); border-top: none;
  border-radius: 0 0 var(--radius) var(--radius); }
.finding-detail .finding-ctl { margin-top: 12px; }
.rate-pill { cursor: pointer; }
.rate-pill.on { background: var(--accent-fill); color: var(--on-accent); border-color: var(--accent-fill); }

/* Story 31.3: the Files vault — legible upload rows that expand INLINE (mirrors the findings rows). */
.file-item { display: flex; flex-direction: column; }
.file-item .op-row { align-items: flex-start; cursor: pointer; }   /* multi-line → top-align */
/* Story 26.9: the generic inline-detail wrapper (Memory) — row + expandable detail stacked, no
   shared bottom panel. Mirrors .file-item; .op-detail carries the spacing. */
.op-item { display: flex; flex-direction: column; }
.op-item .op-row { cursor: pointer; }
.op-item .memory-detail { margin-top: 8px; }
.op-row .fglyph { font-family: var(--font-mono); font-size: 13px; color: var(--muted); }
.file-detail { margin: 2px 0 8px; padding: 12px 14px; background: var(--panel);
  border: 1px solid var(--line-soft); border-top: none;
  border-radius: 0 0 var(--radius) var(--radius); }
.file-meta { display: flex; flex-direction: column; gap: 4px; }
/* Story 100-22: Saved results — rows like Files; the opened text keeps its line breaks and wraps
   anywhere, so a long unbroken URL or token never widens the page at 390 px. */
.saved-item { display: flex; flex-direction: column; }
.saved-item .op-row { align-items: flex-start; cursor: pointer; }
.saved-item .op-row .grow { min-width: 0; }
.saved-item .ftitle, .saved-item .fsummary, .saved-item .rmeta { overflow-wrap: anywhere; }
.saved-detail { margin: 2px 0 8px; padding: 12px 14px; background: var(--panel);
  border: 1px solid var(--line-soft); border-top: none;
  border-radius: 0 0 var(--radius) var(--radius); min-width: 0; }
.saved-body { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13.5px; line-height: 1.55;
  color: var(--text); margin-bottom: 8px; }
.saved-detail .rmeta { overflow-wrap: anywhere; }
.file-meta-row { display: flex; gap: 12px; font-size: 12px; }
.file-meta-k { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--dim); flex: none; width: 120px; padding-top: 2px; }
.file-meta-v { color: var(--muted); word-break: break-word; min-width: 0; }
.op-flash { font-family: var(--font-mono); font-size: 11px; color: var(--teal); margin: 8px 2px;
  min-height: 14px; }
.op-flash.err { color: var(--red); }
.toast { position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
  background: var(--panel); border: 1px solid var(--teal); color: var(--text);
  font-family: var(--font-mono); font-size: 12px; padding: 10px 16px; border-radius: var(--radius);
  box-shadow: var(--shadow); z-index: 600; animation: fadein 0.25s; }
.toast.err { border-color: var(--red); }

/* Story 33.4 (prod bug #4/#5): a calm, non-modal "changes need a restart" tray — replaces the
   jarring per-toggle confirm()+immediate-restart so setting a restart-required knob no longer reads
   as an error+reload. Saved changes queue here; the operator restarts once, deliberately. */
.restart-tray { position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; max-width: min(680px, 94vw);
  background: var(--panel); border: 1px solid var(--teal); color: var(--text);
  font-family: var(--font-mono); font-size: 12px; padding: 10px 16px; border-radius: var(--radius);
  box-shadow: var(--shadow); z-index: 601; animation: fadein 0.25s; }
.restart-tray-msg { font-weight: 600; }
.restart-tray-names { color: var(--muted); overflow: hidden; text-overflow: ellipsis;
  max-width: 320px; white-space: nowrap; }

/* Story 33.6: the chat "Read aloud" control — compact, sits under a marvin reply body. */
.speak-btn { margin-top: 6px; font-size: 11px; align-self: flex-start; }
/* Story 35.2 (hands-free voice): the mic button pulses red while recording so the "listening" state
   is unmistakable; motion respects prefers-reduced-motion (falls back to a static red). */
#mic-btn.recording { color: var(--red); border-color: var(--red); animation: micpulse 1.1s ease-in-out infinite; }
@keyframes micpulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) { #mic-btn.recording { animation: none; } }
/* Story 35.3 (show Marvin): the camera capture modal — a centered live preview + Capture/Cancel. */
.cam-modal { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.6); }
.cam-modal[hidden] { display: none; }
.cam-panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 12px; max-width: 92vw; display: flex; flex-direction: column; gap: 10px; }
#cam-video { max-width: 80vw; max-height: 60vh; width: 480px; background: #000; border-radius: 6px; }
.cam-actions { display: flex; gap: 8px; justify-content: flex-end; }
/* Story 50.2: the Live Air consent dialog (AC2) and the always-on listening bar (AC3). The bar is
   deliberately loud — red, pulsing, and never collapsible: an open microphone the operator has
   forgotten about is the one failure mode this feature must not have. */
.consent-panel { width: 520px; max-width: 92vw; }
/* Story 73.6: the copy outcome, stated honestly. `.ok` and `.bad` carry the two real outcomes —
   never a success the page did not achieve. */
/* 🔴 Story 73.2, AC2 — `display: none` STILL DOWNLOADS. An <img> hidden by CSS is fetched anyway,
   so a phone on a metered connection would pay for a 126 KB decoration it never sees. A background
   image declared ONLY inside a min-width media query is not fetched when the query does not match:
   the browser never resolves the url() for a rule that does not apply. That is why this is a
   background on an empty div rather than an <img> with a hidden class.
   Default: absent. The narrow layout is the one that must cost nothing. */
.empty-mascot { display: none; }
@media (min-width: 760px) {
  .empty-mascot {
    display: block;
    width: 132px;
    height: 142px;
    margin: 0 auto 14px;
    background: url("marvin-mascot.png") center / contain no-repeat;
    /* Decoration must never outrank the one instruction on an empty screen. */
    opacity: 0.92;
  }
}
@media (prefers-reduced-motion: reduce) { .empty-mascot { transition: none; } }

.cred-status { font-family: var(--font-mono); font-size: 11.5px; margin: 0; }
.cred-status.ok { color: var(--teal); }
.cred-status.bad { color: var(--red); }
#cred-value { font-family: var(--font-mono); width: 100%; }
.consent-panel p, .consent-panel li { color: var(--muted); line-height: 1.5; }
.consent-panel ul { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
.live-air-bar { display: flex; align-items: center; gap: 10px; padding: 8px 10px; margin: 8px 0;
  border: 1px solid var(--red); border-radius: 8px; background: color-mix(in srgb, var(--red) 10%, transparent);
  color: var(--red); }
.live-air-bar[hidden] { display: none; }
.live-air-bar .btn { margin-left: auto; }
/* Story 50.3: the hold-SPACE affordance — quiet until the key is held, then the countdown. */
.ptt-hint { margin-left: 4px; white-space: nowrap; }
.ptt-hint.arming { color: var(--accent); }

/* Epic 34.2: the B2 bucket picker under the b2_bucket setting. */
.b2-picker { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.b2-picker-list { display: flex; flex-wrap: wrap; gap: 6px; }
.b2-bucket-chip { font-size: 11px; }
/* Epic 49 (operator ask): the "Hear it" voice-preview control next to the tts_voice dropdown. */
.voice-preview { display: inline-flex; margin-left: 8px; }

/* ---- settings / persona / status (Story 9.4) ----------------------------- */
/* Story 19.2 — topical, collapsible settings sections (theme-aware via tokens) */
.set-section { margin-bottom: 14px; border: 1px solid var(--line-soft); border-radius: var(--radius);
  background: var(--panel-2); overflow: hidden; }
.set-section-head { display: flex; align-items: center; gap: 9px; padding: 11px 13px;
  cursor: pointer; user-select: none; border-bottom: 1px solid var(--line-soft);
  transition: background var(--tap); }
.set-section-head:hover { background: color-mix(in srgb, var(--accent) 6%, var(--panel-2)); }
.set-section-head .chev { color: var(--accent-dim); font-size: 11px; width: 12px; text-align: center;
  transition: transform var(--tap); }
.set-section-title { font-family: var(--font-display); font-size: 14px; color: var(--text);
  letter-spacing: 0.01em; flex: 1; }
.set-section-count { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.12em;
  color: var(--muted); }
.set-section-rows { padding: 10px 12px; display: grid; gap: 2px; }
.set-section.collapsed .set-section-rows { display: none; }
.set-section.collapsed .set-section-head { border-bottom-color: transparent; }
.set-section.collapsed .chev { transform: rotate(-90deg); }
/* 🔴 Story 100-21 (AC3, MPDA-19): a grid track sized `1fr` cannot shrink below its content's
   min-content width, and a read-only value (`llm_profiles`, `task_routing`) renders as ONE unbroken
   JSON line — measured at 1280 px it stretched "🧠 Models & LLM" to 2,299 px, and `.set-section`'s
   `overflow: hidden` cut 28 Set buttons off the edge. `minmax(0, 1fr)` + `min-width: 0` let the
   value column shrink; `overflow-wrap: anywhere` lets the value (and a long key) wrap instead. */
.set-field { display: grid; grid-template-columns: 240px minmax(0, 1fr) auto; gap: 14px;
  align-items: center; padding: 10px 12px; border: 1px solid var(--line-soft);
  border-radius: var(--radius); background: var(--panel); margin-bottom: 2px; }
.set-field > * { min-width: 0; }
.set-field .sk { font-family: var(--font-mono); font-size: 12px; color: var(--text);
  overflow-wrap: anywhere; }
.set-field .sk .desc { display: block; font-size: 10px; color: var(--dim); margin-top: 3px;
  white-space: normal; letter-spacing: 0; }
.set-field .sv { font-family: var(--font-mono); font-size: 12px; overflow-wrap: anywhere; }
.set-field .ro { color: var(--muted); }
.set-field input, .set-field select { font-family: var(--font-mono); font-size: 12px;
  background: var(--ink); border: 1px solid var(--line); border-radius: 2px; color: var(--text);
  padding: 6px 9px; width: 100%; }
.set-field .save { padding: 5px 12px; font-size: 10px; }
.set-field.locked { opacity: 0.62; }
.set-badge { font-family: var(--font-mono); font-size: 8.5px; letter-spacing: 0.1em;
  text-transform: uppercase; padding: 2px 6px; border-radius: 2px; border: 1px solid var(--line);
  color: var(--dim); }

/* Story 16.8 — start-fresh "Danger zone" (red-accented, keyed off --red) */
.danger-zone { margin-top: 30px; padding: 18px; border-radius: var(--radius);
  border: 1px solid var(--red); background: color-mix(in srgb, var(--red) 8%, var(--panel)); }
.dz-head { font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--red); font-weight: 600; margin-bottom: 6px; }
.dz-note { color: var(--muted); font-size: 12px; line-height: 1.6; max-width: 68ch; margin: 0 0 14px; }
.dz-scopes { display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 10px; }
.dz-scope { font-family: var(--font-mono); font-size: 12px; color: var(--text); cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px; }
.dz-summary:not(:empty) { border-left: 2px solid var(--red); padding: 8px 0 8px 12px; margin: 8px 0 14px;
  display: grid; gap: 6px; }
.dz-wipes { font-size: 12px; color: var(--text); line-height: 1.55; }
.dz-keeps { font-size: 12px; color: var(--muted); line-height: 1.55; }
.dz-confirm { font-family: var(--font-mono); font-size: 13px; color: var(--text); background: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 9px 11px; width: min(340px, 100%);
  display: block; margin-bottom: 10px; }
.dz-confirm:focus { outline: none; border-color: var(--red); }
.dz-ack { display: flex; align-items: flex-start; gap: 8px; font-size: 12px; color: var(--text);
  line-height: 1.5; margin-bottom: 14px; max-width: 64ch; cursor: pointer; }
.btn.danger { background: var(--red); border-color: var(--red); color: var(--on-red); }
.btn.danger:disabled { opacity: 0.45; cursor: not-allowed; filter: none; }
.dz-msg:not(:empty) { margin-top: 12px; font-size: 12px; line-height: 1.55; }
.dz-msg.err { color: var(--red); }
.dz-msg.info { color: var(--muted); }

.restart-overlay { position: fixed; inset: 0; z-index: 550; display: grid; place-items: center;
  background: color-mix(in srgb, var(--ink) 90%, transparent); backdrop-filter: blur(4px); }
.restart-overlay.hidden { display: none; }
.restart-overlay .card { width: min(440px, 92vw); background: var(--panel);
  border: 1px solid var(--accent-dim); border-radius: var(--radius); padding: 28px; text-align: center;
  box-shadow: var(--shadow); }
.restart-overlay h2 { font-family: var(--font-display); margin: 8px 0; }
.restart-overlay p { color: var(--muted); font-size: 12.5px; line-height: 1.6; }
/* Story 18.8: restart-recovery failure state (bounded poll timed out → visible failure, not an infinite spinner) */
.restart-overlay .card.failed { border-color: var(--red); }
.restart-overlay .rhint { color: var(--dim); font-size: 11px; margin-top: 10px; }
.restart-overlay .rbtns { display: flex; gap: 10px; justify-content: center; margin-top: 18px; }
.restart-overlay .rsub { color: var(--dim); font-size: 11px; margin-top: 8px; }
/* Story 49.5: an HONEST progress indicator so a ~70s configured restart never reads as frozen — a
   real elapsed-seconds ticker + an INDETERMINATE animated bar (motion, NOT a % claim; it never
   "completes" — the overlay hides on the real /healthz 200). */
.restart-overlay .rbar { margin: 16px auto 0; height: 4px; width: 82%; border-radius: 2px;
  background: var(--line); overflow: hidden; }
.restart-overlay .rbar-fill { height: 100%; width: 38%; border-radius: 2px; background: var(--accent-fill);
  animation: rbar-slide 1.5s ease-in-out infinite; }
@keyframes rbar-slide { 0% { margin-left: -40%; } 100% { margin-left: 100%; } }
.restart-overlay .relapsed { color: var(--dim); font-size: 11px; margin-top: 8px;
  font-family: var(--font-mono); letter-spacing: 0.04em; }
@media (prefers-reduced-motion: reduce) {
  .restart-overlay .rbar-fill { animation: none; width: 100%; opacity: 0.25; margin-left: 0; } }

/* Story 16.6 — first-run wizard overlay.
   Epic 49 (BUG C): the overlay SCROLLS. It used `display:grid; place-items:center` with NO
   overflow, so on a short window (e.g. 1024x640) a tall card clipped its top/bottom off-screen and
   the Skip/action buttons sat below the fold with no way to reach them (only Escape). Grid +
   `margin:auto` on the card + `overflow-y:auto` on the overlay is the bulletproof scrollable-centered
   pattern (flexbox has the "can't scroll to the top of an overflowing centered child" bug; grid
   doesn't) — the card centers when it fits and scrolls fully into reach when it doesn't. */
.wizard-overlay { position: fixed; inset: 0; z-index: 560; display: grid; overflow-y: auto;
  padding: 24px 12px; background: rgba(0, 0, 0, 0.55); }
.wizard-overlay .card { width: min(460px, 92vw); margin: auto; position: relative; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 24px; text-align: center; }
.wizard-close { position: absolute; top: 8px; right: 10px; background: none; border: none;
  color: var(--muted); font-size: 22px; line-height: 1; cursor: pointer; padding: 4px 8px; }
.wizard-close:hover { color: var(--text); }
.wizard-overlay h2 { font-family: var(--font-display); margin: 4px 0 8px; }
.wizard-overlay .wmsg { color: var(--muted); font-size: 12.5px; line-height: 1.6; margin-bottom: 16px; }
.wizard-presets { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-bottom: 12px; }
/* Epic 49 (installer LOW): keep the primary "Skip for now" / get-me-out control ON SCREEN at a short
   1024x640 viewport without scrolling. The overlay (not the card) is the scroll container (the grid
   pattern above), so pin Skip to the bottom of that scroller as a full-width panel-backed footer band
   — a hairline separates it from the content scrolling behind, which the panel background masks. */
.wizard-skip { position: sticky; bottom: 0; display: block; width: 100%; margin-top: 10px;
  background: var(--panel); border-top: 1px solid var(--line); padding-top: 10px; }
.wizard-key-get { font-size: 11.5px; color: var(--muted); text-decoration: underline; white-space: nowrap; }
.wizard-key-get:hover { color: var(--text); }

/* Story 18.3: gate break-glass link + wizard/settings set-password controls */
.linklike { background: none; border: none; color: var(--muted); text-decoration: underline;
  cursor: pointer; font-size: 12px; padding: 6px 0; font-family: inherit; }
.linklike:hover { color: var(--text); }
.wizard-pw { display: grid; gap: 8px; margin: 4px 0 14px; text-align: left; }
.wizard-pw-row { display: flex; flex-wrap: wrap; gap: 8px; }
.wizard-pw-row .input { flex: 1 1 130px; min-width: 120px; }
.wizard-pw-done { color: var(--teal); font-size: 13px; }
/* Story 18.4 — the wizard "connect your AI keys" section. */
.wizard-keys { display: grid; gap: 8px; margin: 4px 0 14px; text-align: left; }
.wizard-key-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.wizard-key-label { flex: 0 0 130px; color: var(--muted); font-size: 13px; }
.wizard-key-row .input { flex: 1 1 160px; min-width: 140px; }
.wizard-key-chip { font-size: 12px; min-width: 90px; }
.wizard-key-chip.ok { color: var(--teal); }
.wizard-key-chip.err { color: var(--red); }
.wizard-key-chip.warn { color: var(--warn); }
.wizard-key-chip.pending { color: var(--muted); }
.wizard-keys-save { margin-top: 6px; justify-self: start; }
/* The wizard's "already have a Marvin?" restore step. Sits FIRST in the card and is visually
   separated below, because it is an alternative to the whole setup flow rather than a step in it. */
.wizard-restore {
  display: grid;
  gap: 8px;
  margin: 4px 0 16px;
  padding-bottom: 14px;
  text-align: left;
  border-bottom: 1px solid var(--line);
}
.wizard-restore select, .wizard-restore input[type="file"] { max-width: 100%; }
.wizard-restore .btn { justify-self: start; }
.dz-msg.ok { color: var(--teal); }
#change-password .panel-body { display: grid; gap: 8px; }
#change-password .panel-body .input { max-width: 340px; }
/* Epic 48.5: Backup & Migration card + the login-gate recovery form */
#backup-migration .panel-body { display: grid; gap: 8px; }
#backup-migration .panel-body .input,
#backup-migration .panel-body select { max-width: 360px; }
#backup-migration .sk { margin-top: 10px; font-weight: 600; }
#gate-recover { display: block; margin-top: 8px; }
.recover-form { display: grid; gap: 8px; margin: 10px 0 4px; text-align: left; }
.recover-form .input { width: 100%; }
.recover-scope { font-size: 12px; display: inline-flex; align-items: center; gap: 4px; margin-right: 12px; }

.persona-field { display: grid; grid-template-columns: 160px 1fr 56px auto; gap: 14px;
  align-items: center; padding: 12px; border: 1px solid var(--line-soft); border-radius: var(--radius);
  background: var(--panel); margin-bottom: 2px; }
.persona-field .pn { font-family: var(--font-mono); font-size: 12.5px; color: var(--text); }
/* Story 39.2: eight dials need a name and a sentence, not a raw field key. The column widens
   because a description that wraps to four lines is not more legible than the snake_case it
   replaced. */
.persona-field { grid-template-columns: 260px 1fr 56px auto; align-items: start; }
.persona-field input[type=range], .persona-field .pv { margin-top: 6px; }
.persona-field .pn-label { font-family: inherit; font-size: 13px;
  font-weight: 600; color: var(--text); letter-spacing: 0.01em; }
.persona-field .pn-desc { font-family: inherit; font-size: 11px;
  line-height: 1.45; color: var(--muted); margin-top: 3px; }
.persona-group { display: block; margin: 18px 0 8px; color: var(--accent-dim);
  letter-spacing: 0.16em; text-transform: uppercase; }
.persona-group:first-child { margin-top: 0; }
.persona-field input[type=range] { width: 100%; accent-color: var(--accent-fill); }
.persona-field .pv { font-family: var(--font-mono); font-size: 13px; color: var(--accent);
  text-align: right; }
.persona-field .ovr { font-size: 9px; color: var(--teal); }

/* Story 16.2 — persona preset bar */
.persona-quick { margin-bottom: 14px; }
.concise-toggle { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.concise-toggle .pn-desc { font-size: 11px; }
.persona-presets { margin-bottom: 14px; }
.persona-presets .mono-label { display: block; margin-bottom: 8px; }
.persona-presets .preset-row { display: flex; flex-wrap: wrap; gap: 8px; }
.preset-btn { padding: 7px 13px; font-size: 11.5px; }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;
  margin-bottom: 18px; }
.stat-tile { border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
  padding: 15px 16px; }
.stat-tile .label { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--muted); }
.stat-tile .val { font-family: var(--font-display); font-size: 25px; margin-top: 6px; color: var(--text); }
.stat-tile .val.ok { color: var(--teal); } .stat-tile .val.warn { color: var(--warn); }
.stat-tile .val.err { color: var(--red); }

@media (max-width: 720px) {
  .set-field { grid-template-columns: minmax(0, 1fr); } .persona-field { grid-template-columns: 1fr auto; }
}

@media (max-width: 720px) {
  .app { grid-template-columns: 1fr; grid-template-rows: var(--bar-h) auto 1fr; }
  .rail { grid-row: 2; flex-direction: row; overflow-x: auto; border-right: 0;
    border-bottom: 1px solid var(--line); padding: 8px; }
  .rail .group { display: none; } .nav .soon { display: none; }
  .main { padding: 22px 18px 40px; }
  .chat-wrap { grid-template-columns: 1fr; height: auto; }
  .sessions { border-right: 0; border-bottom: 1px solid var(--line-soft); padding-right: 0;
    padding-bottom: 12px; max-height: 140px; }
  .sessions-head .mono-label { flex: 0 1 auto; margin-right: auto; }
  .op-row { flex-wrap: wrap; }
}

/* ---- Story 15.1: Status key→value tree, markdown detail, Documentation ---- */
/* AC1: readable Status — labelled rows instead of a raw JSON.stringify wall */
.panel-body.kv { font-family: var(--font-mono); font-size: 12px; line-height: 1.5; }
.kv-row { display: flex; gap: 12px; padding: 2px 0; align-items: baseline; }
.kv-k { color: var(--muted); min-width: 150px; flex-shrink: 0; letter-spacing: 0.02em; }
.kv-v { color: var(--text); word-break: break-word; }
.kv-v.ok { color: var(--teal); } .kv-v.err { color: var(--red); }
.kv-branch .kv-k { color: var(--text); font-weight: 600; }
.kv-nest { margin: 2px 0 6px 16px; padding-left: 12px; border-left: 1px solid var(--line-soft); }
.kv-scalar { color: var(--text); padding: 1px 0; white-space: pre-wrap; }
.kv-scalar.dim, .dim { color: var(--dim); }
/* AC2: findings detail rendered as markdown, not a collapsed raw line */
.op-detail .md-h { font-family: var(--font-display); color: var(--text); margin: 10px 0 4px; }
.op-detail .md-h1 { font-size: 19px; } .op-detail .md-h2 { font-size: 16px; }
.op-detail .md-h3, .op-detail .md-h4 { font-size: 14px; color: var(--muted); }
.op-detail .md-p { font-family: var(--font-display); font-size: 15px; line-height: 1.65;
  color: var(--text); margin: 6px 0; white-space: pre-wrap; }
.op-detail .md-ul { margin: 4px 0 8px 18px; } .op-detail .md-li { font-size: 14px; line-height: 1.55;
  color: var(--muted); list-style: disc; margin: 2px 0; }
/* AC5: Documentation surface */
.docs { max-width: 760px; }
.docs .doc-h { font-size: 20px; color: var(--text); margin: 22px 0 8px; }
.docs .doc-p { font-family: var(--font-display); font-size: 15px; line-height: 1.7; color: var(--muted);
  margin: 6px 0 14px; }
/* Install guide (Documentation surface): sub-headings, copyable command blocks, and a callout. */
.docs .doc-h3 { font-size: 15px; color: var(--text); margin: 18px 0 6px; font-weight: 600; }
.docs .doc-code { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 12px; margin: 6px 0 12px; overflow-x: auto; }
.docs .doc-code code { font-family: var(--font-mono); font-size: 12.5px; line-height: 1.6;
  color: var(--text); white-space: pre; }
.docs .doc-note { border-left: 3px solid var(--accent); background: var(--panel); border-radius: 4px;
  padding: 8px 12px; margin: 6px 0 14px; font-size: 13.5px; line-height: 1.6; color: var(--muted); }
.docs .doc-dl { margin: 10px 0 14px; }
.docs .doc-dl .btn { font-size: 14px; padding: 8px 14px; }
.doc-features { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px;
  margin: 8px 0 6px; }
.doc-feature { border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
  padding: 12px 14px; }
.doc-feature-name { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--accent); margin-bottom: 6px; }
.doc-feature-desc { font-size: 13.5px; line-height: 1.55; color: var(--muted); }
.doc-mut { display: block; margin: 18px 0 6px; color: var(--dim); }
.doc-knob { padding: 8px 0; border-bottom: 1px solid var(--line-soft); }
.doc-knob-name { font-family: var(--font-mono); font-size: 12px; color: var(--text); }
.doc-knob-desc { font-size: 13px; line-height: 1.5; color: var(--muted); margin-top: 3px; }

/* Story 15.2: inline explainer under an operator form (e.g. Attention → Findings flow) */
.op-hint { color: var(--muted); font-size: 12.5px; line-height: 1.6; margin: 2px 0 14px;
  max-width: 68ch; }
.op-hint strong { color: var(--text); font-weight: 600; }

/* Story 16.1: collapsible "what I recalled" chip atop a chat reply (replaces the raw fence) */
.msg .recall-chip { margin: 2px 0 6px; border: 1px solid var(--line-soft); border-radius: var(--radius);
  background: var(--panel-2); overflow: hidden; }
.msg .recall-chip > summary { cursor: pointer; list-style: none; padding: 4px 10px; font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.msg .recall-chip > summary::before { content: "▸ "; color: var(--dim); }
.msg .recall-chip[open] > summary::before { content: "▾ "; }
.msg .recall-chip > summary::-webkit-details-marker { display: none; }
/* Story 26.4: the structured recall list (replaces the raw .recall-body blob). Each memory's content
   reads as a line with quiet entity · provenance · time meta beneath it. */
.msg .recall-list { padding: 4px 12px 10px; display: flex; flex-direction: column; gap: 8px; }
.msg .recall-item { border-left: 2px solid var(--line); padding-left: 10px; }
.msg .recall-content { font-size: 12.5px; line-height: 1.5; color: var(--text);
  word-break: break-word; }
.msg .recall-meta { margin-top: 2px; font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: 0.04em; color: var(--dim); word-break: break-word; }
.msg .msg-body { white-space: pre-wrap; }

/* Story 16.5 — read-only tools catalog rows */
.tool-row { padding: 10px 12px; border: 1px solid var(--line-soft); border-radius: var(--radius);
  background: var(--panel); margin-bottom: 4px; }
.tool-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tool-name { font-family: var(--font-mono); font-size: 13px; color: var(--text); }
.tool-tag { font-size: 9px; text-transform: uppercase; letter-spacing: 0.08em; padding: 1px 6px;
  border-radius: 4px; background: var(--panel-2); color: var(--muted); border: 1px solid var(--line-soft); }
.tool-tag.generated { color: var(--accent); }
.tool-tag.skill { color: var(--teal); }
.tool-cat { font-family: var(--font-mono); font-size: 10px; color: var(--dim); }
.tool-desc { margin-top: 4px; font-size: 12px; color: var(--muted); line-height: 1.5; word-break: break-word; }
/* Story 28.1 — catalog controls, summary, source groups, enabled-state pill */
.tools-controls { margin-bottom: 8px; }
.tools-filter { width: 100%; max-width: 360px; }
.tools-summary { font-family: var(--font-mono); font-size: 11px; color: var(--dim);
  margin-bottom: 10px; }
.tools-summary:empty { display: none; }
.tool-group-head { display: flex; align-items: baseline; gap: 8px; margin: 14px 0 6px; }
.tool-group-head:first-child { margin-top: 0; }
.tool-group-label { color: var(--muted); }
.tool-group-count { font-size: 10px; color: var(--dim); }
.tool-state { font-size: 9px; text-transform: uppercase; letter-spacing: 0.08em; padding: 1px 6px;
  border-radius: 4px; border: 1px solid var(--line-soft); margin-left: auto; }
.tool-state.on { color: var(--teal); }
.tool-state.off { color: var(--red); }
/* Story 28.2 — the "Create a capability" panel (generate a tool / learn a skill) */
.tools-create { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line-soft); }
.tools-create-h { font-size: 15px; margin-bottom: 10px; }
.tools-create-row { margin-bottom: 14px; }
.tools-create-label { color: var(--muted); margin-bottom: 6px; }
.tools-create-input { display: flex; gap: 8px; flex-wrap: wrap; }
.tools-create-input .input { flex: 1; min-width: 200px; }
.tools-create-input .btn { flex: none; }
.tools-create-hint { margin-top: 6px; font-size: 12px; color: var(--dim); line-height: 1.5; }
.tools-create-hint code { font-family: var(--font-mono); font-size: 11px; color: var(--muted); }
/* Story 30.1 — Cogent Bridge config fields */
.cogent-field { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.cogent-field .tools-create-label { margin-bottom: 0; min-width: 110px; }
.cogent-field .input { flex: 1; min-width: 180px; max-width: 360px; }
.cogent-field input[type="checkbox"] { width: 16px; height: 16px; }

/* Story 16.4 — the "how I got here" trace panel (mirrors the recall-chip idiom) */
.msg .trace-panel { margin: 6px 0 2px; border: 1px solid var(--line-soft); border-radius: var(--radius);
  background: var(--panel-2); overflow: hidden; }
.msg .trace-panel > summary { cursor: pointer; list-style: none; padding: 4px 10px; font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.msg .trace-panel > summary::before { content: "▸ "; color: var(--dim); }
.msg .trace-panel[open] > summary::before { content: "▾ "; }
.msg .trace-panel > summary::-webkit-details-marker { display: none; }
.msg .trace-body { padding: 6px 12px 10px; }
.msg .trace-timeline { margin-top: 8px; }
.msg .trace-timeline > .mono-label { display: block; margin-bottom: 4px; }
.msg .trace-step { display: flex; gap: 8px; padding: 2px 0; font-family: var(--font-mono); font-size: 11.5px; }
.msg .trace-step .step-kind { color: var(--teal); min-width: 84px; flex: none; }
/* min-width:0 defeats a flex item's default min-content floor so a long unbreakable id WRAPS
   inside the bubble instead of forcing horizontal overflow (P5 Edge #2). */
.msg .trace-step .step-detail { color: var(--muted); min-width: 0; word-break: break-word; }

/* Story 25.6: interactive memory graph surface (theme-aware; canvas + node detail). */
.graph-wrap { position: relative; width: 100%; height: 62vh; min-height: 320px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel); overflow: hidden; }
#graph-canvas { width: 100%; height: 100%; display: block; cursor: grab; touch-action: none; }
#graph-canvas:active { cursor: grabbing; }
.graph-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--muted); font-style: italic; pointer-events: none; }
.graph-detail { position: absolute; top: 10px; right: 10px; max-width: 260px; max-height: calc(100% - 20px);
  overflow-y: auto; padding: 10px 12px; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.25); font-size: 0.85rem; }
.graph-detail .gd-title { font-weight: 600; word-break: break-word; }
.graph-detail .gd-meta { color: var(--muted); margin: 2px 0 6px; }
.graph-detail .gd-edges { margin: 0; padding-left: 1.1em; }
.graph-detail .gd-edges li { color: var(--ink); word-break: break-word; margin: 1px 0; }

/* Story 25.6 P5: CSP-safe utility classes replacing 3 inline styles in the 25.7 secret-candidates
   header (style-src 'self' blocked the inline attrs → console CSP errors). */
.mt-lg { margin-top: 1.5rem; }
.h2-sm { font-size: 1.1rem; }
.push-right { margin-left: auto; }

/* Story 27.1: the Memory→Danger-zone deep-link affordance + the transient highlight on arrival. */
.btn.danger-link { color: var(--red); border-color: color-mix(in srgb, var(--red) 40%, transparent); }
.btn.danger-link:hover { background: color-mix(in srgb, var(--red) 12%, transparent); }
#memory-start-fresh-hint { margin: 0.15rem 0 0.4rem; }
.dz-flash { animation: dzFlash 1.6s ease-out; border-radius: 8px; }
@keyframes dzFlash {
  0% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 55%, transparent); }
  100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 0%, transparent); }
}

/* Story 40C.1: why a pinned memory is NOT actually in context — persistent, beside the memory it
   is about. The transient toast alone was the defect: it self-removes after 4.2s and the panel
   behind it asserted "always in context" regardless. */
.warn-note { color: var(--warn); font-size: 12px; line-height: 1.5; margin: 6px 0 4px;
  border-left: 2px solid var(--warn); padding-left: 10px; max-width: 68ch; }

/* Story 51.1: the Life OS surface — five kind groups, read-only lists. Reuses op-list/op-row;
   only the group scaffolding is new. */
.lifeos-group { margin-top: 1.25rem; }
.lifeos-group-head { display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 0.35rem; }
.lifeos-count { opacity: 0.7; }

/* Story 51.2: the Life OS inline create/edit form — reuses .input/.btn; only layout is new. */
.lifeos-head-right { display: flex; align-items: baseline; gap: 0.6rem; }
.lifeos-form { margin: 0.4rem 0 0.6rem; padding: 0.6rem 0.7rem; border: 1px solid var(--line);
  border-radius: 8px; }
.lifeos-field { margin: 0.4rem 0; }
.lifeos-field-label { margin-bottom: 0.15rem; }
.lifeos-field .input { width: 100%; }

/* Story 51.4: a Life OS reference chip in Findings is a link into the Life OS view. The
   dotted underline is the AT-REST affordance (P5: hover-only made links indistinguishable). */
.chip.link { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 2px; }
.chip.link:hover { text-decoration: underline solid; }

/* Story 36.5 R7/AC2 — local-backend readiness. Read-only status shown ABOVE the backend selectors
   so an operator knows a backend cannot run BEFORE switching to it, which is the stated purpose of
   the route behind it. Mirrors the .models-* layout idiom rather than inventing a second one. */
.lb-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.lb-row {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  border: 1px solid var(--line-soft); border-radius: 8px; padding: 8px 10px;
}
.lb-skill { font-weight: 600; min-width: 8.5rem; }
.lb-backend { font-family: var(--font-mono); font-size: 12px; color: var(--muted); }
.lb-badge { font-size: 11px; border-radius: 999px; padding: 1px 8px; border: 1px solid var(--line); }
/* teal = it runs; red = it cannot. Deliberately NOT amber: "cannot run" is a definite state, not a
   warning to be squinted at. */
.lb-badge.ok  { color: var(--teal); border-color: var(--teal); }
.lb-badge.bad { color: var(--red);  border-color: var(--red); }
.lb-reason { color: var(--muted); font-size: 12px; flex: 1 1 14rem; }
/* The fix command is selectable and verbatim — an operator must copy exactly what works. */
.lb-fix {
  font-family: var(--font-mono); font-size: 12px; color: var(--text);
  background: var(--ink); border: 1px solid var(--line-soft); border-radius: 6px;
  padding: 2px 6px; user-select: all; flex-basis: 100%;
}
.lb-size { color: var(--dim); font-size: 11px; font-family: var(--font-mono); }

/* The one inline style the app used to carry (the Life OS "show deleted" toggle). A strict
   `style-src 'self'` CSP blocked it, so the checkbox and its label stacked instead of sitting on
   one line — the style was in the HTML and simply never applied. Found by the 1.44.0 browser
   sweep; it had been throwing a CSP console error on prod since Story 51.1. */
.inline-check {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  cursor: pointer;
}

/* Story 63.2: the temporary-chat indicator. The brand accent already means "temporary" on the
   toggle itself (`.temp-toggle input { accent-color: var(--accent-fill) }`), so the transcript border
   reuses it rather than introducing a second vocabulary for the same state. Deliberately a border
   and a label suffix, not a full-panel tint: it must be unmissable without making the text harder
   to read for the whole conversation. */
body.temp-chat-on .transcript { border-left: 2px solid var(--accent); padding-left: 10px; }
body.temp-chat-on #chat-label::after { content: " · temporary — not saved"; color: var(--accent); }

/* Story 64.2. Classes, not inline styles: the CSP blocks `style=` attributes outright, so an
   inline rule is not merely bad practice here — it silently does nothing. */
.section-gap { margin-top: 26px; }
/* 🔴 Story 73.1 removed a SECOND `.btn.danger` block that stood here.
   It was added by Story 64.2 and set `color: var(--red); border-color: var(--red)`. Line ~649
   already defined `.btn.danger` with `background: var(--red)`. Same specificity, so the later
   rule won on `color` while the earlier one still supplied `background` — and the danger button
   rendered its label in EXACTLY its own background colour. Verified in a real browser on both
   themes before removal: color and background both computed rgb(255,107,94) on dark and
   rgb(181,76,67) on light. 1:1. The label on "delete everything" has been invisible since 64.2.
   Neither story's design was what shipped; the appearance that DID ship is the filled button, so
   the fix keeps it filled and lets the single definition above supply a legible --on-red. */
.btn.danger:hover { background: var(--red); color: var(--on-red); }

/* ── Story 84.0h: the deeper answer, announced and then marked ────────────────────────────────
   The two-level answer worked and was invisible: no sign it was coming, and no sign of what it
   was when it landed. These two rules are the whole user-visible half of that fix. */
#deep-pending { color: var(--muted); font-size: 12px; font-family: var(--font-mono);
  padding: 6px 10px; border-left: 2px solid var(--accent); background: var(--panel);
  border-radius: 4px; margin: 4px 0; }
.msg.marvin.deep { border-left: 3px solid var(--accent); }
.msg.marvin.deep .who { color: var(--accent); }

/* ── Story 84.0e: the first-run tools step ─────────────────────────────────────────────────────
   A row is a checkbox beside a title and a plain-language line about what Marvin can DO with it.
   The description carries the weight, so it is not dimmed into unreadability. */
.wizard-tools-list { display: flex; flex-direction: column; gap: 10px; margin: 8px 0 4px; }
.wizard-tool-row { display: flex; gap: 10px; align-items: flex-start; cursor: pointer;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.wizard-tool-row input[type="checkbox"] { margin-top: 3px; flex: none; }
.wizard-tool-text { display: flex; flex-direction: column; gap: 3px; }
.wizard-tool-title { font-family: var(--font-display); font-size: 14px; color: var(--text); }
.wizard-tool-what { font-size: 12.5px; line-height: 1.5; color: var(--muted); }
.wizard-tool-browse { cursor: default; }
.wizard-browse-controls { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.wizard-browse-controls .input { flex: 1 1 260px; }
.wmsg.dim { color: var(--dim); font-size: 12px; }

/* Story 84.7 — the Tools-service card. 🔴 ITS OWN RULES, BECAUSE THE PATTERN HAS NONE: measured
   during this story, `.gp-body`, a bare `.empty` and `.desc` are all used by existing Settings
   cards and match NO rule at that nesting, so those cards render as unstyled text. Copying the
   convention would have copied the defect. */
.orch-list { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 10px; }
.orch-row { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; font-size: 13px; }
.orch-id { font-family: var(--font-mono); }
.orch-state { opacity: 0.85; }
/* Story 89.3: a terminal invocation whose result never reached Marvin. It is the one row an
   operator is hunting for (schemas.py:387), and it is invisible on every other surface - the
   conversation shows nothing and the inbox shows nothing - so it is marked, not dimmed. */
.orch-undelivered { color: var(--warn); font-weight: 600; }
.orch-body .dim { opacity: 0.7; font-size: 13px; }
