/* console.css — Live Simulation: the view shell, the command rail, the comms
   column, and the feed's voices and channels. */

/* =================== SIM VIEW (command console) =================== */
/* One shell for every view. Live Simulation used to be the only tab with
   a gutter and a panel border; Knowledge Base and Armory ran flush to
   the window edge and Data Ingestion floated a bare column in the middle
   of one, so switching tabs felt like switching applications. */
.view-panel { padding: var(--shell-pad); gap: var(--shell-pad); flex-direction: column; }
/* Views that own their own internal split keep a row direction. */
#wiki-view { flex-direction: row; gap: 0; }
#wiki-view > .wiki-sidebar { border-radius: var(--radius) 0 0 var(--radius); }
#wiki-view > .wiki-reading { border: 1px solid var(--surface-border); border-left: 0; border-radius: 0 var(--radius) var(--radius) 0; background: var(--surface-color); }
.wiki-sidebar { border: 1px solid var(--surface-border); }

/* The shared frame Armory and Data Ingestion now sit inside, so all four
   tabs read as the same application. */
.view-frame {
    flex: 1; min-height: 0; width: 100%; display: flex; flex-direction: column;
    background: var(--surface-color); border: 1px solid var(--surface-border);
    border-radius: var(--radius); overflow: hidden;
}
.frame-head {
    padding: var(--space-base) var(--space-loose); border-bottom: 1px solid var(--surface-border);
    display: flex; justify-content: space-between; align-items: center; gap: var(--space-base);
    background: var(--surface-recessed-soft); flex-wrap: wrap;
}

/* Empty states: a mark, what belongs here, and one action — instead of a
   grey sentence adrift in 700px of void. */
.empty-state {
    flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--space-snug); padding: var(--space-section) var(--space-loose); text-align: center; color: var(--text-secondary);
}
.empty-state .es-mark { color: var(--accent-color); opacity: 0.5; }
.empty-state .es-mark .ico { width: 34px; height: 34px; }
.empty-state h4 { font-size: var(--type-body); letter-spacing: var(--track-control); color: var(--text-primary); }
.empty-state p { font-size: var(--type-control); max-width: 40ch; line-height: var(--leading-body); color: var(--text-faint); }
.empty-state .es-action { margin-top: var(--space-micro); }
.sim-grid { flex: 1; min-height: 0; display: grid; grid-template-columns: 332px 1fr; gap: var(--space-shell); }
/* The rail narrows before it leaves, so a 1100px window keeps both columns. */
@media (max-width: 1200px) { .sim-grid { grid-template-columns: 288px 1fr; gap: var(--space-base); } }

/* ── Below 1000px: one column. ────────────────────────────────────────
   The grid used to stay 332px + 1fr at every width, so a 390px phone got
   a 4px conversation column with its input and Transmit Order laid out
   past the edge of the screen. Now the feed owns the viewport and the
   rail becomes a drawer, summoned from the comms head. */
@media (max-width: 1000px) {
    .sim-grid { grid-template-columns: 1fr; gap: 0; }
    .command-rail {
        position: fixed; left: 0; top: 0; bottom: 0; z-index: 210;
        width: min(330px, 86vw); padding: var(--space-shell);
        background: var(--surface-color); border-right: 1px solid var(--surface-border-strong);
        box-shadow: 8px 0 30px var(--shadow-ink);
        transform: translateX(-102%); visibility: hidden;
        transition: transform var(--dur-med) var(--ease-out), visibility var(--dur-med);
    }
    .command-rail.is-open { transform: none; visibility: visible; }
}
@media (prefers-reduced-motion: reduce) {
    .command-rail { transition: none !important; }
}

/* ---- left command rail ---- */
/* The rail's rhythm used to run 16·16·16·18·28·26·24: MORE air between the
   three action buttons than between the unrelated groups above them,
   because each button carried its own inline margin-top. Proximity read
   backwards — the things that belong together looked furthest apart.
   One gap for siblings, one class for a group boundary, and no inline
   margins anywhere in the rail. */
.command-rail { display: flex; flex-direction: column; gap: var(--space-base); min-height: 0; overflow-y: auto; overflow-x: hidden; }
/* Rail scrolls as a whole; its blocks keep their intrinsic height (no squish). */
.command-rail > * { flex-shrink: 0; }
.rail-sep { margin-top: var(--space-base); }   /* 13 + 13 gap = one loose step between groups */
.rail-actions { display: flex; flex-direction: column; gap: var(--space-snug); }
.rail-actions > button { width: 100%; }
/* The tally is a readout, so it is set in the data face — and that is what
   actually makes it stable. Oswald ships no tabular figures, so on the button's
   own face the tabular-nums below was inert and the count jittered 6.75px per
   digit while it climbed. A declaration only holds if the face can honour it. */
.btn-count { opacity: 0.7; font-family: var(--font-data); font-size: var(--type-data);
             font-variant-numeric: tabular-nums; }
/* Adapt to Player cannot profile anything until three actions have been
   observed. Until then it reads as not-yet-available — dimmed label, no
   hover lift — and it arms on the third one. Not `disabled`: a keyboard
   user has to be able to reach it and hear the title explaining why. */
/* Dimming the border to --surface-border dropped this control's own
   boundary to 1.3:1 — a divider token doing a control's job, which is
   exactly what The Control Edge Rule exists to prevent, and the button is
   still fully clickable so the inactive-component exemption does not
   apply. The edge stays at full strength and goes DASHED instead: a
   not-yet signal that survives greyscale, with the faint label carrying
   the rest. */
.btn-ghost.is-waiting { color: var(--text-faint); border-style: dashed; }
.btn-ghost.is-waiting:hover { background: transparent; border-color: var(--control-border); }
@keyframes armLand {
    from { box-shadow: 0 0 0 0 var(--accent-color); }
    60%  { box-shadow: 0 0 0 5px transparent; }
    to   { box-shadow: 0 0 0 0 transparent; }
}
.btn-ghost.just-armed { animation: armLand 700ms var(--ease-out); }
/* The comms rail has been dead the whole download. When it comes live it
   should be seen coming live — the top rule brightens to the accent and
   settles back, once, on the first enable only. */
/* Border and an inset bleed only — never `background`. Writing the dark
   theme's own rgba into the `to` frame made the rail settle onto a colour
   that is wrong on manila and on Los Angeles, then snap back when the
   class came off. Tokens all the way down, or it is not theme-portable. */
@keyframes railCameLive {
    from { border-top-color: var(--accent-color); box-shadow: inset 0 26px 26px -26px var(--accent-color); }
    to   { border-top-color: var(--surface-border); box-shadow: inset 0 26px 26px -26px transparent; }
}
.input-area.came-live { animation: railCameLive 900ms var(--ease-out); }
.crest {
    position: relative; display: flex; align-items: center; gap: var(--space-base); padding: var(--space-shell);
    /* Each theme states its crest washes in tokens.css (phase 14). They were
       derived with rgb(from …) behind a Doctrine-red literal fallback, so an
       engine without relative colour showed Doctrine's crest in every theme. */
    background: linear-gradient(180deg, var(--crest-wash-top), var(--crest-wash-bottom));
    border: 1px solid var(--crest-edge);
    border-radius: var(--radius); overflow: hidden;
}
.crest::before { content: ""; position: absolute; inset: 0;
    background: repeating-linear-gradient(135deg, var(--crest-hatch) 0 10px, transparent 10px 20px); }
.crest img { height: 44px; width: 44px; position: relative; border-radius: var(--radius); }   /* the emblem is a printed plate: .plate inks it; a glow is for emitters only */
.crest .ct { position: relative; }
.crest .ct .l1 { font-family: var(--font-display); font-weight: 700; font-size: var(--type-body); letter-spacing: var(--track-display); text-transform: uppercase; }
.crest .ct .l2 { font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-label); color: var(--text-secondary); margin-top: var(--space-hair); }

.ctrl-block { display: flex; flex-direction: column; gap: var(--space-snug); }

/* ---- Commander memory (src/ui/memory.js) ----
   The fight the commander remembers, in the rail under the persona. A status
   block like the one below it: recessed, hairline, flat. The combat state
   leads with an icon and a word (the shape and the word carry the state; the
   colour only repeats it), the reason is prose, the gauges and pressure are
   machine values in the data face, and the SITUATION block sits one
   disclosure down, verbatim. The select under it is the presenter override. */
.mem-panel { background: var(--surface-2); border: 1px solid var(--surface-border); border-radius: var(--radius); padding: var(--space-base); }
.mem-panel > .field-label { font-size: var(--type-data); }
.mem-summary { font-size: var(--type-control); line-height: var(--leading-data); color: var(--text-primary); }
.mem-state { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: var(--space-tight) var(--space-snug); padding: var(--space-snug); border: 1px solid var(--surface-border-strong); border-radius: var(--radius); background: var(--surface-recessed-soft); }
.mem-state .ico { width: 18px; height: 18px; color: var(--accent-color); }
.mem-state-name { font-family: var(--font-display); font-size: var(--type-control); font-weight: 600; letter-spacing: var(--track-display); text-transform: uppercase; color: var(--text-primary); }
.mem-state-src { font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-data); text-transform: uppercase; color: var(--text-secondary); border: 1px dashed var(--surface-border-strong); border-radius: var(--radius); padding: 0 var(--space-tight); }
.mem-state:has(.mem-state-name:not([data-state="nominal"])) .ico { color: var(--status-working); }
.mem-reason { grid-column: 1 / -1; font-size: var(--type-caption); line-height: var(--leading-data); color: var(--text-secondary); }
.mem-gauges { grid-column: 1 / -1; font-size: var(--type-data); letter-spacing: var(--track-data); color: var(--terminal-text); }
#combat-state-selector { font-family: var(--font-display); text-transform: uppercase; letter-spacing: var(--track-display); color: var(--text-primary); }
.mem-table { width: 100%; border-collapse: collapse; font-size: var(--type-caption); }
.mem-table caption { text-align: left; font-family: var(--font-display); font-size: var(--type-data); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--text-secondary); padding-bottom: var(--space-tight); }
.mem-table th, .mem-table td { text-align: left; padding: var(--space-hair) var(--space-tight) var(--space-hair) 0; border-top: 1px solid var(--surface-border); font-weight: 400; color: var(--text-primary); }
.mem-table thead th { font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-data); color: var(--text-faint); border-top: 0; }
.mem-table td.mono { font-size: var(--type-data); white-space: nowrap; color: var(--terminal-text); }
.mem-pword { color: var(--text-secondary); }
.mem-ctl { white-space: nowrap; }
.mem-table tr[data-control="contested"] .mem-ctl { color: var(--status-working); }
.mem-table tr[data-control="lost"] .mem-ctl { color: var(--danger-text); font-weight: 600; }
.mem-label { font-family: var(--font-display); font-size: var(--type-data); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--text-secondary); }
.mem-list { margin: 0; padding-left: var(--space-base); font-size: var(--type-caption); line-height: var(--leading-data); color: var(--text-primary); }
.mem-list .mono { font-size: var(--type-data); }
.mem-sent > summary { cursor: pointer; font-family: var(--font-display); font-size: var(--type-data); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--accent-color); padding: var(--space-tight) 0; }
.mem-sent > summary:hover { color: var(--text-primary); }
.mem-note { font-size: var(--type-caption); line-height: var(--leading-data); color: var(--text-secondary); margin-bottom: var(--space-tight); }
.mem-block { margin: 0; padding: var(--space-snug); max-height: 260px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--font-data); font-size: var(--type-data); line-height: var(--leading-data); color: var(--terminal-fg); background: var(--terminal-bg); border: 1px solid var(--terminal-fg); border-radius: var(--radius); }
.mem-forget { align-self: flex-start; font-size: var(--type-caption); padding: var(--space-tight) var(--space-snug); }
.mem-forget .ico { width: 12px; height: 12px; }

.status-panel { background: var(--surface-2); border: 1px solid var(--surface-border); border-radius: var(--radius); padding: var(--space-base); display: flex; flex-direction: column; gap: var(--space-snug); }
.status-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-snug); font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-data); }
.status-row .k { color: var(--text-secondary); white-space: nowrap; }
.status-row .v { white-space: nowrap; font-family: var(--font-data); font-variant-numeric: tabular-nums; }
.status-row .v { color: var(--terminal-text); }
.status-row .v.red { color: var(--danger-text); }

.rail-stamp {
    /* margin-top:auto pinned this to the bottom of the rail — but the rail
       scrolls in practice, so auto resolved to 0, and a -7deg rotation
       inflates the box about 8px upward. The measured gap to the button
       above it was MINUS 2px: the stamp sat on Clear Knowledge Base.
       Explicit clearance, sized for the rotation. */
    margin-top: var(--space-base); align-self: flex-start; transform: rotate(-7deg);
    font-family: var(--font-display); font-weight: 700; font-size: var(--type-control); letter-spacing: var(--track-title); text-transform: uppercase;
    color: var(--danger-text); border: 1px solid var(--danger-border); padding: var(--space-micro) var(--space-base); border-radius: var(--radius); opacity: 0.7;
}

/* ---- right comms column ---- */
.comms-col { display: flex; flex-direction: column; min-height: 0; background: var(--surface-color); border: 1px solid var(--surface-border); border-radius: var(--radius); overflow: hidden; }
.comms-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-base); padding: var(--space-base) var(--space-shell); border-bottom: 1px solid var(--surface-border); background: var(--surface-recessed-soft); flex-shrink: 0; }
.rail-toggle {
    display: none; align-items: center; gap: var(--space-micro); flex-shrink: 0;
    font-family: var(--font-display); font-weight: 600; font-size: var(--type-data); letter-spacing: var(--track-tab); text-transform: uppercase;
    background: transparent; color: var(--text-primary);
    border: 1px solid var(--surface-border-strong); border-radius: var(--radius);
    padding: var(--space-micro) var(--space-snug); cursor: pointer;
    transition: color .16s ease, background .16s ease, border-color .16s ease;
}
.rail-toggle .ico { color: var(--accent-color); }
.rail-toggle:hover, .rail-toggle[aria-expanded="true"] { background: var(--tint-objective); border-color: var(--accent-color); }
/* Must sit after the base rule above, or the cascade keeps it hidden. */
@media (max-width: 1000px) { .rail-toggle { display: inline-flex; } }
/* On a phone the head carries the drawer control and the section name;
   the Cyrillic subtitle and the REC tag are atmosphere and can wait. */
@media (max-width: 640px), (max-height: 600px) {
    .comms-head { padding: var(--space-snug) var(--space-base); }
    .comms-head .title .ru { display: none; }
    .comms-head .live-tag { display: none; }
}
.comms-head .title { display: flex; align-items: baseline; gap: var(--space-base); }
.comms-head .title .en { font-family: var(--font-display); font-weight: 600; font-size: var(--type-body); letter-spacing: var(--track-title); line-height: var(--leading-title); text-transform: uppercase; }
.comms-head .title .ru { font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-label); color: var(--text-secondary); }
.live-tag { display: inline-flex; align-items: center; gap: var(--space-snug); font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-label); color: var(--danger-text); }
.live-tag::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent-danger); box-shadow: 0 0 6px var(--accent-danger); }

#action-log {
    flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: var(--space-shell); display: flex; flex-direction: column; gap: var(--space-base);
    background:
        linear-gradient(var(--scanline) 1px, transparent 1px) 0 0 / 100% 26px;
}

.log-entry {
    position: relative; overflow-wrap: break-word; padding: var(--space-base) var(--space-base) var(--space-base) var(--space-shell); border: 1px solid var(--surface-border); border-left: 3px solid var(--surface-border-strong);
    border-radius: var(--radius); background: var(--entry-ground); animation: fadeIn var(--dur-med) var(--ease-out);
}
.log-entry > strong:first-child { font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-data); }

/* ---- Three voices in the feed, where there was one ----
   Measured before this: every entry in the comms feed rendered at 15px
   regular. A routine "Loaded OGA drop table: 14 items" and DRIFT
   PREVENTED — the single claim this product exists to demonstrate — had
   the same size, the same weight and the same padding, and the claim was
   a bolded phrase in the middle of a running sentence. Blur the console
   until only mass survives and the three loudest things on screen were
   all decoration; the retrieval trace, which IS the positioning claim,
   was the faintest band in the feed.
   So: a claim leads, the payload reads, and routine chatter recedes. The
   sizes are the documented Headline / Body / Control steps — the top of
   the ramp existed and was doing no work anywhere in the product's
   central surface. */
.log-entry .claim {
    display: block; font-family: var(--font-display); font-weight: 600;
    font-size: var(--type-claim); letter-spacing: var(--track-control); text-transform: uppercase;
    line-height: var(--leading-claim); margin-bottom: var(--space-micro);
    text-wrap: balance;
}
.log-entry .claim-sub { display: block; font-size: var(--type-control); color: var(--text-secondary); line-height: var(--leading-body); text-wrap: pretty; }
/* Routine status is the majority of the feed by volume and the least of
   it by consequence. It steps down so the two tiers above it can exist. */
.log-entry.system { font-size: var(--type-control); }
/* …except when the entry IS the payload. The commander's answer and its
   reasoning share the system channel — it is the machine's own voice —
   but it is not routine, and stepping it down with the chatter was the
   wrong half of this change. */
.log-entry.has-payload { font-size: var(--type-body); }
/* The order's head (phase 13): the commander's portrait, the headline with
   who gave it, and the printed plate of the place it lands on, its name under
   it. The portrait and plate are identity and place, never data; each has its
   words beside it, so neither is the only place a fact appears. */
.entry-head { display: flex; align-items: center; gap: var(--space-base); }
.entry-head .claim { margin-bottom: 0; }
.order-title { display: flex; flex-direction: column; gap: var(--space-hair); flex: 1 1 auto; min-width: 0; }
.order-who { font-size: var(--type-caption); color: var(--text-secondary); }
.order-portrait, .persona-portrait { flex: 0 0 auto; border: 1px solid var(--surface-border-strong); border-radius: var(--radius); overflow: hidden; line-height: 0; }
.order-portrait .plate { width: 48px; height: 48px; }
.order-place { flex: 0 0 auto; margin: 0; display: flex; flex-direction: column; align-items: center; gap: var(--space-hair); }
.order-place .plate { width: 96px; height: 64px; border: 1px solid var(--surface-border-strong); border-radius: var(--radius); }
.order-place figcaption { font-family: var(--font-display); font-size: var(--type-data); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--text-secondary); }

/* ---- The turn path (phase 13, src/ui/turnpath.js) ----
   The whole order in one row at its top: Retrieved › Thought › Checked ›
   Cited › Re-run, each step the value that step produced and a link to the
   entry that shows its work. A sealed turn measured ~2,600px against a feed
   window of 234-364px, so this row is the only place the full path is ever
   on screen at once. State is the word in the value; the edge repeats it:
   Drift prevented takes the drift channel's gold all the way round (never a
   side stripe), Uncited and Off a dashed edge, Cited the accent. */
.turn-path-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-base); margin-bottom: var(--space-base); }
.turn-path ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: stretch; gap: var(--space-tight); }
.tp-step { display: flex; align-items: center; gap: var(--space-tight); }
.tp-step + .tp-step::before { content: "›" / ""; color: var(--text-faint); font-family: var(--font-display); font-size: var(--type-control); }
.tp-link {
    display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-hair);
    padding: var(--space-tight) var(--space-snug); background: transparent; color: var(--text-primary);
    border: 1px solid var(--surface-border-strong); border-radius: var(--radius); cursor: pointer; text-align: left;
    text-transform: none; letter-spacing: normal; font-weight: 400;
    transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.tp-link:hover:not(:disabled) { border-color: var(--control-border); background: var(--tint-objective); }
.tp-link:disabled { cursor: default; }
.tp-name { font-family: var(--font-display); font-size: var(--type-data); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--accent-color); line-height: var(--leading-label); }
.tp-val { font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-body); line-height: var(--leading-data); color: var(--text-primary); white-space: nowrap; }
.turn-path-row .entry-tools .btn-inline { white-space: nowrap; }
.tp-step[data-state="cited"] .tp-link { border-color: var(--accent-color); }
.tp-step[data-state="drift"] .tp-link { border-color: var(--loot-gold); box-shadow: inset 0 0 0 1px var(--loot-gold); background: var(--tint-gold); }
/* The label stays in the primary text colour, not the gold: gold as 11px text
   measured 3.85:1 on manila. The gold edge and the words carry the state. */
.tp-step[data-state="drift"] .tp-name { color: var(--text-primary); }
.tp-step[data-state="uncited"] .tp-link, .tp-step[data-state="off"] .tp-link,
.tp-step[data-state="empty"] .tp-link, .tp-step[data-state="default"] .tp-link { border-style: dashed; }
.tp-step[data-state="uncited"] .tp-val, .tp-step[data-state="default"] .tp-val { color: var(--danger-text); }
.tp-step[data-state="off"] .tp-val, .tp-step[data-state="empty"] .tp-val { color: var(--text-secondary); }
/* The belief moment's entry point, at the end of the path that leads to it.
   Inline rank, in the workhorse accent: it is the next thing to do. */
.tp-rerun { align-self: stretch; display: inline-flex; align-items: center; gap: var(--space-tight); color: var(--accent-color); border-color: var(--accent-color); }
.tp-rerun .ico { width: 14px; height: 14px; }

/* The packet box's label: plain words first, the structure's name after. */
.packet-label { font-family: var(--font-display); font-size: var(--type-data); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--terminal-fg); }
.packet-label .mono { font-family: var(--font-data); font-weight: 400; letter-spacing: var(--track-data); text-transform: none; }
/* ---- The six feed channels ----
   Every channel owns exactly one colour and no two may resolve to the same
   value in a theme. That was not true: `user` and `error` shared a stripe
   token in all three themes, and `learning` and `retrieval` shared stripe
   AND label in two of them — on Los Angeles the six channels rendered as
   three. The `[LABEL]` text was carrying the whole system on its own.
   The two red channels stay red on purpose: both are the consequence
   voice, and the Two Voices Rule assigns red to command. They are told
   apart STRUCTURALLY instead — `.error` is a closed box, `.user` is a
   left rule on an open one — which is a distinction that survives a
   greyscale projector and every form of colour-vision deficiency, neither
   of which a second red would. */
.log-entry.system { border-left-color: var(--accent-color); background: var(--tint-system); }
.log-entry.system > strong:first-child { color: var(--accent-color); }
.log-entry.user { border-left-color: var(--accent-danger); background: var(--tint-command); }
/* The label was --accent-danger, which measures 3.55:1 at 11px on this
   entry's own tinted ground. Command Red is the fill colour; the text red
   is --danger-text, and that is what the Three Reds Rule says to use. */
.log-entry.user > strong:first-child { color: var(--danger-text); }
/* Drift-prevented: the Semantic Linter caught an out-of-role order. Amber
   so it reads as a headline "we stopped something" moment, not log noise. */
.log-entry.drift { border-left-color: var(--loot-gold); background: var(--tint-gold); }
.log-entry.drift > strong:first-child, .log-entry.drift .claim { color: var(--loot-gold); }
/* Something failed and the operator has to act. This channel did not exist:
   a blocked ingestion, a dead CDN and a wiped knowledge base all rendered
   as ordinary olive [SYSTEM] chatter. Command Red stripe, red label, and
   the word FAILURE — the state survives a greyscale projector. */
.log-entry.error {
    border-color: var(--accent-danger); border-left-color: var(--accent-danger);
    background: var(--tint-command);
}
.log-entry.error > strong:first-child { color: var(--danger-text); }
/* Adaptive intelligence: the commander learning or applying this player's
   patterns. It used to borrow --oga-accent, which means "recovered asset
   provenance" everywhere else — one token, two meanings, and in light and
   Los Angeles it resolved to the same value as the retrieval channel, so
   [INTEL] and [RETRIEVAL] were the same colour. It has its own now, and
   --oga-accent is back to meaning one thing.
   strong:first-child, not strong: this channel and drift were colouring
   EVERY bolded word in the entry while the other four coloured only the
   label, so emphasis inside an intel entry silently became channel-coded. */
.log-entry.learning { border-left-color: var(--ch-learned); background: var(--tint-learned); }
.log-entry.learning > strong:first-child, .log-entry.learning .claim { color: var(--ch-learned); }
/* The engine is thinking and you can watch it happen — the one place in the
   console where a still frame would be a lie about what is going on. */
.working { display: flex; align-items: center; gap: var(--space-base); margin-top: var(--space-snug); }
/* 3px on a visible hairline track: at 2px against a soft gradient this
   washed out entirely on a projector, which is where this console is read. */
/* The track must read as an EMPTY channel. At --surface-border-strong it
   read as a filled bar, i.e. as a completed progress bar — the opposite of
   what an indeterminate indicator should say. */
.working-track { position: relative; flex: 1; height: 3px; border-radius: 2px; background: var(--surface-recessed); border: 1px solid var(--surface-border); overflow: hidden; }
.working-sweep {
    position: absolute; top: 0; bottom: 0; left: 0; width: 34%; border-radius: 2px;
    background: linear-gradient(90deg, transparent 0%, var(--accent-color) 40%, var(--accent-color) 60%, transparent 100%);
    /* linear, not --ease-out: this is the one loop in the system, and a
       decelerating curve makes a scanner appear to stall at the right edge
       every cycle. A constant rate is what "still working" actually looks
       like. Documented as the single exception in DESIGN.md. */
    animation: sweep 1.15s linear infinite;
}
/* Phosphor, like every other value the machine produced. */
.working-elapsed {
    flex: none; min-width: 46px; text-align: right; font-family: var(--font-data); font-size: var(--type-data);
    letter-spacing: var(--track-control); color: var(--terminal-text); font-variant-numeric: tabular-nums;
}
.log-entry.drift { animation: fadeIn var(--dur-med) var(--ease-out), driftLand 900ms var(--ease-out); }

/* The turn, witnessed (phase 10, src/ui/stages.js): Retrieval → Thinking →
   Writing orders → Committed. Each stage is a row with a glyph AND a state
   word — Waiting, Now, Done, Off, Failed — so the state survives greyscale,
   projection and every colour-vision deficiency; colour only repeats it.
   Nothing here moves: tokens arrive as text, the meter is a true count of
   thinking tokens against the budget, and the only animation is the existing
   scanner while the prompt is read, which freezes under reduced motion. */
.turn-stages .ts-headline { display: flex; align-items: baseline; gap: var(--space-snug); flex-wrap: wrap; }
/* While the turn runs the stage list holds at the top of the feed, so the
   reader keeps every stage in view while the reasoning and the orders stream
   in underneath it. It needs a solid ground to pass over the text scrolling
   beneath it; once the order is sealed it scrolls like any other entry. */
.turn-stages:not(.is-sealed) .ts-top {
    position: sticky; top: calc(-1 * var(--space-shell)); z-index: 1;   /* -1 × #action-log's padding: flush with the feed's top edge */
    margin: calc(-1 * var(--space-base)) calc(-1 * var(--space-base)) 0 calc(-1 * var(--space-shell));
    padding: var(--space-base) var(--space-base) var(--space-tight) var(--space-shell);
    background: var(--surface-color); border-bottom: 1px solid var(--surface-border);
}
.ts-head { font-family: var(--font-display); font-size: var(--type-control); font-weight: 600; letter-spacing: var(--track-control); text-transform: uppercase; color: var(--text-primary); }
.ts-elapsed { margin-left: auto; font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-control); color: var(--terminal-text); font-variant-numeric: tabular-nums; min-width: 52px; text-align: right; }
.ts-list { list-style: none; margin: var(--space-snug) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-tight); }
.ts-stage {
    display: grid; grid-template-columns: 1.25em minmax(7.5rem, max-content) 4.5rem 1fr; align-items: baseline; gap: var(--space-snug);
    padding: var(--space-tight) var(--space-snug); border: 1px solid var(--surface-border); border-radius: var(--radius); background: var(--surface-recessed-soft);
}
.ts-mark { font-family: var(--font-data); font-size: var(--type-control); text-align: center; color: var(--text-faint); }
.ts-name { font-family: var(--font-display); font-size: var(--type-data); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--text-secondary); }
.ts-state { font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-data); text-transform: uppercase; color: var(--text-secondary); }
.ts-detail { font-size: var(--type-caption); line-height: var(--leading-data); color: var(--text-secondary); overflow-wrap: anywhere; }
/* Now: the row stands out by weight and a strong edge drawn all the way round. */
.ts-stage[data-state="active"] { border-color: var(--surface-border-strong); background: var(--tint-objective); }
.ts-stage[data-state="active"] .ts-name, .ts-stage[data-state="active"] .ts-state { color: var(--text-primary); }
.ts-stage[data-state="active"] .ts-mark { color: var(--status-working); }
.ts-stage[data-state="done"] .ts-mark { color: var(--success-text); }
.ts-stage[data-state="done"] .ts-name { color: var(--text-primary); }
.ts-stage[data-state="skipped"] { border-style: dashed; background: transparent; }
.ts-stage[data-state="failed"] { border-color: var(--danger-border); }
.ts-stage[data-state="failed"] .ts-mark, .ts-stage[data-state="failed"] .ts-state { color: var(--danger-text); }
@media (max-width: 640px) {
    .ts-stage { grid-template-columns: 1.25em 1fr auto; }
    .ts-detail { grid-column: 2 / -1; }
}
.turn-stages .working { margin-top: var(--space-tight); }
/* .working sets display:flex, which would otherwise outrank the hidden attribute. */
.turn-stages .working[hidden] { display: none; }
.ts-working-note { flex: none; font-size: var(--type-caption); color: var(--text-secondary); }

/* Live reasoning: the model's own words, as they arrive. Body face — this is
   prose, not a machine value (The Mono Is Evidence Rule) — capped in height,
   scrollable, and pinned to the newest line unless the reader scrolls up. */
.ts-reasoning { margin-top: var(--space-snug); max-width: 85ch; }
.ts-reasoning > summary { cursor: pointer; padding: var(--space-tight) 0; line-height: var(--leading-data); }
.ts-reasoning-title { font-family: var(--font-display); font-size: var(--type-data); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--accent-color); }
.ts-plain { font-size: var(--type-caption); color: var(--text-secondary); }
/* A replayed order's stages carry the recording's label (phase 12). */
.ts-replay-note { margin: var(--space-tight) 0 0; font-size: var(--type-caption); line-height: var(--leading-data); color: var(--text-secondary); max-width: 74ch; }
.ts-replay-note b { color: var(--text-primary); font-weight: 600; }
.live-tag.is-replay, .live-tag.is-not-live { color: var(--text-primary); }
.live-tag.is-replay::before, .live-tag.is-not-live::before { background: transparent; box-shadow: inset 0 0 0 2px var(--status-working); }
.ts-reasoning > summary:hover .ts-reasoning-title { color: var(--text-primary); }
.ts-meter { position: relative; height: 3px; border-radius: 2px; background: var(--surface-recessed); border: 1px solid var(--surface-border); overflow: hidden; margin-top: var(--space-tight); }
.ts-meter > i { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: var(--accent-color); transition: width var(--dur-fast) var(--ease-out); }
.ts-meter-text { margin: var(--space-tight) 0 0; font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-data); color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.ts-think {
    margin-top: var(--space-tight); max-height: 10.5em; overflow-y: auto; white-space: pre-wrap; overflow-wrap: break-word;
    padding: var(--space-snug) var(--space-base); border: 1px solid var(--surface-border); border-radius: var(--radius);
    background: var(--surface-recessed-soft); color: var(--text-secondary); font-size: var(--type-control); line-height: var(--leading-body);
}
.ts-think:empty::before { content: "Waiting for the first words…"; color: var(--text-faint); }

/* The orders as the model writes them. A row's state is its word — writing…,
   written — beside the value; the value is exactly what the model wrote. */
.ts-fields { margin-top: var(--space-snug); }
.ts-fields-note { margin: 0 0 var(--space-tight); font-size: var(--type-caption); color: var(--text-secondary); }
.ts-field-list { margin: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-hair) var(--space-base); }
/* Two columns where there is room: the short machine values pair up, the
   prose (what it saw, why, the radio lines) keeps the full width. */
@media (min-width: 1100px) {
    .ts-field-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ts-field.is-wide { grid-column: 1 / -1; }
}
.ts-field { display: grid; grid-template-columns: 7.5rem 1fr 4.5rem; gap: var(--space-snug); align-items: baseline; padding: var(--space-hair) var(--space-tight); border: 1px solid transparent; border-radius: var(--radius); }
.ts-field dt { font-family: var(--font-display); font-size: var(--type-data); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--text-secondary); }
.ts-field dd { margin: 0; min-width: 0; overflow-wrap: anywhere; color: var(--text-primary); }
.ts-value { font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-data); }
.ts-prose { font-size: var(--type-control); line-height: var(--leading-data); }
.ts-fstate { font-family: var(--font-data); font-size: var(--type-data); letter-spacing: var(--track-data); color: var(--text-secondary); text-align: right; }
.ts-field[data-state="waiting"] dt { color: var(--text-faint); }
.ts-field[data-state="writing"] { border-color: var(--surface-border-strong); border-style: dashed; }
.ts-field[data-state="writing"] .ts-fstate { color: var(--text-primary); }
@media (max-width: 640px) {
    .ts-field { grid-template-columns: 1fr auto; }
    .ts-field dd { grid-column: 1 / -1; }
}

/* The safety rules the commit ran, each with its verdict in words. A rule
   that fired gets the accent edge all the way round, never a side stripe. */
.ts-rules { margin-top: var(--space-snug); max-width: 85ch; font-size: var(--type-caption); line-height: var(--leading-data); }
.ts-rules > summary { cursor: pointer; font-family: var(--font-display); font-size: var(--type-data); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--text-secondary); padding: var(--space-tight) 0; }
.ts-rules > summary:hover { color: var(--text-primary); }
.ts-rules ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-tight); }
.ts-rules li { padding: var(--space-tight) var(--space-snug); border: 1px solid var(--surface-border); border-radius: var(--radius); color: var(--text-secondary); }
.ts-rules li[data-fired="yes"] { border-color: var(--loot-gold); box-shadow: inset 0 0 0 1px var(--loot-gold); background: var(--tint-gold); color: var(--text-primary); }
.ts-rule-id { font-family: var(--font-data); font-weight: 600; color: var(--text-primary); }
.ts-rule-verdict { font-family: var(--font-data); letter-spacing: var(--track-data); text-transform: uppercase; color: var(--text-primary); }
.ts-rule-why { color: var(--text-secondary); }

/* The radio traffic, printed on the phosphor slab. It used to arrive in two
   more generations after the order; since phase 10 it is part of the order's
   own reply, so it is there the moment the order is. */
.radio-log {
    margin-top: var(--space-base); padding: var(--space-base); background: var(--terminal-bg); border: 1px solid var(--terminal-fg);
    border-radius: var(--radius); color: var(--terminal-fg); font-family: var(--font-data);
}
.radio-missing { font-family: var(--font-body); font-style: italic; }
/* Phase 13: each line its own paragraph, the static markup turned into the
   quote it stood for, and, when the engine changed the order, a note over
   the lines saying they are the model's words from before the checks. */
.radio-log p { margin: 0; }
.radio-log p + p { margin-top: var(--space-snug); }
.radio-line strong { display: block; font-family: var(--font-display); font-size: var(--type-data); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; }
.radio-line q { quotes: "“" "”"; }
.radio-note { font-family: var(--font-body); font-size: var(--type-caption); font-style: italic; color: var(--text-primary); }

/* Retrieval: which doctrine the engine actually pulled, before it answers.
   This is the positioning claim, so it gets its own channel rather than
   being folded into the payload entry. */
.log-entry.retrieval { border-left-color: var(--status-info); background: var(--tint-info); }
.log-entry.retrieval > strong:first-child, .log-entry.retrieval .claim { color: var(--status-info); }
.log-entry.retrieval b { color: var(--text-primary); font-weight: 600; }
.retrieval-list { display: flex; flex-direction: column; gap: var(--space-micro); margin-top: var(--space-snug); }
/* Each row is a real button: the passage is the evidence, and reaching its
   source must not require a mouse. */
.retrieval-row {
    display: flex; align-items: flex-start; gap: var(--space-base); width: 100%; text-align: left;
    font-family: var(--font-body); font-size: var(--type-control); font-weight: 400;
    letter-spacing: normal; text-transform: none;
    padding: var(--space-snug) var(--space-snug); cursor: pointer;
    background: var(--surface-recessed-soft); color: var(--text-primary);
    border: 1px solid var(--surface-border); border-radius: var(--radius);
    transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.retrieval-row:hover { border-color: var(--status-info); background: var(--tint-info); }
.retrieval-score {
    flex: none; min-width: 52px; font-family: var(--font-data); font-size: var(--type-claim); font-weight: 600; line-height: var(--leading-claim);
    color: var(--status-info); letter-spacing: var(--track-body); font-variant-numeric: tabular-nums;
}
.retrieval-score i { font-style: normal; font-size: var(--type-data); opacity: 0.7; }
.retrieval-body { display: flex; flex-direction: column; flex: 1; gap: var(--space-hair); min-width: 0; }
/* `anywhere` vs `break-word` is a content distinction, and it had been made
   backwards. `anywhere` permits a break at ANY character even when the word
   would fit on the next line, and it lets that opportunity shrink the
   element's min-content width. That is right for an unbreakable token — a
   source id like soviet_defensive_doctrine has to be allowed to split, and
   these three rules are the id columns. It is wrong for prose: it let
   "reconnaissance" split mid-word in the commander's reasoning, which is the
   one block a non-technical viewer is meant to read. Those are `break-word`
   now, which breaks only a word that cannot fit a line by itself.
   No `hyphens: auto` to go with it: hyphenation applies at syllable points
   during normal wrapping and does NOT put a hyphen on an emergency
   overflow-wrap break, so it would restyle every paragraph without
   addressing the case it appears to address. */
.retrieval-src { font-size: var(--type-data); letter-spacing: var(--track-tab); color: var(--text-secondary); text-transform: uppercase; overflow-wrap: anywhere; }
.retrieval-text { font-size: var(--type-control); color: var(--text-primary); line-height: var(--leading-data); overflow-wrap: break-word; max-width: 72ch; }
/* Each row names its passage (the ID the commander is handed and cites) and,
   once the commander has answered, says what became of it. The words are the
   state; the border repeats it: a cited passage has a solid heavy edge, one that
   was handed over and not cited a dashed one. Colour only repeats both. */
.retrieval-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-hair) var(--space-snug); }
.retrieval-id {
    font-size: var(--type-data); font-weight: 600; letter-spacing: var(--track-data); color: var(--text-primary);
    border: 1px solid var(--control-border); border-radius: var(--radius); padding: 0 var(--space-tight);
}
.retrieval-use { margin-left: auto; font-size: var(--type-data); letter-spacing: var(--track-tab); text-transform: uppercase; color: var(--text-secondary); }
.retrieval-row.is-cited { border-color: var(--accent-color); box-shadow: inset 0 0 0 1px var(--accent-color); background: var(--tint-objective); }
.retrieval-row.is-cited .retrieval-use { color: var(--text-primary); font-weight: 600; }
/* The olive tint under a cited row is lighter than the blue score was set against: 3.9:1 on Field Briefing. */
.retrieval-row.is-cited .retrieval-score { color: var(--text-primary); }
.retrieval-row.is-unused, .retrieval-row.is-cut { border-style: dashed; border-color: var(--surface-border-strong); }
.retrieval-row.is-unused .retrieval-text, .retrieval-row.is-cut .retrieval-text { color: var(--text-secondary); }
.retrieval-tally { display: block; margin-top: var(--space-snug); font-size: var(--type-data); letter-spacing: var(--track-tab); color: var(--text-secondary); }
.retrieval-tally:empty { display: none; }

/* The order entry's own controls: Re-run and Copy log, kept together and wrapping
   rather than pushing the claim out of its row on a narrow feed. */
.entry-tools { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-micro); }

/* ---- Rail: the commander's face and the folded override (phase 13) ---- */
.persona-row { display: flex; align-items: center; gap: var(--space-snug); }
.persona-row select { flex: 1 1 auto; min-width: 0; }
.persona-portrait .plate { width: 56px; height: 56px; }
.mem-override > summary { cursor: pointer; font-family: var(--font-display); font-size: var(--type-data); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--text-secondary); padding: var(--space-tight) 0; }
.mem-override > summary:hover { color: var(--text-primary); }
.mem-override[open] > summary { margin-bottom: var(--space-snug); }
/* The ready card's preview of the turn path: the same steps an order will
   show, as words, not links (nothing has run yet), on a dashed edge. */
.tp-preview { list-style: none; margin: var(--space-snug) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-tight); }
.tp-preview .tp-link { cursor: default; border-style: dashed; }
.tp-preview .tp-val { font-family: var(--font-body); font-size: var(--type-caption); letter-spacing: normal; color: var(--text-secondary); }
