/* ============================================================
   Unified Engine — run view
   Unified Sciences design system: instrument-grade measurement readout.

   Governing rules (measured, see scratchpad/ui_research/03_design_principles.md):
     1. Colour is the third channel. State is a WORD, then a position, then a hue.
     2. Only OUTCOMES earn a hue, and only as text — never as a fill. Process state
        (proposed/running/done) turns over every few seconds; hue-coding it makes
        the run flicker.
     3. Neutrals are hue-locked to the paper (OKLCH H 92-106). Cool text on warm
        paper is the difference between "instrument" and "grubby".
     4. Semantic hues are chroma-locked at OKLCH C = 0.105, so none can shout over
        another. Weight is bought with LIGHTNESS (-strong), never saturation.
     5. Every line that carries meaning clears 3:1. Decorative hairlines are exempt.
     6. Small text gets MORE contrast: everything <=13px is ink-2, never muted.
     7. Each typeface owns a category: Archivo = chrome and labels and numerals,
        Newsreader = running prose only, Plex Mono = verbatim machine output only.
   ============================================================ */

:root {
  /* neutrals — one warm hue family, no temperature break */
  --paper:      #FAFAF7;
  --paper-2:    #F4F4F0;
  --line:       #E8E7E1;   /* decorative hairlines */
  --line-2:     #D8D7D1;   /* card boundary at rest */
  --line-3:     #92918B;   /* 3.02:1 — graph edges, any line that must be SEEN */
  --muted:      #74736E;   /* 4.54:1 — secondary text >=14px only */
  --ink-2:      #494844;   /* 8.76:1 — body prose and all text <=13px */
  --ink:        #1F1E1B;   /* 15.94:1 — titles, primary values */

  /* semantics — chroma-locked at C .105, -text 4.8:1, -mark 3.0:1, -strong 7.0:1 */
  --accent:        #4371AC;
  --accent-mark:   #6494D0;
  --gain:          #397D4B;
  --gain-mark:     #5BA06B;
  --gain-strong:   #1B6232;
  --loss:          #A85951;
  --loss-mark:     #CD7A71;
  --loss-strong:   #8B3F39;
  --gain-wash:     #EEF6EF;
  --loss-wash:     #FDF1EF;

  /* Node surfaces in the run graph: two warm values and nothing else. An earlier
     pass gave every kind its own tint and the graph turned into six colours, half
     of them cool greys fighting the warm paper. Hue lives in the map, which has a
     legend to explain it; the graph distinguishes by value and by the kind label. */
  --k-warm:        #F2F0E9;   /* query, gates — the declarative frame of the run */
  --k-lesson:      #F6F1E1;   /* what the run learned and kept */
  --k-lesson-line: #DED5BE;
  --k-lesson-ink:  #6F6142;
  --k-lesson-mark: #B99B45;   /* map only */

  --font-display: "Archivo", ui-sans-serif, system-ui, sans-serif;
  --font-body:    "Newsreader", Georgia, Cambria, serif;
  --font-mono:    "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --fs-12: 0.75rem;      /* eyebrows, meta */
  --fs-14: 0.875rem;     /* ui */
  --fs-17: 1.0625rem;    /* narration prose, card titles */
  --fs-20: 1.25rem;
  --fs-28: 1.75rem;      /* the headline number */

  --s1: 4px;  --s2: 8px;  --s3: 12px;  --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px;  --s8: 64px;

  /* Card width. Declared once and read by the layout code through
     getComputedStyle, because ELK is told the width before the browser has laid
     the card out — two copies of this number drift and the wires miss the boxes. */
  --node-w: 244px;

  --radius: 4px;
  --header-h: 56px;
  --rail: 400px;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  background: var(--paper); color: var(--ink);
  font-family: var(--font-display); font-size: var(--fs-14);
  display: flex; flex-direction: column; overflow: hidden;
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "kern", "liga"; font-variant-numeric: tabular-nums;
}
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* Neutral reset only. Styling bare `button` previously beat .node on font-family
   and then on :hover background. Components carry their own look. */
button { font: inherit; color: inherit; background: none; border: none; padding: 0;
         cursor: pointer; text-align: inherit; }
button:disabled { opacity: .38; cursor: default; }

.eyebrow { font-size: var(--fs-12); letter-spacing: .06em; text-transform: uppercase;
           font-weight: 600; color: var(--muted); }
.grow { flex: 1; }
.num  { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ============================== header ============================== */
header {
  flex: 0 0 var(--header-h); height: var(--header-h);
  display: flex; align-items: center; gap: var(--s5); padding: 0 var(--s5);
  border-bottom: 1px solid var(--line); background: var(--paper); z-index: 5;
}
.brand { display: flex; align-items: center; gap: 9px; white-space: nowrap; }
.brand .mark { width: 9px; height: 9px; border-radius: 50%; border: 1.5px solid var(--ink); }
.brand b { font-weight: 600; letter-spacing: -0.01em; font-size: var(--fs-17); }
.brand .ver { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }

#submit-form { display: flex; gap: var(--s3); flex: 1; max-width: 820px; }
#query {
  flex: 1; padding: var(--s2) var(--s3); border: 1px solid var(--line-2); border-radius: var(--radius);
  background: var(--paper); color: var(--ink); font-family: var(--font-display); font-size: var(--fs-14);
}
#query::placeholder { color: var(--muted); }
#query:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(67,113,172,.14); }

.btn {
  font-family: var(--font-mono); font-size: var(--fs-14);
  border-radius: var(--radius); border: 1px solid var(--ink);
  background: var(--ink); color: var(--paper); padding: var(--s2) 18px;
  transition: background .12s ease;
}
.btn:hover:not(:disabled) { background: #000; }

.right { display: flex; align-items: center; gap: var(--s3); margin-left: auto; }
#runs {
  font-family: var(--font-mono); font-size: var(--fs-12); color: var(--ink-2);
  background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--radius);
  padding: 6px var(--s2); max-width: 230px;
}
.status {
  font-family: var(--font-mono); font-size: var(--fs-12); letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-2); white-space: nowrap;
  display: inline-flex; align-items: center; gap: 6px;
}
.status::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--line-3); }
.status.running::before  { background: var(--ink); animation: breathe 2.4s ease-in-out infinite; }
.status.finished::before { background: var(--gain-mark); }
.status.error            { color: var(--loss-strong); }
.status.error::before    { background: var(--loss-mark); }
@keyframes breathe { 0%, 100% { opacity: 1 } 50% { opacity: .28 } }
@media (prefers-reduced-motion: reduce) { .status.running::before { animation: none } }

/* ============================== layout ============================== */
main { flex: 1; display: flex; min-height: 0; }
#main-pane { flex: 1; display: flex; flex-direction: column; min-width: 0; }
#body { position: relative; flex: 1; min-height: 0; }
.view { display: none; }
.view.active { display: block; }

#toolbar {
  flex: 0 0 44px; display: flex; align-items: center; gap: var(--s3);
  padding: 0 var(--s4) 0 var(--s4); border-bottom: 1px solid var(--line); background: var(--paper);
}
#headline { font-size: var(--fs-14); color: var(--ink-2); white-space: nowrap;
            overflow: hidden; text-overflow: ellipsis; }
#headline .k { font-family: var(--font-mono); color: var(--ink); }
#headline .k.gain { color: var(--gain); }
#headline .k.loss { color: var(--loss); }

#view-toggle { display: flex; border: 1px solid var(--line-2); border-radius: var(--radius); overflow: hidden; }
.vt { background: var(--paper); color: var(--muted); padding: 6px var(--s4);
      font-size: var(--fs-12); letter-spacing: .06em; text-transform: uppercase; font-weight: 600; }
.vt + .vt { border-left: 1px solid var(--line-2); }
.vt:hover:not(.active) { color: var(--ink-2); background: var(--paper-2); }
.vt.active { background: var(--ink); color: var(--paper); }

/* ---- activity strip: one line of "what is happening right now" ----
   Not a feed. A viewer who looked away needs to re-enter in one glance, and a
   results page that shows nothing for ninety seconds reads as a hung app. */
#activity[hidden] { display: none; }
#activity {
  flex: 0 0 auto; display: flex; align-items: center; gap: var(--s3);
  padding: 9px var(--s4) 9px var(--s5);
  border-bottom: 1px solid var(--line); background: var(--paper-2);
  font-size: var(--fs-14); color: var(--ink-2);
}
#activity .pip {
  flex: 0 0 auto; width: 6px; height: 6px; border-radius: 50%; background: var(--ink);
  animation: breathe 2.4s ease-in-out infinite;
}
#activity.settled .pip { animation: none; background: var(--muted); }
@media (prefers-reduced-motion: reduce) { #activity .pip { animation: none } }
.act-iter { font-family: var(--font-mono); font-size: var(--fs-12); letter-spacing: .06em;
            text-transform: uppercase; font-weight: 600; color: var(--ink); }
.act-what { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.act-what .kind { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.linkish { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--accent); }
.linkish:hover { text-decoration: underline; }
.linkish[hidden] { display: none; }

/* ============================== results ledger ============================== */
/* Every other tool in this category leads with an activity feed, because activity
   is what they bill for. We sell the outcome, so the outcome is the landing view. */
#results { overflow-y: auto; height: 100%; padding: var(--s7) var(--s7) var(--s8); }
.ledger { max-width: 940px; margin: 0 auto; }

.ledger-head { margin-bottom: var(--s6); }
.ledger-head h2 { font-size: var(--fs-20); font-weight: 600; letter-spacing: -0.01em;
                  margin-top: var(--s1); color: var(--ink); }
/* the answer, stated once, at the size it deserves */
.hero { border: 1px solid var(--line-2); border-radius: var(--radius);
        background: var(--paper-2); padding: var(--s4) var(--s5) var(--s5); }
.hero-h { display: flex; align-items: baseline; gap: var(--s3); margin-bottom: var(--s4); }
.hero-src { margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-12);
            color: var(--muted); display: inline-flex; gap: var(--s2); align-items: baseline; }
.hero-cells { display: flex; flex-wrap: wrap; gap: var(--s5) var(--s7); }
.hc-d { font-family: var(--font-mono); font-size: var(--fs-28); font-weight: 600;
        line-height: 1; letter-spacing: -0.02em; }
.hc-d.gain { color: var(--gain); }
.hc-d.loss { color: var(--loss); }
.hc-d.tie  { color: var(--ink); }
.hc-n { margin-top: 7px; font-size: var(--fs-14); font-weight: 500; color: var(--ink); }
.hc-v { margin-top: 3px; font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.hc-v .arrow { padding: 0 5px; color: var(--line-3); }

/* declared-before-the-run: the bar, then whether it was cleared */
.gr { display: flex; align-items: baseline; gap: var(--s3);
      padding: var(--s2) 0; border-bottom: 1px solid var(--line); }
.gr:last-child { border-bottom: none; }
.gr-k { flex: 0 0 11em; font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.gr-v { flex: 1; min-width: 0; font-family: var(--font-body); font-size: var(--fs-14); color: var(--ink); }
.gr-s { font-family: var(--font-mono); font-size: var(--fs-12); letter-spacing: .06em;
        text-transform: uppercase; }
.gr-s.gain { color: var(--gain); }
.gr-s.loss { color: var(--loss); }
.gr-s.muted { color: var(--muted); }

.journey { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); align-items: baseline; }
.jb { font-family: var(--font-body); font-size: var(--fs-14); color: var(--ink-2); }
.jb b { font-family: var(--font-mono); font-weight: 600; color: var(--ink);
        font-size: var(--fs-17); margin-right: 3px; }

.grp { margin-bottom: var(--s5); }
.grp-head { display: flex; align-items: baseline; gap: var(--s3); margin-bottom: var(--s2); }
.grp-head .t { font-size: var(--fs-14); font-weight: 600; color: var(--ink); }
.grp-it { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--line-3); }
.grp-head .vf { margin-left: auto; }

table.rows { width: 100%; border-collapse: collapse; }
table.rows td.r { text-align: right; }
table.rows td { padding: var(--s3) var(--s3) var(--s3) 0; border-bottom: 1px solid var(--line);
                font-size: var(--fs-14); color: var(--ink-2); vertical-align: baseline; }
/* the per-measurement list is now supporting evidence under the hero, so its
   deltas step down a size — two competing 20px numeric columns read as two
   headlines and the reader has to work out which one is the answer */
td.delta { font-family: var(--font-mono); font-size: var(--fs-14); font-weight: 600;
           letter-spacing: -0.01em; width: 1%; white-space: nowrap; }
td.delta.gain { color: var(--gain); }
td.delta.loss { color: var(--loss); }
td.delta.tie  { color: var(--muted); }
td.metric { color: var(--ink); font-weight: 500; }
td.ba { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); white-space: nowrap; }
td.ba .to { color: var(--ink-2); }
td.ba .arrow { padding: 0 var(--s1); }
/* the grade word on a measurement group — a span now, not a table cell */
.grade { font-size: var(--fs-12); letter-spacing: .06em; text-transform: uppercase;
         font-weight: 600; white-space: nowrap; }
.g-improvement { color: var(--gain); }
.g-regression  { color: var(--loss); }
.g-tradeoff, .g-tie { color: var(--muted); }

.ledger-empty { font-family: var(--font-body); font-size: var(--fs-17); color: var(--muted);
                max-width: 60ch; }
.le-prog { margin-top: var(--s3); font-family: var(--font-mono); font-size: var(--fs-12);
           letter-spacing: .04em; color: var(--ink-2); }

/* ============================== run graph ============================== */
#run { height: 100%; }
#graph-scroll { position: absolute; inset: 0; overflow: auto; }
#stage { position: relative; margin: 0 auto; }
#wires { position: absolute; inset: 0; overflow: visible; pointer-events: none; }
/* Cross-references between branches — supersedes, borrows-from, blocks, revives.
   Near-invisible at rest and legible one at a time: six dashed verticals at equal
   weight is six things you cannot follow. Lit on hover, on selection, and for a
   few seconds when the link first appears. */
/* At rest: present but recessive — always there, so nothing appears to vanish,
   and quiet enough that six of them do not compete with the spine. Lit only on
   hover or selection, which is a deliberate act, so the emphasis is never a
   surprise mid-playback. */
#wires path.xref { stroke: var(--line-2); stroke-dasharray: 3 5;
                   transition: stroke .2s ease, stroke-width .2s ease; }
#wires path.xref[marker-end] { marker-end: none; }
#wires text.xref { fill: var(--line-2); font-style: italic; opacity: 0;
                   transition: opacity .2s ease, fill .2s ease; }
#wires path.xref.lit { stroke: var(--accent); stroke-width: 1.6; stroke-dasharray: 5 4; }
#wires path.xref.lit[marker-end] { marker-end: url(#ah); }
#wires text.xref.lit { fill: var(--accent); opacity: 1; }
#wires marker path { fill: var(--accent-mark); stroke: none; }
@media (prefers-reduced-motion: reduce) { #wires path.xref, #wires text.xref { transition: none } }
#wires path { fill: none; stroke: var(--line-3); stroke-width: 1; }   /* 3.02:1 — was invisible */
#wires text {
  font-family: var(--font-mono); font-size: var(--fs-12); fill: var(--ink-2);
  text-anchor: middle; dominant-baseline: middle;
  paint-order: stroke; stroke: var(--paper); stroke-width: 4px; stroke-linejoin: round;
}

.node {
  position: absolute; width: var(--node-w); box-sizing: border-box;
  display: flex; flex-direction: column; gap: 3px;
  /* A DOI, an idea slug and `recall_at_10__resample` are all single unbreakable
     tokens wider than the card. Without this they run past the border. */
  overflow-wrap: anywhere; word-break: break-word;
  padding: var(--s3) 14px 14px; border: 1px solid var(--line-2); border-radius: var(--radius);
  background: var(--paper); color: var(--ink); cursor: pointer; text-align: left;
  font-family: var(--font-display); font-size: var(--fs-14);
  opacity: 0; transform: translateY(9px);
  transition: opacity .55s ease, transform .55s cubic-bezier(.22,.61,.36,1),
              border-color .16s ease, box-shadow .16s ease, background-color .22s ease;
}
.node.in { opacity: 1; transform: none; }
/* Position only animates once a card has been placed. Without the two-step a new
   card glides in from 0,0; with it, existing cards slide to their new rank instead
   of teleporting, which is the difference between "blip" and something readable. */
.node.placed {
  transition: opacity .55s ease, transform .55s cubic-bezier(.22,.61,.36,1),
              left .8s cubic-bezier(.33,.9,.35,1), top .8s cubic-bezier(.33,.9,.35,1),
              border-color .16s ease, box-shadow .16s ease, background-color .22s ease;
}
/* A card that just arrived holds a brief ring, so the eye knows where to look when
   several things settle at once. */
@keyframes arrive { from { box-shadow: 0 0 0 6px rgba(31,30,27,.10); } to { box-shadow: 0 0 0 6px rgba(31,30,27,0); } }
.node.fresh { animation: arrive 1.5s ease-out 1; }
@media (prefers-reduced-motion: reduce) { .node.fresh { animation: none } }
@media (prefers-reduced-motion: reduce) { .node, .node.placed { transition: none; } }

/* two surfaces, both warm. Everything else stays paper. */
.node.k-query, .node.k-gates { background: var(--k-warm); }
.node.k-lesson { background: var(--k-lesson); border-color: var(--k-lesson-line); }
.node.k-lesson .k { color: var(--k-lesson-ink); }
/* An outcome earns colour as a *line*, not a fill — a washed card reads as a
   status banner and there is already a coloured verdict word inside it. */
.node.v-gain { border-color: var(--gain-mark); }
.node.v-loss { border-color: var(--loss-mark); }
.node:hover { border-color: var(--muted); box-shadow: 0 1px 2px rgba(31,30,27,.04), 0 4px 14px rgba(31,30,27,.06); }
.node .k { font-size: var(--fs-12); letter-spacing: .06em; text-transform: uppercase;
           font-weight: 600; color: var(--muted); min-width: 0; }
.node .l { font-size: var(--fs-17); font-weight: 600; line-height: 1.28; letter-spacing: -0.01em; color: var(--ink); min-width: 0; }
.node .m { margin-top: 2px; font-family: var(--font-mono); font-size: var(--fs-12); color: var(--ink-2); min-width: 0; }
/* Quieter than the headline: the clock is context for the number above it, not a
   competing fact. Tabular figures so a column of cards does not jitter as seconds
   tick. */
.node .ts { margin-top: 1px; font-family: var(--font-mono); font-size: var(--fs-12);
            color: var(--muted); font-variant-numeric: tabular-nums; min-width: 0; }

/* What this node cost, in tokens. Absent when nothing was attributed to it —
   a node with no line spent nothing *that we could attribute*, which is not the
   same as free, so it says nothing rather than "0". */
.node .tok {
  margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--line);
  font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted);
  display: flex; gap: 8px; flex-wrap: wrap;
}
.node .tok .io { color: var(--ink-2); }
.node .tok .cache { color: var(--muted); }
.node .m.gain { color: var(--gain); }
.node .m.loss { color: var(--loss); }

.node.is-muted .k, .node.is-muted .l { color: var(--muted); }
.node.is-muted  { border-color: var(--line); }
.node.is-current { border-color: var(--ink-2); }
.node.is-current .k::before {
  content: ''; display: inline-block; vertical-align: middle;
  width: 5px; height: 5px; margin: -1px 7px 0 0; border-radius: 50%; background: var(--ink);
}
/* the active node has to be findable at a glance in a 23-node graph */
.node.is-working {
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(31,30,27,.075), 0 6px 20px rgba(31,30,27,.09);
}
.node.is-working .k::before { width: 7px; height: 7px; animation: breathe 2.4s ease-in-out infinite; }
.node .st { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--ink);
            letter-spacing: .04em; }
/* which pass of the loop, since the graph's vertical axis is depth and not time */
.node .k { display: flex; align-items: baseline; gap: 6px; }
.node .it { margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-12);
            font-weight: 500; color: var(--line-3); letter-spacing: 0; }
@media (prefers-reduced-motion: reduce) { .node.is-working .k::before { animation: none } }
.node.sel { border-color: var(--ink); box-shadow: 0 1px 2px rgba(31,30,27,.05), 0 6px 18px rgba(31,30,27,.09); }

/* ============================== map ============================== */
#mini { position: absolute; inset: 0; width: 100%; height: 100%; }
#mini.active { display: block; }

/* the loop unrolled through time — one column per pass, state carried along the top */
#mini .lane { fill: var(--paper-2); opacity: .5; }
#mini .lane-l { font-family: var(--font-mono); font-size: 11px; font-weight: 600;
                fill: var(--ink); letter-spacing: .08em; text-anchor: middle; }
#mini .lane-s { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .06em;
                text-transform: uppercase; fill: var(--muted); text-anchor: middle; }
#mini .lane-s.gain { fill: var(--gain); }
#mini .lane-s.loss { fill: var(--loss); }
#mini .jump { fill: none; stroke: var(--line-2); stroke-width: 1.1; stroke-dasharray: 3 4; }
/* the recurrent connection: what one pass hands the next */
#mini .carry { fill: none; stroke: var(--k-lesson-mark); stroke-width: 1.6; }
#mini marker path { fill: var(--k-lesson-mark); }
#mini .carry-l { font-family: var(--font-mono); font-size: 10.5px; fill: var(--k-lesson-ink);
                 text-anchor: middle; letter-spacing: .04em; }

/* legend — only the map spends hue, so only the map explains it */
#map-legend[hidden] { display: none; }
#map-legend {
  /* top-right: the foot of each column now carries that pass's outcome, and the
     legend was landing on top of them */
  position: absolute; right: var(--s5); top: var(--s4); z-index: 2;
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4);
  padding: var(--s3) var(--s4); border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--paper);
}
.lg { display: inline-flex; align-items: center; gap: 7px;
      font-family: var(--font-mono); font-size: var(--fs-12); color: var(--ink-2); }
.lg-d { width: 9px; height: 9px; border-radius: 50%; border: 1.4px solid transparent; }
#mini line { stroke: var(--line-3); stroke-width: 1; }
#mini circle { cursor: pointer; transition: r .12s ease; }
#mini circle:hover { r: 8; }

#empty { position: absolute; inset: 0; display: flex; flex-direction: column;
         align-items: center; justify-content: center; gap: var(--s2); pointer-events: none; }
#empty .big { font-family: var(--font-body); font-size: var(--fs-20); color: var(--ink-2); }
#empty .sub { font-size: var(--fs-12); letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
#empty.hidden { display: none; }

/* The key gate borrows the empty state's centring, and the empty state is
   click-through on purpose so it never blocks the graph beneath it. That made the
   gate's own input unclickable. It is a blocking dialog, not an empty state, so it
   takes its events back. */
#empty.gate { pointer-events: auto; background: var(--paper); }
#empty.gate .sub { text-transform: none; letter-spacing: 0; font-size: var(--fs-14); }
.gate-row { display: flex; gap: var(--s2); align-items: center; }
#gate-key { font-family: var(--font-mono); font-size: var(--fs-14); padding: 9px 12px;
            min-width: 24em; border: 1px solid var(--line-2); border-radius: var(--radius);
            background: var(--surface); color: var(--ink); }
#gate-key:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
#gate-err:empty { display: none; }

#tip {
  position: absolute; z-index: 6; pointer-events: none; max-width: 280px;
  background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--radius);
  padding: var(--s3) 14px; box-shadow: 0 1px 2px rgba(31,30,27,.04), 0 4px 16px rgba(31,30,27,.07);
}
#tip .tk { display: block; font-size: var(--fs-12); letter-spacing: .06em; text-transform: uppercase;
           font-weight: 600; color: var(--muted); }
#tip .tl { display: block; margin-top: 3px; font-size: var(--fs-14); font-weight: 600; line-height: 1.3; color: var(--ink); }
#tip .tm { display: block; margin-top: 5px; font-family: var(--font-mono); font-size: var(--fs-12); color: var(--ink-2); }
#tip .tm.gain { color: var(--gain); }
#tip .tm.loss { color: var(--loss); }

/* ============================== rail (node detail, full height) ============================== */
#rail[hidden] { display: none; }
#rail {
  position: relative;
  width: var(--rail); flex: 0 0 var(--rail); border-left: 1px solid var(--line);
  background: var(--paper-2); display: flex; flex-direction: column; min-height: 0;
}

/* Drag handle on the rail's own left edge. Sits over the border rather than taking
   layout width, so widening the rail does not shift the graph by the handle's size
   as well. */
#rail-grip {
  position: absolute; top: 0; bottom: 0; left: -3px; width: 7px; z-index: 2;
  cursor: col-resize; background: transparent;
}
#rail-grip:hover, #rail-grip:focus-visible, #rail.resizing #rail-grip {
  background: var(--gain-mark); outline: none;
}
/* While dragging, the pointer is over the graph half the time and text selection
   turns the whole page blue. */
.resizing-body { user-select: none; cursor: col-resize; }

/* ============================== sheet (the wide read of one node) ============================== */
/* ID-scoped throughout. A bare `dialog` rule would be the fourth bug from styling an
   element selector in this file. */
#sheet {
  position: fixed; inset: 0 0 0 auto; margin: 0; max-height: 100vh; height: 100vh;
  width: min(920px, 92vw); max-width: none;
  padding: 0; border: none; border-left: 1px solid var(--line);
  background: var(--paper-2); color: var(--ink);
  display: flex; flex-direction: column; min-height: 0;
  box-shadow: 0 0 60px rgba(31,30,27,.14);
}
#sheet:not([open]) { display: none; }
#sheet::backdrop { background: rgba(31,30,27,.28); }
/* The moved-in `#detail` keeps its own scrolling; this is only the flex parent. */
#sheet-mount { flex: 1; min-height: 0; display: flex; flex-direction: column; }

@media (prefers-reduced-motion: no-preference) {
  #sheet[open] { animation: sheet-in .22s cubic-bezier(.22,.61,.36,1); }
  #sheet[open]::backdrop { animation: fade-in .22s ease-out; }
}
@keyframes sheet-in { from { transform: translateX(16px); opacity: .6 } }
@keyframes fade-in { from { opacity: 0 } }

/* ============================== logs drawer (left) ============================== */
#logs-scrim[hidden], #logs-drawer[hidden] { display: none; }
#logs-scrim { position: fixed; inset: 0; z-index: 18; background: rgba(31,30,27,.16); }
#logs-drawer {
  position: fixed; top: 0; bottom: 0; left: 0; z-index: 19;
  width: min(var(--rail), 92vw); display: flex; flex-direction: column; min-height: 0;
  background: var(--paper-2); border-right: 1px solid var(--line);
  box-shadow: 0 0 40px rgba(31,30,27,.10);
  transform: translateX(-100%); transition: transform .26s cubic-bezier(.22,.61,.36,1);
}
#logs-drawer.open { transform: translateX(0); }
@media (prefers-reduced-motion: reduce) { #logs-drawer { transition: none } }

#logs-toggle {
  flex: 0 0 auto; display: flex; flex-direction: column; justify-content: center; gap: 3.5px;
  width: 26px; height: 26px; padding: 0 4px; border-radius: var(--radius);
  border: 1px solid transparent;
}
#logs-toggle .bar { display: block; height: 1.5px; background: var(--ink-2); border-radius: 1px; }
#logs-toggle .bar:nth-child(2) { width: 72%; }
#logs-toggle:hover { border-color: var(--line-2); background: var(--paper-2); }
#logs-toggle:hover .bar { background: var(--ink); }
#logs-toggle[aria-expanded="true"] .bar { background: var(--ink); }

.rail-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4) var(--s3) var(--s5);
  border-bottom: 1px solid var(--line); background: var(--paper);
}
.rail-head .eyebrow { flex: 1; }
/* class, not element — `.rail-head button` outranks `.dn` and was resizing the
   density control to 17px. Third bug from styling a bare element selector. */
.rail-head .x { color: var(--muted); font-size: var(--fs-17); line-height: 1; padding: 0 var(--s1); }
.rail-head .x:hover { color: var(--ink); }

/* a text switch, not a segmented control — at this size a filled pill is a lump */
.density { display: flex; align-items: baseline; gap: var(--s2); }
.dn { font-size: var(--fs-12); letter-spacing: .06em; text-transform: uppercase; font-weight: 600;
      color: var(--muted); padding: 0; background: none; }
.dn:hover { color: var(--ink-2); }
.dn.active { color: var(--ink); box-shadow: inset 0 -2px 0 var(--gain-mark); }
.dn + .dn::before { content: ''; }

.rail-body { overflow-y: auto; flex: 1; padding: var(--s2) var(--s5) var(--s5); }

/* narration — prose, so Newsreader */
.note { display: flex; gap: var(--s3); padding: var(--s3) 0; border-bottom: 1px solid var(--line); }
.note:last-child { border-bottom: none; }
.note .seq { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted);
             flex: 0 0 24px; padding-top: 5px; }
.note .body { flex: 1; }
.note .who { display: block; margin-bottom: 3px; font-family: var(--font-mono);
             font-size: var(--fs-12); color: var(--muted); }
.note .txt { font-family: var(--font-body); font-size: var(--fs-17); line-height: 1.55; color: var(--ink-2); }
.note.insight .txt { color: var(--ink); }

/* raw events, shown at "all" density. Provenance wraps to its own indented line
   rather than being truncated — which model made a judgment is the point of it. */
.ev { display: flex; flex-wrap: wrap; gap: 0 var(--s3); align-items: baseline;
      font-family: var(--font-mono); font-size: var(--fs-12); padding: 5px 0;
      border-bottom: 1px solid var(--line); color: var(--ink-2); }
.ev .s { color: var(--muted); flex: 0 0 24px; text-align: right; }
.ev .t { color: var(--ink); }
.ev .by { flex-basis: 100%; padding-left: calc(24px + var(--s3)); color: var(--muted); }

/* ============================== raw ==============================
   The dump. Every event, in order, nothing summarised away and nothing dropped.
   Machine output, so Plex Mono throughout and no prose face anywhere — this is the
   one view whose job is to be scanned for a string, not read.

   It exists because the other three views are projections: a step that runs a
   search for twenty-five minutes leaves the graph, the ledger and the headline
   byte-identical for the whole time, and "nothing is moving" and "nothing is
   happening" then look the same. */
#raw { height: 100%; display: none; flex-direction: column; min-height: 0; }
#raw.active { display: flex; }

#raw-bar {
  flex: 0 0 auto; display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s4); border-bottom: 1px solid var(--line);
  background: var(--paper-2);
}
#raw-filter {
  flex: 0 0 240px; padding: 5px var(--s2); border: 1px solid var(--line-2);
  border-radius: var(--radius); background: var(--paper); color: var(--ink);
  font-family: var(--font-mono); font-size: var(--fs-12);
}
#raw-filter:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Type chips, one per type the run produced — derived from the log, not declared,
   so a type the engine invents tomorrow gets a chip without a CSS change. */
#raw-kinds { display: flex; flex-wrap: wrap; gap: var(--s1); min-width: 0; }
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px var(--s2); border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--paper); color: var(--ink-2);
  font-family: var(--font-mono); font-size: var(--fs-12); cursor: pointer;
}
.chip:hover { border-color: var(--line-3); }
.chip.on { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.chip .n { color: var(--muted); font-variant-numeric: tabular-nums; }
.chip.on .n { color: var(--line-2); }

#raw-count { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--ink-2);
             white-space: nowrap; font-variant-numeric: tabular-nums; }
#raw-follow-wrap { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
                   font-family: var(--font-mono); font-size: var(--fs-12); color: var(--ink-2);
                   cursor: pointer; }

#raw-rows { flex: 1; min-height: 0; overflow: auto; padding: var(--s2) 0 var(--s6); }

/* One row per event. Fixed columns for seq/time/actor/type so the eye can run
   down any one of them; the summary takes the rest and clips rather than wraps,
   because a wrapping row destroys that alignment. The payload is a click away. */
.rw {
  display: grid; grid-template-columns: 56px 62px 108px 96px minmax(0, 1fr);
  gap: var(--s3); align-items: baseline; padding: 3px var(--s4);
  font-family: var(--font-mono); font-size: var(--fs-12); line-height: 1.5;
  color: var(--ink-2); cursor: pointer; border-left: 2px solid transparent;
}
.rw:hover { background: var(--paper-2); }
.rw.open { background: var(--paper-2); border-left-color: var(--ink); }
.rw-s { color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }
.rw-t { color: var(--muted); font-variant-numeric: tabular-nums; }
.rw-a { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rw-k { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rw-d { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A trace is the loudest thing in here by count and the least important by
   meaning, so it is the quietest thing by value. */
.rw.tr .rw-k, .rw.tr .rw-d { color: var(--muted); }
.rw.wa .rw-d { color: var(--loss); }
.rw.er { border-left-color: var(--loss-mark); }
.rw.er .rw-d { color: var(--loss-strong); }

.rw-json {
  margin: 0 var(--s4) var(--s2) calc(var(--s4) + 2px);
  padding: var(--s3); background: var(--paper-2);
  border: 1px solid var(--line-2); border-radius: var(--radius);
  font-family: var(--font-mono); font-size: var(--fs-12); line-height: 1.5;
  color: var(--ink-2); white-space: pre-wrap; word-break: break-word;
  max-height: 50vh; overflow: auto;
}
.rw-more { padding: var(--s2) var(--s4); font-family: var(--font-mono);
           font-size: var(--fs-12); color: var(--muted); }

/* The last thing a backend did, on the activity strip. Truncates before the node
   label does, since the label answers "where" and this only answers "still?". */
.tr-tail { display: inline-flex; align-items: baseline; gap: var(--s2); margin-left: var(--s3);
           padding-left: var(--s3); border-left: 1px solid var(--line-2);
           font-family: var(--font-mono); font-size: var(--fs-12); }
.tr-tail .tr-n { color: var(--line-3); font-variant-numeric: tabular-nums; }
.tr-tail .tr-h { color: var(--ink); }
.tr-tail .tr-d { color: var(--muted); }

/* node detail */
#detail .head { padding: var(--s3) 0 var(--s1); }
#detail h3 { font-size: var(--fs-17); font-weight: 600; letter-spacing: -0.01em; line-height: 1.25; margin-top: var(--s1); }

/* Key/value rows. The old version gave the label 38% of the width and set every
   value in mono, so a sentence wrapped into a narrow column of typewriter text
   with half the panel left empty beside it. Label sizes to its content; the value
   takes the rest and is set in the body face unless it is genuinely a value. */
.kvr { display: flex; gap: var(--s3); align-items: baseline;
       padding: 5px 0; border-bottom: 1px solid var(--line); }
.kvr:last-child { border-bottom: none; }
.kvk { flex: 0 0 auto; min-width: 5.4em; max-width: 9em;
       font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.kvv { flex: 1; min-width: 0; font-family: var(--font-body); font-size: var(--fs-14);
       line-height: 1.45; color: var(--ink-2); }
/* This data is full of tokens with no spaces in them — DOIs, URLs, engine keys
   like `api:arxiv+openalex+semanticscholar+europepmc+pubmed`. Without this they
   run past the right edge of the rail rather than wrapping. */
.kvv, .vr-v, .vlist > li, .lz-v, .gr-v { overflow-wrap: anywhere; }

/* A list or a record gets the full width of the rail, with its key as a heading
   above it. Side by side, the value would have about 230px of the 400px. */
.kvr-block { display: block; }
.kvr-block .kvk { display: block; margin-bottom: var(--s1); max-width: none; }

/* ---- structured attribute values ----
   An attr can be any JSON, so a value slot has to render a list or a record and
   not only a string. Two rules govern the look:

   The label goes ABOVE its value, not beside it. These nest inside a 400px rail
   and the values are often a paragraph — a side-by-side label would leave the
   value a hundred pixels wide.

   Depth is shown with a rule down the left, not with indentation. Indenting each
   level eats the width the prose needs. */
.vlist { list-style: none; display: flex; flex-direction: column; gap: 3px; }
.vlist > li { position: relative; padding-left: var(--s3); }
.vlist > li::before {
  content: '';  position: absolute; left: 3px; top: 0.62em;
  width: 4px; height: 1px; background: var(--line-3);
}
.vlist-obj { gap: var(--s2); }
.vlist-obj > li { padding-left: var(--s3); border-left: 1px solid var(--line-2); }
.vlist-obj > li::before { display: none; }
.vmore { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }

.vrec { display: flex; flex-direction: column; gap: 3px; }
.vr-k {
  font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted);
  /* the key is a lookup target, so it never wraps */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Short scalar beside its key. `min-content` on the key column lets a two-word
   value start right after it instead of at a fixed gutter. */
.vr-inline { display: grid; grid-template-columns: minmax(min-content, 9em) minmax(0, 1fr);
             gap: var(--s2); align-items: baseline; }
.vr-stack > .vr-k { display: block; margin-bottom: 1px; }

.vnone { color: var(--line-3); }
.vjson { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--ink-2);
         word-break: break-all; }

.verdict { margin-top: var(--s5); border: 1px solid var(--line-2); border-radius: var(--radius);
           padding: 14px var(--s4); background: var(--loss-wash); }
.verdict.accepted { background: var(--gain-wash); }
.verdict h4 { font-size: var(--fs-12); letter-spacing: .06em; text-transform: uppercase;
              font-weight: 600; color: var(--loss-strong); }
.verdict.accepted h4 { color: var(--gain-strong); }
.verdict ul { margin: var(--s2) 0 0; padding-left: var(--s4); }
.verdict li { font-family: var(--font-body); font-size: var(--fs-14); line-height: 1.5;
              color: var(--ink-2); margin-bottom: var(--s1); }
.verdict li:last-child { margin-bottom: 0; }

/* the finding, in prose, above the telemetry */
.lede { font-family: var(--font-body); font-size: var(--fs-17); line-height: 1.5;
        color: var(--ink); margin-top: var(--s4); }

/* metrics as compact rows — three hero numbers pushed everything below the fold */
.mt { width: 100%; border-collapse: collapse; margin-top: var(--s1); }
.mt td { padding: 5px 0; border-bottom: 1px solid var(--line); vertical-align: baseline; }
.mt tr:last-child td { border-bottom: none; }
.mt-d { font-family: var(--font-mono); font-size: var(--fs-14); font-weight: 600;
        width: 3.8em; white-space: nowrap; }
.mt-d.gain { color: var(--gain); }
.mt-d.loss { color: var(--loss); }
.mt-d.tie  { color: var(--ink-2); }
.mt-n { font-size: var(--fs-14); color: var(--ink); padding-right: var(--s3); }
.mt-v { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted);
        text-align: right; white-space: nowrap; }
.mt-v .arrow { padding: 0 5px; color: var(--line-3); }

/* ---- node detail sections ---- */
.sect { margin-top: var(--s5); }
/* no header: these rows say what they are. "Recorded" was a label on a label. */
.sect-bare { margin-top: var(--s4); }
.sect > h4, .lesson-box > h4 {
  font-size: var(--fs-12); letter-spacing: .06em; text-transform: uppercase;
  font-weight: 600; color: var(--muted); margin-bottom: var(--s2);
}

/* lessons: the claim reads as prose, the condition is the part that makes it usable */
.lesson-box {
  margin-top: var(--s4); padding: 14px var(--s4); border-radius: var(--radius);
  background: var(--k-lesson); border: 1px solid var(--k-lesson-line);
}
.lesson-box > h4 { color: var(--k-lesson-ink); }
.lz-claim { font-family: var(--font-body); font-size: var(--fs-17); line-height: 1.42;
            color: var(--ink); margin-bottom: var(--s3); }
.lz-r { display: grid; grid-template-columns: 6.6em 1fr; gap: var(--s3);
        padding: var(--s2) 0; border-top: 1px solid var(--k-lesson-line); }
.lz-k { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--k-lesson-ink); }
.lz-v { font-family: var(--font-body); font-size: var(--fs-14); line-height: 1.45; color: var(--ink-2); }

/* lineage — clickable, because the whole point of a graph is following an edge */
.lin-g { margin-bottom: var(--s3); }
.lin-h { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); margin-bottom: 4px; }
.lin {
  display: flex; gap: var(--s3); align-items: baseline; width: 100%;
  padding: 4px 0; border-bottom: 1px solid var(--line);
  transition: color .14s ease;
}
.lin:last-child { border-bottom: none; }
.lin:hover .lin-lbl { color: var(--accent); text-decoration: underline; }
.lin-rel { flex: 0 0 8.4em; font-family: var(--font-mono); font-size: var(--fs-12);
           color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lin-lbl { flex: 1; min-width: 0; font-size: var(--fs-14); color: var(--ink); line-height: 1.35;
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* verified / not-verified marker — text only, never a fill */
.vf { font-family: var(--font-mono); font-size: var(--fs-12); letter-spacing: .04em; }
.vf-verified { color: var(--gain-strong); }
.vf-failed { color: var(--loss-strong); }
.vf-unverified { color: var(--muted); }
.grp-out { opacity: .72; }

/* verification checks — the fixed battery, then the gates declared with the target.
   Status is text-coloured, never a fill: colour stays the third channel. */
.ckg { margin-top: var(--s3); }
.ckg + .ckg { margin-top: var(--s4); }
.ckh { font-family: var(--font-mono); font-size: var(--fs-12); letter-spacing: .04em;
       color: var(--muted); margin-bottom: var(--s1); }
.ck { padding: var(--s2) 0; border-top: 1px solid var(--line-2); }
.ck:last-child { padding-bottom: 0; }
.ck-terse { padding: 3px 0; border-top: none; }
.ck-terse + .ck-terse { border-top: none; }
.ck-s { font-family: var(--font-mono); font-size: var(--fs-12); letter-spacing: .04em;
        margin-right: var(--s2); }
.ck-pass .ck-s { color: var(--gain-strong); }
.ck-fail .ck-s { color: var(--loss-strong); }
.ck-n { font-family: var(--font-body); font-size: var(--fs-14); color: var(--ink); }
.ck-fail .ck-n { font-weight: 600; }
.ck-d { display: block; margin-top: 3px; font-family: var(--font-body);
        font-size: var(--fs-14); line-height: 1.45; color: var(--ink-2); }

/* provenance — which model produced this judgment */
.by { margin-top: var(--s3); font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.by .ext { color: var(--ink-2); }

.empty-note { font-family: var(--font-body); font-size: var(--fs-14); color: var(--muted); padding-top: var(--s5); }

/* ============================== footer ============================== */
footer {
  flex: 0 0 auto; display: flex; gap: var(--s4); align-items: baseline;
  padding: var(--s2) var(--s5); border-top: 1px solid var(--line); background: var(--paper);
}
#summary { font-family: var(--font-body); font-size: var(--fs-14); color: var(--ink-2); }
/* activity counts are telemetry, not the point — they live here, quietly */
#counter { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); white-space: nowrap; }

.rail-body::-webkit-scrollbar, #results::-webkit-scrollbar { width: 10px; }
.rail-body::-webkit-scrollbar-track, #results::-webkit-scrollbar-track { background: transparent; }
.rail-body::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 10px; border: 3px solid var(--paper-2); }
#results::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 10px; border: 3px solid var(--paper); }

/* ============================== live ==============================
   The view a run spends most of its life in. Results is the landing tab because
   we sell the outcome, but a seven-hour run has no outcome for seven hours, and
   an empty ledger is not an answer to "is this working".

   Three blocks, in the order the questions get asked: is it alive (pulse), what
   is it doing (in flight, activity), is it getting closer (scoreboard). */
#live { overflow-y: auto; height: 100%; padding: var(--s5) var(--s6) var(--s8); }
.livewrap { max-width: 940px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--s5); }

/* ---- pulse: the heartbeat line ---- */
.pulse {
  display: flex; align-items: baseline; gap: var(--s3) var(--s4); flex-wrap: wrap;
  padding: var(--s3) var(--s4); border: 1px solid var(--line-2);
  border-radius: var(--radius); background: var(--paper-2);
  font-family: var(--font-mono); font-size: var(--fs-12);
}
.pulse .pl-dot {
  align-self: center; width: 7px; height: 7px; border-radius: 50%;
  background: var(--gain-mark); animation: breathe 2.4s ease-in-out infinite;
}
.pulse.stalled .pl-dot { background: var(--loss-mark); animation: none; }
.pulse.settled .pl-dot { background: var(--line-3); animation: none; }
@media (prefers-reduced-motion: reduce) { .pulse .pl-dot { animation: none } }
.pl-word { font-weight: 600; color: var(--ink); text-transform: uppercase; letter-spacing: .06em; }
.pulse.stalled .pl-word { color: var(--loss); }
.pl-sep { width: 1px; align-self: stretch; background: var(--line-2); }
.pl-k { color: var(--muted); }
.pl-v { color: var(--ink); font-variant-numeric: tabular-nums; }

/* ---- in flight: what is open right now ---- */
.flz { display: flex; flex-direction: column; gap: var(--s2); }
.fl {
  border: 1px solid var(--line-2); border-left: 2px solid var(--accent-mark);
  border-radius: var(--radius); background: var(--paper); padding: var(--s3) var(--s4);
}
.fl-h { display: flex; align-items: baseline; gap: var(--s2); }
.fl-k { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.fl-l { font-size: var(--fs-14); font-weight: 500; color: var(--ink);
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fl-t { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--ink-2);
        font-variant-numeric: tabular-nums; }
.fl-m { display: flex; gap: var(--s3); margin-top: var(--s1);
        font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.fl-turn { color: var(--accent); }
.fl-cmd { margin-top: var(--s2); display: flex; gap: var(--s2); min-width: 0;
          font-family: var(--font-mono); font-size: var(--fs-12); }
.fl-tool { flex: 0 0 auto; color: var(--muted); }
.fl-d { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- scoreboard: the needle ---- */
.sb { border: 1px solid var(--line-2); border-radius: var(--radius);
      background: var(--paper); padding: var(--s4) var(--s5) var(--s5); }
.sb-head { display: flex; align-items: baseline; gap: var(--s3); margin-bottom: var(--s4); }
.sb-meta { margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.sb-none { font-family: var(--font-body); font-size: var(--fs-14); color: var(--muted); }
.sb-row { padding: var(--s3) 0; border-top: 1px solid var(--line); }
.sb-row:first-of-type { border-top: 0; padding-top: 0; }
.sb-top { display: flex; align-items: baseline; gap: var(--s3); }
.sb-name { font-size: var(--fs-14); font-weight: 500; color: var(--ink); }
.sb-aim { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.sb-now { margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-17);
          font-weight: 600; font-variant-numeric: tabular-nums; }
.sb-now.gain { color: var(--gain); }
.sb-now.tie { color: var(--ink); }
.sb-now.muted { color: var(--muted); font-size: var(--fs-12); font-weight: 400; }
.sb-bar { margin-top: var(--s2); height: 4px; border-radius: 2px; background: var(--line);
          overflow: hidden; }
.sb-bar i { display: block; height: 100%; background: var(--line-3);
            transition: width .5s ease; }
.sb-bar i.gain { background: var(--gain-mark); }
.sb-unit { margin-top: 5px; font-family: var(--font-mono); font-size: var(--fs-12);
           color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.gt { margin-top: var(--s4); border-top: 1px solid var(--line); }
.gt-row { display: flex; align-items: baseline; gap: var(--s3); padding: 5px 0;
          font-size: var(--fs-14); border-top: 1px solid var(--line); }
.gt-row:first-child { border-top: 0; }
.gt-n { color: var(--ink-2); min-width: 0; overflow: hidden;
        text-overflow: ellipsis; white-space: nowrap; }
.gt-a { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted); }
.gt-v { margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-12);
        color: var(--ink); font-variant-numeric: tabular-nums; }
.gt-s { flex: 0 0 auto; width: 62px; text-align: right;
        font-family: var(--font-mono); font-size: var(--fs-12); }
.gt-s.gain { color: var(--gain); }
.gt-s.loss { color: var(--loss); }
.gt-s.muted { color: var(--muted); }

.sb-bat { margin-top: var(--s5); }
.chips { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s2); margin-top: var(--s2); }
.chip { font-family: var(--font-mono); font-size: var(--fs-12); padding: 2px 7px;
        border-radius: 10px; border: 1px solid var(--line-2); color: var(--muted); }
.chip.gain { color: var(--gain-strong); border-color: var(--gain-mark); background: var(--gain-wash); }
.chip.loss { color: var(--loss-strong); border-color: var(--loss-mark); background: var(--loss-wash); }

/* ---- the feed ----
   Notes and traces in one stream. A note is what the run concluded and carries the
   weight; a trace is what a backend did and sits quieter beneath it. Text wraps:
   the old feed clipped every row to one line, so the strategist's reasoning
   arrived as a fragment ending in an ellipsis and read as noise. */
.fd { border: 1px solid var(--line-2); border-radius: var(--radius);
      background: var(--paper); padding: var(--s4) var(--s5) var(--s3); }
.fd-head { display: flex; align-items: baseline; gap: var(--s3); margin-bottom: var(--s3); }
.fd-row { display: grid; grid-template-columns: 58px 84px auto; gap: 4px var(--s3);
          padding: 7px 0; border-top: 1px solid var(--line); align-items: baseline; }
.fd-row:first-of-type { border-top: 0; }
.fd-t { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted);
        font-variant-numeric: tabular-nums; }
.fd-s { font-family: var(--font-mono); font-size: var(--fs-12); color: var(--muted);
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fd-h { grid-column: 3; font-family: var(--font-mono); font-size: var(--fs-12);
        color: var(--accent); }
.fd-h.said { color: var(--muted); font-style: italic; }
.fd-d { grid-column: 3; min-width: 0; overflow-wrap: anywhere; }

/* A note: the run's own words, in the reading face, at full width. */
.fd-row.note .fd-d { font-family: var(--font-body); font-size: var(--fs-17);
                     line-height: 1.5; color: var(--ink); }
.fd-row.note .fd-s { color: var(--ink-2); }
/* A trace: what a backend did. Mono, quieter, capped in height so one long shell
   command cannot push the decisions off the screen. */
.fd-row.tr .fd-d { font-family: var(--font-mono); font-size: var(--fs-12);
                   line-height: 1.55; color: var(--ink-2);
                   max-height: 4.7em; overflow: hidden; }
.fd-d.warn { color: var(--loss); }
.fd-d.bad { color: var(--loss-strong); font-weight: 500; }

#live::-webkit-scrollbar { width: 10px; }
#live::-webkit-scrollbar-track { background: transparent; }
#live::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 10px; border: 3px solid var(--paper); }
/* An open node with no step behind it: the engine that was driving it is gone.
   Shown rather than hidden — it is why the graph has a build that never finishes. */
.fl.idle { border-left-color: var(--line-2); opacity: .72; }
.fl-stale { color: var(--loss); }
/* The baseline under its target. Landing it is progress — it is the denominator
   every later comparison divides by — so it reads as a value, not as absence. */
.sb-base { margin-top: 5px; font-family: var(--font-mono); font-size: var(--fs-12);
           color: var(--muted); }
.sb-base b { color: var(--ink-2); font-weight: 600; }
