/* STARNET — app.css : screens for the REAL harness (title · connect · game).
   Reuses the v7 phosphor/CRT design language from style.css (vars + body::before/after).
   No "web app" softness — 2px hard lines, inverted-block headers, heavy bloom. */

/* ---------- screen scaffolding ---------- */
.screen { position: fixed; inset: 0; display: none; z-index: 10; }
.screen.active { display: flex; }

/* boot veil — the neutral holding-card shown during init()'s async gap before the boot router picks a
   real screen. Solid ink so there's never a flash of the wrong screen (the old #screen-connect .active
   bug); reuses the phosphor vars so it matches whatever theme is about to load. */
#screen-boot { background: var(--ink); align-items: center; justify-content: center; }
.boot-veil { text-align: center; opacity: .82; animation: boot-fade 2.4s ease-in-out infinite; }
.boot-mark { color: var(--ph-bright); font-size: 30px; letter-spacing: 8px; text-shadow: 0 0 14px var(--ph-glow), 0 0 3px var(--ph-glow); }
.boot-line { color: var(--ph-dim); font-size: 14px; letter-spacing: 3px; text-transform: uppercase; margin-top: 12px; }
.boot-dots::after { content: '…'; animation: cc-blink 1.1s steps(1) infinite; }
@keyframes boot-fade { 0%, 100% { opacity: .62; } 50% { opacity: .9; } }
@media (prefers-reduced-motion: reduce) { .boot-veil { animation: none; } .boot-dots::after { animation: none; } }
#screen-connect.active { align-items: center; justify-content: center; }
/* THE CABINET IS HARDWARE — TEXT SIZE is a type dial, not a magnifying glass.
   body carries the TEXT SIZE zoom (stationui.js applySettings), and it multiplies into every
   length below, so the whole frame used to swell with the text: at HUGE the crew rail went
   232→336px and COMMS 360→522px, and the station view — the thing you are actually watching —
   was squeezed 1279→995px (-29% area). --sn-unzoom is the exact reciprocal, so every CABINET
   dimension here resolves back to the size it was designed at while the type inside still grows.
   `1fr` then hands the reclaimed space to the stage automatically. Unset (the 100% default)
   resolves to 1, i.e. these are the shipped numbers untouched.
   --chat-w (chatresize.js, the draggable COMMS seam) and --crew-w (leftrail.js, the CREW seam) are
   written in VISUAL px for the same reason, so a dragged width means the same thing at every text size. */
#screen-game.active {
  display: grid;
  grid-template-rows: calc(64px * var(--cz)) 1fr calc(44px * var(--cz));
  /* both side columns are live vars so the user can drag either divider (leftrail.js / chatresize.js) */
  grid-template-columns: calc(var(--crew-w, 232px) * var(--cz)) 1fr calc(var(--chat-w, 360px) * var(--cz));
  grid-template-areas: "top top top" "left center chat" "bot bot bot";
  padding: calc(11px * var(--cz)); gap: calc(9px * var(--cz));
}
/* The cabinet BLOCKS whose whole content is hardware — the instrument bar, the dock, and the two
   molded nameplates with their engraved labels. Their boxes are now fixed by the grid above, so
   their contents have to come back to 1:1 too or they overflow a box that no longer grew with
   them. One declaration each resets a whole subtree, which is why this is a `zoom` and not a
   per-property counter-scale. #logo is a <body> child (hoisted over the CRT glass) rather than a
   topbar descendant, so it needs its own — app.js positionLogo() measures the effective ratio off
   the element, so it follows this either way.
   `.ws-head` (SESSIONS / PROJECTS / + NEW) is here too, and that was measured rather than assumed:
   it is a control strip, so the first pass let it scale with the type — and in a rail that no
   longer widens, 45% bigger tabs ran straight off the edge and clipped "+ NEW". A view switcher
   that loses a button is worse than one that stays the size it was drawn at, so it is furniture.
   `--cz: 1` because these subtrees are already at 1:1: it keeps `calc(<designed>px * var(--cz))`
   (style.css `body`) correct on BOTH sides of the counter-zoom line, so shared rules like the
   panel bezel radius need no fork. */
#topbar, #bottombar, #logo,
#left > h3, #chat-panel > h3, .ws-head { zoom: var(--sn-unzoom, 1); --cz: 1; }
.hidden { display: none !important; }

/* visually-hidden but exposed to assistive tech (screen-reader-only live regions / labels) */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

.dim { color: var(--ph-dim); }
.dimb { color: var(--ph-dim); font-weight: normal; }
.pos { color: var(--gold); }
.muted { color: var(--text); opacity: 0.82; }

/* ---------- shared inputs / buttons ---------- */
.btn, .btn-xl, .btn-sm, .prov {
  font-family: inherit; color: var(--ph);
  background: linear-gradient(180deg, color-mix(in srgb, var(--ph) 8%, var(--panel2)), var(--panel2));
  border: 1px solid var(--ph-dim); border-radius: 5px; padding: 7px 15px; cursor: pointer;
  text-transform: uppercase; letter-spacing: 1px; line-height: 1;
  text-shadow: 0 0 6px var(--ph-glow2);
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb), .12), 0 1px 0 rgba(0,0,0,.4);
  transition: color .12s, border-color .12s, background-color .12s, box-shadow .12s, transform .12s;
}
.btn:hover, .btn-xl:hover, .btn-sm:hover, .prov:hover {
  border-color: var(--ph); background: var(--ph-faint); color: var(--ph-bright);
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb), .18), 0 0 10px var(--ph-glow2), 0 1px 0 rgba(0,0,0,.4);
}
.btn:active, .btn-xl:active, .btn-sm:active, .prov:active { transform: translateY(1px); box-shadow: inset 0 1px 3px rgba(0,0,0,.35); }
.btn:focus-visible, .btn-xl:focus-visible, .btn-sm:focus-visible, .prov:focus-visible { outline: 2px solid var(--ph-bright); outline-offset: 2px; }
.btn-xl { font-size: 26px; padding: 13px 27px; border-color: var(--ph); box-shadow: inset 0 1px 0 rgba(var(--ph-rgb), .16), 0 0 0 2px rgba(0,0,0,.85), 0 0 18px var(--ph-glow2); }
.btn-sm { font-size: 15px; padding: 5px 10px; }
.prov.sel {
  background: linear-gradient(180deg, var(--ph-bright) -40%, var(--ph) 60%); color: var(--ink); border-color: var(--ph);
  text-shadow: none; box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 0 10px var(--ph-glow2);
}

/* ---------- COMPACT CONSOLE KEYS — .bb.sm / .bb.xs ----------
   The ~105 in-window action buttons (ADD KEY · SIGN IN · REVOKE · RUN NOW · REFRESH …). They
   were the one control family that never got the material pass: flat hollow rectangles with hard
   90° corners, sitting inside cards that are rounded and raised, and hanging off the surrounding
   text's BASELINE. Reported 2026-08-03 (Andrew, SETTINGS ▸ PROVIDERS: "ugly and not even centered
   correctly"). Both halves were measured live before this rule existed, not guessed:

   1. GEOMETRY — every provider row read the button's centre 2.50px above its status text's centre.
      The cause is baseline layout, and it cannot be nudged away with a margin: EVERY label in this
      family mixes VT323 with a symbol glyph VT323 does not contain (＋ ⏼ ✕ ▸ ↻ ⌫ all fall back to
      a system font — measured at 2.0–3.2× VT323's monospace advance), so the label carries two
      different baselines before it even meets the text beside it. inline-flex + centre +
      line-height:1 centres the label on the BOX instead, which is level by construction.
   2. MATERIAL — the molded keycap the rest of the console already wears (.btn above, .bb-grp in
      panelchrome.css): top light, phosphor body, shaded base, contact shadow, press sink. Matte,
      never glossy. Only the surface and the radius change; the compact metrics stay compact so no
      caller reflows.

   Deliberately NOT the CONTROL FLOOR (see the block near line 100): that floor is element-level and
   must never carry box metrics, radius or shadow. This is a real surface class, which is exactly
   where material belongs. Per-surface skins still override (.mkt .bb.sm keeps the bay's tighter
   metrics), and the canonical danger-armed rule still wins on !important. */
.bb.sm, .bb.xs {
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1; vertical-align: middle; border-radius: 4px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ph) 6%, var(--panel2)) 0%,
    var(--panel2) 58%,
    color-mix(in srgb, #000 20%, var(--panel2)) 100%);
  box-shadow:
    inset 0 1px 0 rgba(var(--ph-rgb), .08),
    inset 0 -2px 0 rgba(0,0,0,.28),
    0 1px 2px rgba(0,0,0,.45);
}
/* 5px, not the 3px this rule replaced, and not the 7px a first pass guessed at. Measured: the label's
   line box is 17.05px at this font size (line-height:1 sets a 13px STRUT, but VT323's own ascent+descent
   still win the line box, so line-height alone shrinks nothing). 17.05 + 10 + 2px border = 29.05px —
   within a fifth of a pixel of the 28.88px key this replaced, so all ~105 call sites keep their rhythm. */
.bb.sm { padding: 5px 12px; }
.bb.sm:hover, .bb.xs:hover {
  color: var(--ph-bright); border-color: var(--ph);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ph) 11%, var(--panel2)) 0%,
    var(--panel2) 62%,
    color-mix(in srgb, #000 16%, var(--panel2)) 100%);
  box-shadow:
    inset 0 1px 0 rgba(var(--ph-rgb), .12),
    inset 0 -2px 0 rgba(0,0,0,.28),
    0 1px 2px rgba(0,0,0,.45),
    0 0 10px var(--ph-glow2);
}
/* pressed = the key is DOWN: the seat sinks and the base light goes out (same verb as .bb-grp) */
.bb.sm:active, .bb.xs:active { transform: translateY(1px); box-shadow: inset 0 2px 4px rgba(0,0,0,.42); }

input[type=text], input[type=password], input[type=url], input[type=search], input:not([type]), textarea.key-input {
  width: 100%; font-family: inherit; font-size: 19px; color: var(--ph-bright);
  background: linear-gradient(180deg, #000, color-mix(in srgb, var(--ph) 3%, #000));
  border: 1px solid var(--ph-dim); border-radius: 4px; padding: 8px 11px; outline: none;
  text-shadow: 0 0 5px var(--ph-glow2);
  box-shadow: inset 0 2px 6px rgba(0,0,0,.7), inset 0 -1px 0 rgba(var(--ph-rgb),.06);
  transition: border-color .12s, box-shadow .12s;
}
input:focus, textarea.key-input:focus {
  border-color: var(--ph);
  box-shadow: inset 0 2px 6px rgba(0,0,0,.7), inset 0 -1px 0 rgba(var(--ph-rgb),.08), 0 0 12px var(--ph-glow2);
}
/* solid --ph-dim (no opacity multiplier) keeps placeholder hint text >=4.5:1 (WCAG AA) on --bg across every theme; .7 opacity dropped it to ~3.2:1 */
input::placeholder, textarea.key-input::placeholder { color: var(--ph-dim); opacity: 1; }

/* ---------- CONTROL FLOOR — NO control may ever paint itself with OS chrome ----------
   LAW (2026-07-27, Andrew): every control in this product is station hardware. The browser's
   own paint — buttonface white, ButtonBorder grey, black system text — is a BUG here, every
   single time it surfaces, and it has surfaced repeatedly (the SURFACE/REFIT bare <button>,
   the CHANNELS "RUNS AS" select, the DELIVERABLES status filter). The cause was always the
   same: the sheet skinned controls per surface (.btn, .bb, .mkt-in, .fbc-sel, .mp-model …)
   and a new control that forgot its class fell through to the user agent.

   These rules are the FLOOR, not a skin. Every selector below is element-level — specificity
   (0,0,1)/(0,0,2) — so EVERY existing class or id skin still outranks it and nothing already
   themed changes appearance. All it does is replace OS paint with phosphor paint wherever the
   surface sheets forgot. Measured live, floor-off vs floor-on over the same DOM (1100 controls,
   all 17 windows open, every <details> expanded): 210 computed-style deltas, and exactly TWO
   elements changed bounding box. The deltas are 2 white selects becoming phosphor fields
   (#tg-bot-agent, #dl-status), 5 OS-blue accents becoming phosphor, 200 buttons leaving Arial
   for the station font stack — and the rest are no-ops on invisible surfaces (border-colour on
   zero-width borders, text colour on empty icon buttons, radius on borderless elements).

   Do not "clean this up" by deleting it and re-skinning per surface — that is the exact
   pattern that keeps letting white HTML controls ship. Do not raise its specificity either:
   an earlier draft that also set border-radius/box-shadow here leaked onto id-styled,
   class-less buttons (#ws-new, #estop-btn). A floor may only replace what the UA would paint.
   Locked by test/control-floor-theming.test.js. */
select {
  font-family: inherit; font-size: 15px; color: var(--ph-bright); cursor: pointer;
  background-color: #000;
  border: 1px solid var(--ph-dim); border-radius: 4px; padding: 6px 9px; outline: none;
  text-shadow: 0 0 5px var(--ph-glow2);
  box-shadow: inset 0 2px 6px rgba(0,0,0,.7);
  transition: border-color .12s, box-shadow .12s;
}
select:focus { border-color: var(--ph); box-shadow: inset 0 2px 6px rgba(0,0,0,.7), 0 0 12px var(--ph-glow2); }
/* a bare <textarea> is a field, same as a bare <input> (the rule above only covers .key-input) */
textarea {
  font-family: inherit; color: var(--ph-bright);
  background-color: #000; border: 1px solid var(--ph-dim); border-radius: 4px; padding: 8px 11px; outline: none;
}
textarea::placeholder { color: var(--ph-dim); opacity: 1; }
/* buttons: neutralize the OS paint and NOTHING else. No box metrics, no radius, no shadow —
   measured the hard way: a draft of this block that also set border-radius/box-shadow leaked
   onto id-styled, class-less buttons (#ws-new, #estop-btn) that declare no radius of their
   own. A floor may only replace what the user agent would have painted. */
button, input[type=button], input[type=submit], input[type=reset] {
  font-family: inherit; color: var(--ph); cursor: pointer;
  background-color: transparent;
  border: 1px solid var(--ph-dim);
}
button:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid var(--ph-bright); outline-offset: 2px; }
/* the OS ticks/knobs: phosphor, never the platform's blue */
input[type=checkbox], input[type=radio], input[type=range], progress { accent-color: var(--ph); }
/* the typed-field rule above is keyed on [type], so checkbox/range/file kept the UA's Arial.
   Harmless where nothing renders text, wrong the moment a file input is ever shown. */
input { font-family: inherit; }
/* the native file picker's own button is a control too — it paints buttonface until told otherwise */
input[type=file]::file-selector-button {
  font-family: inherit; color: var(--ph); cursor: pointer;
  background-color: transparent; border: 1px solid var(--ph-dim); border-radius: 5px;
  padding: 4px 9px; margin-right: 9px;
}
input[type=file]::file-selector-button:hover { border-color: var(--ph); background-color: var(--ph-faint); color: var(--ph-bright); }

/* STATION TOOLTIP — the phosphor hover card that replaces the browser's native `title` bubble
   (tooltip.js). Same molded-card language as .model-dock-tip, which had this right in one place;
   this generalizes it to every [title] in the app. position:fixed + body-child = its style px are
   ZOOMED px (uiZoom law) — tooltip.js divides by z, this sheet must not re-scale anything. */
.station-tip {
  position: fixed; z-index: 200; pointer-events: none;
  width: max-content; max-width: 280px;
  padding: 7px 10px; text-align: left;
  font-size: 12px; line-height: 1.4; white-space: normal; overflow-wrap: anywhere;
  color: var(--ph); background: var(--panel);
  border: 2px solid var(--ph); border-radius: 4px;
  box-shadow: 0 0 0 2px var(--ink), 0 0 18px var(--ph-glow2);
  opacity: 0; transform: translateY(3px);
  transition: opacity .12s var(--ease-out, ease-out), transform .12s var(--ease-out, ease-out);
}
.station-tip[hidden] { display: none; }
.station-tip.show { opacity: 1; transform: translateY(0); }
/* the arrow tracks the anchor's centre (--tip-arrow, set by tooltip.js after clamping) */
.station-tip::after {
  content: ''; position: absolute; left: var(--tip-arrow, 50%); margin-left: -6px;
  top: 100%; border: 6px solid transparent; border-top-color: var(--ph);
}
.station-tip.below::after { top: auto; bottom: 100%; border-top-color: transparent; border-bottom-color: var(--ph); }
@media (prefers-reduced-motion: reduce) {
  .station-tip { transition: none; transform: none; }
  .station-tip.show { transform: none; }
}

/* (The standalone TITLE screen was removed — boot auto-resumes into the station, or opens CREATE/RESUME
   directly. The data-status line that used to live there now sits in the connect-screen footer.) */
.title-status { color: var(--ph-dim); font-size: 14px; letter-spacing: 1px; }

/* ---------- FIRST-BOOT SPLASH — the key-art boot card (first run only) ----------
   Layers, back to front: star canvas · nebula haze · perspective grid floor · HUD brackets ·
   the wordmark + PRESS ANY KEY. Everything rides the theme phosphor vars so a re-tint later
   still matches; default amber IS the key art. */
#screen-splash { background: #050302; overflow: hidden; cursor: pointer; }
#screen-splash.active { display: block; }
#sp-stars { position: absolute; inset: 0; width: 100%; height: 100%; }
.sp-nebula {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 42% 30% at 72% 22%, rgba(var(--ph-rgb),.10), transparent 70%),
    radial-gradient(ellipse 34% 26% at 14% 78%, rgba(var(--ph-rgb),.07), transparent 70%),
    radial-gradient(ellipse 60% 42% at 50% 52%, rgba(var(--ph-rgb),.06), transparent 72%);
}
/* the grid floor: a plane of phosphor graph lines pitched away from the camera, faded into the horizon */
.sp-floor {
  position: absolute; left: -18%; right: -18%; bottom: 0; height: 46vh;
  perspective: 340px; perspective-origin: 50% 0%; pointer-events: none;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 34%, #000 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 34%, #000 100%);
}
.sp-grid {
  position: absolute; inset: 0; transform: rotateX(62deg); transform-origin: 50% 0%;
  background:
    repeating-linear-gradient(90deg, rgba(var(--ph-rgb),.30) 0 1px, transparent 1px 64px),
    repeating-linear-gradient(0deg, rgba(var(--ph-rgb),.30) 0 1px, transparent 1px 56px);
  box-shadow: inset 0 0 120px rgba(0,0,0,.75);
  animation: sp-scroll 9s linear infinite;
}
@keyframes sp-scroll { from { background-position: 0 0, 0 0; } to { background-position: 0 0, 0 56px; } }
@media (prefers-reduced-motion: reduce) { .sp-grid { animation: none; } }
.sp-tl { top: 10px; left: 14px; } .sp-tr { top: 10px; right: 16px; }
.sp-bl { bottom: 10px; left: 14px; } .sp-br { bottom: 10px; right: 16px; }
.sp-center {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 10px; padding-bottom: 8vh; pointer-events: none;
}
.sp-logo {
  width: min(58vw, 640px); height: auto; image-rendering: auto; user-select: none;
  filter: drop-shadow(0 0 18px var(--ph-glow)) drop-shadow(0 0 60px var(--ph-glow2));
  animation: sp-breathe 4.2s ease-in-out infinite;
}
@keyframes sp-breathe {
  0%, 100% { filter: drop-shadow(0 0 18px var(--ph-glow)) drop-shadow(0 0 60px var(--ph-glow2)); }
  50%      { filter: drop-shadow(0 0 26px var(--ph-glow)) drop-shadow(0 0 84px var(--ph-glow2)); }
}
.sp-tag { color: var(--ph-dim); letter-spacing: 7px; font-size: 15px; text-transform: uppercase; text-shadow: 0 0 8px var(--ph-glow2); }
.sp-press {
  margin-top: 9vh; pointer-events: auto; cursor: pointer;
  font-family: inherit; font-size: 24px; letter-spacing: 4px; text-transform: uppercase;
  color: var(--ph-bright); background: none; border: none; padding: 8px 18px;
  text-shadow: 0 0 12px var(--ph-glow), 0 0 3px var(--ph-glow);
  animation: sp-blink 1.5s steps(1) infinite;
}
.sp-press:focus-visible { outline: 2px solid var(--ph); outline-offset: 4px; }
@keyframes sp-blink { 0%, 62% { opacity: 1; } 62.01%, 100% { opacity: .18; } }
.sp-stamp {
  position: absolute; left: 0; right: 0; bottom: 14px; display: flex; justify-content: center;
  gap: 18px; font-size: 13px; letter-spacing: 2px; color: var(--ph-dim); pointer-events: none;
}
.sp-rec { color: var(--ok); animation: cc-blink 1.4s steps(1) infinite; }   /* a live-local heartbeat, not a fake "REC" */

/* ---------- generic panel (reuses v7 bezel look) ---------- */
.panel {
  position: relative; background:
    radial-gradient(ellipse 130% 100% at 50% 0%, var(--ph-glow2), transparent 42%), var(--panel);
  border: 1px solid rgba(0,0,0,.85); border-radius: var(--r-lg);
  box-shadow: var(--bezel);
  padding: 20px 22px;
}
.panel h2 { color: var(--ink); background: var(--ph); display: inline-block; padding: 2px 12px; margin-bottom: 12px; letter-spacing: 2px; }

/* ---------- CONNECT / COMMISSION CONSOLE ----------
   Desktop-first: a wide terminal that lays BRAIN+IDENTITY beside VOICE instead of one tall scroll.
   Heavier chrome (corner brackets, an ASCII titlebar, a faux boot stamp) so it reads as a premium
   piece of station hardware, not a web form. Collapses to one column under 860px. */
.connect-panel {
  width: min(1060px, 95%); padding: 0;             /* % of `.screen` (fixed inset:0) — a bare 95vw is not a cap under the TEXT SIZE zoom */
  position: relative; overflow: hidden;            /* clip the corner brackets + scanline to the bezel */
}
.connect-panel label { display: block; color: var(--ph); letter-spacing: 1px; margin: 8px 0 3px; font-size: 15px; }
.provider-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.provider-row .prov { flex: 0 1 auto; min-width: 0; padding-inline: 10px; }

/* ===== two-tier genesis provider pick: ChatGPT hero card up top, BYOK chips in a compact grid below.
   (declared AFTER .prov / .prov.sel so equal-specificity ties resolve to these on the hero + grid) ===== */
.ov-prov { flex-direction: column; align-items: stretch; gap: 9px; }
.ov-prov .prov-hero {
  display: block; width: 100%; text-align: left; padding: 12px 15px 13px;
  border: 1px solid var(--ph-dim); border-radius: 7px; text-transform: none; letter-spacing: normal;
  background: linear-gradient(180deg, rgba(var(--ph-rgb),.10), rgba(var(--ph-rgb),.03)); line-height: 1.2;
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.12), 0 1px 0 rgba(0,0,0,.4);
}
.ov-prov .prov-hero:hover { border-color: var(--ph); color: var(--ph); background: linear-gradient(180deg, rgba(var(--ph-rgb),.16), rgba(var(--ph-rgb),.05)); }
.ov-prov .prov-hero.sel {
  border-color: var(--ph); color: var(--ph); text-shadow: 0 0 6px var(--ph-glow2);
  background: linear-gradient(180deg, rgba(var(--ph-rgb),.18), rgba(var(--ph-rgb),.06));
  box-shadow: inset 0 0 0 1px var(--ph), 0 0 20px var(--ph-glow2);
}
.prov-hero-badge { display: block; color: var(--gold); font-size: 11px; letter-spacing: 2px; margin-bottom: 7px; }
.prov-hero-row { display: flex; align-items: center; gap: 12px; }
.prov-hero-ic { flex: 0 0 auto; font-size: 27px; line-height: 1; color: var(--ph-bright); text-shadow: 0 0 12px var(--ph-glow); }
.prov-hero-body { min-width: 0; }
.prov-hero-title { display: block; color: var(--ph-bright); font-size: 18px; letter-spacing: 1.5px; }
.prov-hero-sub { display: block; color: var(--ph-dim); font-size: 12px; letter-spacing: .2px; line-height: 1.25; margin-top: 3px; text-shadow: none; }
/* (the grok/kimi mini-hero cards are gone — they're standard .prov-grid chips now; picking one
   swaps in the shared sign-in block, so the only card up top is the ChatGPT hero.) */
.prov-byok-lab { text-align: center; color: var(--ph-dim); font-size: 11px; letter-spacing: 2px; opacity: .8; margin: 0; }
.prov-grid { display: flex; flex-wrap: wrap; gap: 7px; }
.prov-grid .prov { flex: 0 1 auto; min-width: 0; font-size: 13px; padding: 5px 11px; }
/* the free-local tag on the OLLAMA chip: a quiet gold caption inside the chip, never a second control */
.prov-tag { display: inline-block; margin-left: 6px; color: var(--gold); font-size: 9px; letter-spacing: 1.5px; vertical-align: 1px; opacity: .92; }

/* corner brackets — the in-game cam-HUD framing, reused so the create screen feels like the same machine */
.cc-bracket { position: absolute; font-size: 22px; color: var(--ph); opacity: .5; z-index: 4; pointer-events: none; text-shadow: 0 0 8px var(--ph-glow); }
.cc-tl { top: 4px; left: 8px; } .cc-tr { top: 4px; right: 10px; } .cc-bl { bottom: 4px; left: 8px; } .cc-br { bottom: 4px; right: 10px; }

/* ===== ASCII console header ===== */
.cc-console-head { padding: 12px 22px 0; }
.cc-titlebar {
  display: flex; align-items: center; gap: 10px; font-size: 14px; letter-spacing: 2px;
  color: var(--ph-dim); text-transform: uppercase;
}
.cc-tb-tag { color: var(--ink); background: var(--ph); padding: 1px 9px; letter-spacing: 3px; text-shadow: none; }
.cc-tb-rule { flex: 1; height: 0; border-top: 1px dashed var(--ph-faint); }
.cc-tb-mode { color: var(--ph); letter-spacing: 3px; }
.cc-tb-rec { color: var(--ok); letter-spacing: 2px; animation: cc-blink 1.4s steps(1) infinite; }   /* live-local session dot */
/* PHOSPHOR tint selector — lives in the titlebar as console hardware (zero added panel height). Each
   swatch recolours the WHOLE station live and persists (app.js → StationUI.setTheme). */
.cc-tb-phosphor { display: flex; align-items: center; gap: 7px; }
.cc-tb-plabel { color: var(--ph-dim); letter-spacing: 2px; }
.phosphor-swatches { display: flex; gap: 5px; }
.phosphor-swatches .swatch {
  width: 14px; height: 14px; padding: 0; cursor: pointer; border-radius: 50%;
  background: var(--sw); border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 5px var(--sw); opacity: .6; transition: none;
}
.phosphor-swatches .swatch:hover { opacity: 1; }
.phosphor-swatches .swatch.sel { opacity: 1; outline: 1px solid var(--ph-bright); outline-offset: 2px; box-shadow: 0 0 9px var(--sw); }
.phosphor-swatches .swatch:focus-visible { outline: 2px solid var(--ph-bright); outline-offset: 2px; }
.cc-title-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.cc-title-row h2 { margin: 4px 0 0; }      /* the inverted-block h2 keeps its global look */
@keyframes cc-blink { 0%,50% { opacity: 1; } 50.01%,100% { opacity: 0; } }   /* used by .sp-rec + .cc-tb-rec */

/* MOBILE CONTROL REACHABILITY: PHOSPHOR is real setup hardware, not expendable title
   decoration. Give its six switches a dedicated second row before the bezel clips them. */
@media (max-width: 600px) {
  .cc-titlebar { flex-wrap: wrap; row-gap: 5px; }
  .cc-tb-phosphor { flex: 0 0 100%; order: 5; min-width: 0; }
  .phosphor-swatches { flex-wrap: wrap; }
}

/* .cc-grid is the CONNECTORS catalog grid (stationui.js), NOT genesis — genesis uses .ov-grid. Kept here;
   the old genesis two-column body (cc-col, cc-emblem, cc-ascii, cc-stamp, cc-cursor, lead-banner) is deleted. */
.cc-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 0;
  padding: 10px 22px 2px;
}

/* recommended-model quick picks (OpenRouter): one-tap slugs for newcomers — premium + beginner-friendly */
.model-picks { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 2px; }
.model-picks:empty { display: none; }
.mp-chip {
  font-family: inherit; font-size: 13px; letter-spacing: .5px; color: var(--ph);
  background: var(--panel2); border: 1px solid var(--ph-dim); border-radius: 4px;
  padding: 3px 9px; cursor: pointer; text-shadow: 0 0 6px var(--ph-glow2); white-space: nowrap;
}
.mp-chip:hover { border-color: var(--ph); background: var(--ph-faint); color: var(--ph-bright); }
.mp-chip.sel { background: var(--ph); color: var(--ink); border-color: var(--ph); text-shadow: none; }
.mp-chip b { color: var(--gold); font-weight: normal; }
.mp-chip.sel b { color: var(--ink); }

@media (max-width: 860px) {
  .cc-grid { grid-template-columns: 1fr; padding: 12px 18px 4px; }
}

/* ChatGPT-subscription (Codex OAuth) sign-in block on the connect screen */
.codex-signin {
  border: 1px solid var(--ph-dim); border-radius: 6px; padding: 13px;
  background: linear-gradient(180deg, rgba(var(--ph-rgb),.04), rgba(0,0,0,.35));
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.08);
  display: flex; flex-direction: column; gap: 10px;
}
.codex-status { color: var(--ph-bright); font-size: 16px; min-height: 20px; }
.codex-status.ok { color: var(--gold); }
.codex-status.bad { color: var(--bad); }
.codex-status .conn-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 7px; vertical-align: middle; }
.codex-code {
  font-size: 30px; letter-spacing: 6px; text-align: center; color: var(--ink);
  background: var(--ph); padding: 8px 6px; border-radius: 5px; user-select: all;
  text-shadow: none;}
.codex-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.hint { font-size: 15px; margin-top: 4px; min-height: 18px; }
.connect-actions {
  display: flex; align-items: center; gap: 12px; margin: 10px 0 0; padding: 12px 22px 14px;
  border-top: 1px solid var(--ph-faint);
}
.cc-actions-ascii { color: var(--ph-dim); font-size: 17px; opacity: .7; }
.connect-actions #btn-wake { margin-left: auto; }   /* WAKE anchors right; BACK sits by the ASCII tick on the left */
.msg { min-height: 20px; color: var(--bad); font-size: 16px; margin: 6px 22px 0; }
.msg:empty { min-height: 0; margin: 0; }
.msg.ok { color: var(--gold); }
/* the "get a key" deep-link in a cold-start connect message — reads as an actionable link, not error text */
.connect-link { color: var(--gold); text-decoration: underline; text-underline-offset: 2px; }
.connect-link:hover { filter: brightness(1.2); }

/* (deleted: the .adv-toggle/.adv-caret/.adv-body "FINE-TUNE THE VOICE" reveal — the voice fine-tune UI was
   removed from genesis 2026-07-09; the archetype chip is the whole voice choice now.) */

/* ---------- GAME ---------- */
#game-top {
  display: flex; align-items: center; gap: 14px; padding: 0 14px; height: 52px; flex: 0 0 auto;
  background: var(--panel); border-radius: 9px;
  box-shadow: 0 0 0 2px var(--ph), 0 0 0 3.5px rgba(0,0,0,.9), inset 0 0 40px rgba(0,0,0,.8);
}
.gt-logo { color: var(--ph-bright); letter-spacing: 2px; }
.gt-sep { color: var(--ph-dim); }
.gt-field { color: var(--ph-dim); font-size: 15px; letter-spacing: 1px; }
.gt-field b { color: var(--ph-bright); font-weight: normal; margin-left: 4px; }
.gt-spacer { flex: 1; }

#game-main { flex: 1; display: flex; gap: 9px; min-height: 0; }

/* ---- TUBE APERTURE (2026-07-27) ----
   The glass overlay's darkening is a SEPARATE layer from the barrel warp (World.crt.curve): the warp bows
   the picture, this vignette dims its outer band. Held at `transparent 50%` the clear zone ended barely past
   the halfway mark, so the outer ~third of each axis was progressively crushed and the panel read as a small
   oval inside a big black rectangle — real estate lost to styling, not to curvature. These are now dials, not
   literals: crtlab's TUBE APERTURE section writes them live (?crtlab=1), so the numbers get TUNED and copied
   back rather than guessed. Curvature is untouched by every one of them. */
:root {
  --tube-clear: 68%;      /* how far out the glass stays fully clear (was 50%) */
  --tube-mid: 88%;        /* the mid stop of the falloff */
  --tube-mid-a: 0.26;     /* alpha at the mid stop (was 0.34) */
  --tube-edge-a: 0.70;    /* alpha at the very corner (was 0.82) */
  --tube-inset: 40px;     /* the wrap's inner edge shadow (was 60px) */
}
#stage-wrap {
  /* radius + the phosphor ring are the tube's CASING — hardware, so they hold their drawn size at
     every TEXT SIZE (see the --cz note on `body` in style.css). */
  flex: 1; position: relative; min-width: 0; border-radius: calc(9px * var(--cz)); overflow: hidden;
  background: var(--bg);
  box-shadow: 0 0 0 calc(2px * var(--cz)) var(--ph), 0 0 0 calc(3.5px * var(--cz)) rgba(0,0,0,.9), inset 0 0 var(--tube-inset) rgba(0,0,0,.9);
}
/* Keep the approved room colour grade independent of the UI phosphor theme.
   The custom theme's --cam-grade desaturates the entire feed (0.72 vs 1.06),
   making identical room lighting look dull after opening a saved custom theme.
   Brightness presets still control room exposure through StationBake.LIGHT. */
#stage { display: block; width: 100%; height: 100%; image-rendering: pixelated; filter: saturate(1.06) contrast(1.1) brightness(0.94); }

/* KEYLESS-BRAIN CTA — the honest standing banner shown after an awakening that finished with no key.
   Sits low-center over the feed, above the cam HUD (z3) so it reads clearly; the world stays visible behind
   it. Only the banner itself takes pointer events. Rendered/hidden purely from Harness.hasStoredCredential. */
.key-cta {
  /* centred by auto margins, NOT left:50%+translate: an absolutely positioned box shrink-fits to the space
     right of `left`, so left:50% capped the banner at half the stage and crushed the sentence to one word
     per line once it carried three doors. left:0;right:0 gives it the whole stage to fit-content in. */
  position: absolute; left: 0; right: 0; bottom: 16px; margin: 0 auto; z-index: 4;
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px; width: fit-content; max-width: 88%;
  padding: 9px 12px; pointer-events: auto;
  color: var(--warn); font-size: 13px; line-height: 1.4;
  background: rgba(4, 8, 5, 0.92); border: 1px solid var(--warn); border-radius: 4px;
  box-shadow: 0 0 12px rgba(0,0,0,.7), 0 0 10px color-mix(in srgb, var(--warn) 35%, transparent);
  animation: keyctaIn .5s ease both;
}
@keyframes keyctaIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.key-cta[hidden] { display: none; }
.key-cta-glyph { flex: 0 0 auto; }
.key-cta-txt { min-width: 0; flex: 1 1 240px; }   /* the sentence keeps a real column; the doors wrap under it when the stage is narrow */
.key-cta-act {
  flex: 0 0 auto; cursor: pointer; padding: 5px 9px; font: inherit; font-size: 11px; color: var(--ink); background: var(--warn); border: 0; border-radius: 3px; white-space: nowrap;
}
.key-cta-act:hover { filter: brightness(1.12); }
/* three doors (issue #6): the named fix is the filled primary; SWITCH PROVIDER and the free local path are
   outlined twins — same phosphor, quieter weight, so one banner never shouts three commands at once. */
.key-cta-acts { display: flex; flex: 0 0 auto; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: flex-end; }
.key-cta-alt, .key-cta-free {
  flex: 0 0 auto; cursor: pointer; padding: 4px 8px; font: inherit; font-size: 11px; color: var(--warn); background: transparent; border: 1px solid var(--warn); border-radius: 3px; white-space: nowrap;
}
.key-cta-alt:hover, .key-cta-free:hover { background: color-mix(in srgb, var(--warn) 16%, transparent); }

/* ---- CRT TUBE FACE: a curved-glass overlay so the feed reads as a convex tube.
   Pure overlay (bowed corner vignette + edge rim) — the pixel canvas underneath
   stays crisp; only the glass implies the bulge. Sits above the canvas but UNDER
   .cam-hud (z3) so REC/FEED labels stay legible. Toggle off with body.no-curve.
   True geometric barrel-warp would need a shader pass, not CSS.

   NO SPECULAR SWEEP (2026-08-07). There used to be a second gradient here —
   `ellipse 72% 52% at 32% 3%, rgba(255,247,228,0.17)` — a big warm bloom pinned to
   the upper-left of the glass. On a station-only frame it passes as a reflection;
   over open void it is a fixed cream haze sitting in DEEP SPACE, brighter than the
   nebulas it covers and nailed to the screen while the sky drifts under it. Andrew
   called it out as "this prefixed light" and had it removed. The rim highlight in
   the box-shadow below (inset 0 2px 1px) is the glass edge and STAYS — that is a
   1-2px line on the bezel, not a light in the sky. Do not re-add a large soft
   highlight to this overlay. */
#stage-wrap::before {
  content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none; border-radius: 9px;
  background:
    radial-gradient(ellipse 142% 136% at 50% 48%, transparent var(--tube-clear), rgba(0,0,0,var(--tube-mid-a)) var(--tube-mid), rgba(0,0,0,var(--tube-edge-a)) 100%);  /* bowed corner vignette → convex tube; stops are crtlab dials */
  box-shadow: inset 0 0 30px rgba(0,0,0,0.6), inset 0 2px 1px rgba(255,243,220,0.11), inset 0 0 0 1px rgba(255,238,210,0.04);   /* tube rim + top edge glass highlight */
}
body.no-curve #stage-wrap::before { background: none; box-shadow: none; }
/* NOTE: the Appearance CRT dial deliberately does NOT reach this rule. The tube glass over the feed
   is part of the station being a tube, not a reading surface — DULLED thins the glass over the HTML
   only (style.css body.crt-dull). `no-curve` above stays an internal/debug hatch. */

/* NOTE: scanlines are painted IN-CANVAS now (world.js drawCRT — device-px integer lines, drawn OVER
   the warped feed). They are NOT a CSS overlay anymore: the old #stage-wrap::after gradient rasterised
   against the display grid and beat into wide moiré stripes. Don't re-add a CSS scanline layer here. */

/* camera HUD overlay (reused v7 idiom) */
.cam-hud { position: absolute; inset: 0; pointer-events: none; font-size: 14px; color: var(--ph); z-index: 3; }
.cam-c { position: absolute; font-size: 20px; opacity: .55; }
.cam-tl { top: 6px; left: 8px; } .cam-tr { top: 6px; right: 8px; } .cam-bl { bottom: 6px; left: 8px; } .cam-br { bottom: 6px; right: 8px; }
.cam-label { position: absolute; top: 9px; left: 24px; letter-spacing: 1px; opacity: .8; }
.cam-rec { position: absolute; top: 9px; right: 26px; color: var(--bad); }
.cam-feed { position: absolute; bottom: 9px; left: 24px; opacity: .65; }

/* chat panel */
#chat-panel {
  flex: 0 0 360px; display: flex; flex-direction: column; min-height: 0; border-radius: calc(9px * var(--cz));
  background: var(--panel);
  box-shadow: 0 0 0 calc(2px * var(--cz)) var(--ph), 0 0 0 calc(3.5px * var(--cz)) rgba(0,0,0,.9), inset 0 0 40px rgba(0,0,0,.8);
  isolation: isolate;
}
#chat-panel h3 {
  display: flex; justify-content: space-between; align-items: baseline;
  color: var(--ink); background: var(--ph); padding: 4px 12px; letter-spacing: 2px; border-radius: 7px 7px 0 0;
}
#chat-panel h3 .dim { color: var(--ink); opacity: .7; font-size: 14px; }
#chat-panel h3::after { display: none; }   /* the global engraved-rule ::after doesn't belong on the solid amber COMMS header */
#chat-log {
  position: relative; z-index: 1;
  flex: 1; min-height: 0; overflow-y: auto; padding: 12px;
  display: flex; flex-direction: column; gap: 3px;
  background: rgba(5, 3, 1, 0.58);
}
/* PILL CLEARANCE (2026-07-27): .comms-newpill floats over this scroller's bottom edge and used to sit
   ON TOP of the last transcript row. While the pill is up, reserve its height at the foot of the scroll
   so the newest line can always be read past it. Toggled by chat.js showNewPill/hideNewPill. */
#chat-log.pill-clear { padding-bottom: 48px; }
#chat-log > * { position: relative; z-index: 1; flex: 0 0 auto; }
#chat-log > .choice-row { z-index: 2; }
#chat-log > .cmsg.consent, #chat-log > .cmsg.turnin, #chat-log > .cmsg.nudge { z-index: 3; }
/* COMMS scrollbar — scoped to #chat-log so it cleanly beats the global .scrolly rules on both engines */
#chat-log { scrollbar-color: var(--ph-dim) transparent; }
#chat-log::-webkit-scrollbar { width: 8px; }
#chat-log::-webkit-scrollbar-track { background: transparent; }
#chat-log::-webkit-scrollbar-thumb { background: var(--ph-dim); border-radius: 4px; }
#chat-log::-webkit-scrollbar-thumb:hover { background: var(--ph); }

/* ===== COMMS as a CONVERSATION (not a stack of boxes) =====
   De-boxed + grouped by speaker: consecutive turns from the same speaker collapse under ONE
   name header (the repeated label is hidden), tied together by a thin side rail — agent on the
   left, Commander on the right. Every turn is a plain grouped message; the live streaming row is
   just the newest one. Per-message HH:MM stamps come from each turn's REAL recorded ts (comms.css
   .cmsg-ts) — a legacy turn with no stored ts renders no stamp rather than a fabricated one. */
.cmsg { font-size: 16px; line-height: 1.4; min-width: 0; max-width: 100%; position: relative; }   /* min-width:0 lets long tokens wrap instead of stretching the column; position anchors the copy button */
.cmsg .who { font-size: 12px; letter-spacing: 1.5px; color: var(--ph-dim); display: block; margin: 9px 0 3px; text-transform: uppercase; }
.cmsg .body { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; }

/* SELECTABLE TRANSCRIPT — the log is real text: mouse-select + copy anywhere in it, like any terminal
   (overrides the game chrome's body { user-select: none }). Controls and labels stay unselectable so a
   drag-select never smears button captions / speaker chips / timestamps into the copied prose. */
#chat-log { user-select: text; }
#chat-log .cmsg .body { cursor: text; }
#chat-log button, #chat-log .who, #chat-log .cmsg-ts { user-select: none; }

/* COPY — a hover-revealed ⧉ button at the top-right of an agent MESSAGE row (CSS keeps it off the work-log
   beats, which aren't prose). Flashes to ✓ on a successful copy. Touch/keyboard-focus reveal it too. */
.cmsg-copy {
  position: absolute; top: 1px; right: 0; z-index: 1;
  border: 1px solid var(--ph-dim); background: var(--panel2); color: var(--ph-dim);
  font-size: 12px; line-height: 1; padding: 2px 5px; border-radius: 4px; cursor: pointer;
  opacity: 0; transition: opacity .12s, color .12s, border-color .12s;
}
.cmsg.agent:hover > .cmsg-copy, .cmsg.user:hover > .cmsg-copy, .cmsg-copy:focus-visible { opacity: .85; }
.cmsg-copy:hover { color: var(--ph-bright); border-color: var(--ph); opacity: 1; }
.cmsg-copy.copied { color: var(--ph); border-color: var(--ph); opacity: 1; }
/* the work-log beats are not "messages" → no copy button */
.cmsg.tool > .cmsg-copy, .cmsg.consent > .cmsg-copy, .cmsg.turnin > .cmsg-copy,
.cmsg.nudge > .cmsg-copy, .cmsg.deliverable > .cmsg-copy { display: none; }

/* the agent: a left rail + phosphor name */
.cmsg.agent { align-self: stretch; border-left: 2px solid var(--ph-faint); padding-left: 11px; }
.cmsg.agent .who { color: var(--ph); }
.cmsg.agent .body { color: var(--text); }
.cmsg.agent.err .body { color: var(--bad); }
/* the de-emphasized raw technical detail under a plain-language error headline: dim, smaller, on its own
   line. Kept for debugging (also mirrored into its title tooltip) without competing with the readable lead. */
.cmsg.agent.err .err-detail {
  display: block; margin-top: 3px; font-size: 12.5px; line-height: 1.4;
  color: var(--ph-dim); opacity: .8; word-break: break-word;
}

/* the Commander: right-aligned with a gold right rail — a party opposite the agent, no heavy box.
   min-width keeps a one-word message ("hi") from collapsing to a cramped sliver against the rail. */
.cmsg.user { align-self: flex-end; min-width: 4.5em; max-width: 86%; text-align: left; border-right: 2px solid var(--gold); padding-right: 11px; }
.cmsg.user .who { color: var(--gold); text-align: right; }
.cmsg.user .body { color: var(--ph-bright); }

/* GROUPING — hide the repeated name when the SAME speaker continues; add air on a speaker change */
.cmsg.agent + .cmsg.agent .who,
.cmsg.user + .cmsg.user .who { display: none; }
.cmsg.user + .cmsg.agent, .cmsg.agent + .cmsg.user { margin-top: 8px; }

.cmsg .caret { display: inline-block; width: .5em; margin-left: 1px; color: var(--ph); text-shadow: 0 0 6px var(--ph-glow); animation: blink 1s steps(1) infinite; }
@keyframes blink { 50% { opacity: 0; } }

/* (The `.cmsg.agent.reply` lit-headline treatment was retired: chat.js never tags a row `.reply` — every turn,
   live or replayed, is a plain grouped message and the streaming caret marks the newest one. Rules deleted.) */

/* first-run / empty stream: a single dim hint instead of a blank void */
.cmsg-empty { color: var(--ph-dim); font-size: 14px; line-height: 1.5; text-align: center; padding: 18px 10px; opacity: .85; }

/* tool-activity lines: the agent's actions, interleaved with its prose (classic harness flow).
   Dim subordinate micro-lines that read as "what it's doing" between what it's saying. */
.cmsg.tool { font-size: 13.5px; padding-left: 11px; }
.cmsg.tool .who { display: none; }
.cmsg.tool .body { color: var(--ph-dim); letter-spacing: 0.3px; }
.cmsg.tool.err .body { color: var(--bad); }
.cmsg.deliverable .body { color: var(--ph); }
.deliverable-link { color: var(--gold); text-decoration: underline; cursor: pointer; }
.deliverable-link:hover { color: var(--ph-bright); }
/* an inline image the agent generated: a small clickable thumbnail; click opens the full image in a new tab */
.deliverable-thumb { display: inline-block; vertical-align: middle; margin-top: 4px; line-height: 0; border: 1px solid var(--gold); border-radius: 4px; overflow: hidden; cursor: pointer; }
.deliverable-thumb img { display: block; max-width: 168px; max-height: 128px; }
.deliverable-thumb:hover { border-color: var(--ph-bright); box-shadow: 0 0 6px var(--ph-glow); }
/* an inline video/audio clip the agent produced — a seekable player (Range-streamed from /api/file) */
.media-name { display: block; color: var(--ph-dim); font-size: 12px; margin: 2px 0 4px; letter-spacing: 0.3px; }
.deliverable-video { display: block; max-width: 340px; max-height: 240px; width: 100%; border: 1px solid var(--gold); border-radius: 5px; background: #000; }
.deliverable-audio { display: block; max-width: 340px; width: 100%; margin-top: 2px; }
.media-fallback { display: inline-block; margin-top: 5px; }
/* a clickable URL the agent typed in its prose — match the gold deliverable-link affordance */
.cmsg.agent .body a { color: var(--gold); text-decoration: underline; }
.cmsg.agent .body a:hover { color: var(--ph-bright); }

/* END-OF-RUN RECAP (work-visibility): a passive work-log REPORT in the .tool register — what the run
   produced (files/images/channel sends) + cost/duration/model. Never a beat: it stays in the log like a
   tool line (no vanish), and only shows when there ARE artifacts or the run ended abnormally. */
.cmsg.recap .recap-line { display: block; line-height: 1.55; min-width: 0; overflow-wrap: anywhere; }
.cmsg.recap .recap-head { color: var(--gold); letter-spacing: .4px; }
.cmsg.recap .recap-foot { font-size: 12px; opacity: .85; }
.recap-copy {
  font: inherit; font-size: 12px; cursor: pointer;
  color: var(--ph-dim); background: transparent; border: none;
  margin-left: 6px; padding: 0 2px; opacity: .75;
}
.recap-copy:hover { color: var(--ph-bright); opacity: 1; }

/* OPEN-FOLDER affordance on a file deliverable (Theme 4 — outputs are findable). Quiet inline
   control reusing the phosphor language; desktop reveals the folder, browser copies the path. */
.deliverable-folder {
  font: inherit; font-size: 11px; letter-spacing: .3px; cursor: pointer;
  color: var(--ph-dim); background: transparent;
  border: 1px solid rgba(var(--ph-bright-rgb), .28); border-radius: 3px;
  margin-left: 8px; padding: 0 6px; opacity: .85; white-space: nowrap;
}
.deliverable-folder:hover { color: var(--ph-bright); border-color: rgba(var(--ph-bright-rgb), .55); opacity: 1; }

/* live consent prompt — the agent paused for approval, INLINE in the flow where it happened (this
   replaced the always-on top APPROVALS panel). A clear gold frame so it reads as "act on me now". */
.cmsg.consent { font-size: 15px; border-left: 2px solid var(--gold); background: rgba(var(--gold-rgb),.06); padding: 8px 11px; border-radius: 0 5px 5px 0; margin: 5px 0; }
.cmsg.consent .body { color: var(--ph-bright); }
.consent-btns { display: inline-flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.consent-btn {
  font: inherit; font-size: 13px; letter-spacing: 0.5px; cursor: pointer;
  color: var(--ph); background: linear-gradient(180deg, rgba(var(--ph-rgb),.07), rgba(var(--ph-rgb),.02));
  border: 1px solid var(--ph-dim); padding: 5px 11px; border-radius: 4px; text-shadow: 0 0 5px var(--ph-glow2);
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.1), 0 1px 0 rgba(0,0,0,.35);
  transition: background-color .1s, color .1s, border-color .1s, box-shadow .1s, transform .1s;
}
.consent-btn:active { transform: translateY(1px); }
.consent-btn:hover { color: var(--ph-bright); border-color: var(--ph); background: var(--ph-faint); }
.consent-btn:focus-visible { outline: none; color: var(--ph-bright); border-color: var(--ph); background: var(--ph-faint); box-shadow: 0 0 8px var(--ph-glow2); }
/* PRIMARY ACTION — at most ONE per card. Named friction (2026-08-04): every decision card rendered
   its affirmative, its editor and its refusal as three identical outlined pills, so the eye had to
   READ all three to find the "yes". The primary now carries a matte gold fill and reads first.
   Matte law: the gradient spans 8% of the base tone, the top inset is .14, and hover is a FILL
   change — never an outer glow. */
.consent-btn.primary {
  color: var(--ph-bright); border-color: var(--gold);
  background: linear-gradient(180deg, rgba(var(--gold-rgb),.20), rgba(var(--gold-rgb),.12));
  box-shadow: inset 0 1px 0 rgba(var(--ph-bright-rgb),.14), 0 1px 0 rgba(0,0,0,.35);
}
.consent-btn.primary:hover,
.consent-btn.primary:focus-visible {
  color: var(--ph-bright); border-color: var(--gold); box-shadow: inset 0 1px 0 rgba(var(--ph-bright-rgb),.14), 0 1px 0 rgba(0,0,0,.35);
  background: linear-gradient(180deg, rgba(var(--gold-rgb),.30), rgba(var(--gold-rgb),.20));
}
/* QUIET SECONDARY — the third rank, below the ghost default. Named friction: the away digest put two
   equally-weighted pills on every row, so three runs arrived as six identical buttons and the rows stopped
   being distinguishable. The card's copy already states the order ("read each one, THEN rate it"), so the
   later step recedes until it is earned. Chrome only — the control keeps its full hit area. */
.consent-btn.quiet {
  color: var(--ph-dim); border-color: rgba(var(--ph-rgb), .28); background: none;
  box-shadow: none; text-shadow: none;
}
.consent-btn.quiet:hover, .consent-btn.quiet:focus-visible {
  color: var(--ph-bright); border-color: var(--ph); background: var(--ph-faint); box-shadow: none;
}
.consent-btn.danger { color: var(--gold); border-color: var(--gold); }
.consent-btn.danger:hover { color: var(--ph-bright); background: rgba(var(--gold-rgb), 0.12); }
.consent-btn.deny { color: var(--bad); border-color: var(--bad); }
.consent-btn.deny:hover { color: var(--ph-bright); background: rgba(255, 90, 90, 0.12); }
/* MULTI-SELECT clarify (2026-08-14): a toggled pick reads as a matte FILL, never an outer glow —
   the same vocabulary as the primary action, so "chosen" and "the yes" share one voice. */
.consent-btn.sel {
  color: var(--ph-bright); border-color: var(--ph-bright);
  background: linear-gradient(180deg, rgba(var(--ph-rgb),.34), rgba(var(--ph-rgb),.24));
  box-shadow: inset 0 0 0 1px rgba(var(--ph-bright-rgb),.22), 0 1px 0 rgba(0,0,0,.35);
}
.consent-btn.consent-confirm { color: var(--ph-bright); border-color: var(--gold);
  background: linear-gradient(180deg, rgba(var(--gold-rgb),.20), rgba(var(--gold-rgb),.12)); }
.consent-btn.consent-confirm:disabled { color: var(--ph-dim); border-color: rgba(var(--ph-rgb), .28);
  background: none; cursor: default; box-shadow: none; text-shadow: none; }
.consent-result { font-size: 13px; letter-spacing: 0.5px; color: var(--ph); }
.consent-result.err { color: var(--bad); }

/* Cortex turn-in beat: reflection proposed durable memories — Keep / Edit / Discard (the formation loop) */
.cmsg.turnin {
  font-size: 15px; margin: 7px 0; padding: 9px 11px 10px;
  border-left: 2px solid var(--gold); border-radius: 0 5px 5px 0;
  background: linear-gradient(180deg, rgba(var(--gold-rgb),.08), rgba(var(--gold-rgb),.035));
  box-shadow: inset 0 1px 0 rgba(var(--ph-bright-rgb),.08), 0 0 10px rgba(var(--gold-rgb),.08);
}
.cmsg.turnin .body {
  display: flex; flex-direction: column; gap: 7px;
  color: var(--ph-bright);
}
.turnin-title { display: block; line-height: 1.35; }
.turnin-queue {
  align-self: flex-start; color: var(--gold); font-size: 11px; letter-spacing: 0.6px;
  border: 1px solid rgba(var(--gold-rgb),.55); border-radius: 2px; padding: 1px 5px;
  background: rgba(var(--gold-rgb),.06);
}
.turnin-queue[hidden] { display: none; }
.turnin-slot { display: block; min-width: 0; }
/* "rate the work" — the primary leveling beat: a gold label + the shared consent buttons (reused styling). */
.turnin-rate { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; margin: 5px 0 9px; }
.work-rate-label { color: var(--gold); font-size: 12px; letter-spacing: .4px; opacity: .95; }
/* one-time explainer above the very first rate control (chat.js WORKRATE_COACH_KEY) — a quiet full-width
   line so it reads as coaching, not part of the label. flex-basis 100% breaks it onto its own row in the
   .turnin-rate / consent-btns flex hosts. */
.work-rate-hint { flex-basis: 100%; width: 100%; color: var(--ph); font-size: 11px; letter-spacing: .3px; opacity: .8; margin-bottom: 2px; }
.turnin-rate .consent-btns, .cmsg.work-rate .consent-btns { margin-top: 0; }
/* FLAT item row — the card is the only box. A second bordered box inside the gold inset read as
   stacked popups; the item is now a quiet row: soft kind pill + text, evidence + buttons below. */
.turnin-item {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start; gap: 6px 8px;
  margin-top: 1px; padding: 1px 0; border-radius: 0;
  background: transparent; border-left: 0;
}
.turnin-kind {
  font-size: 10px; letter-spacing: 1px; color: var(--gold);
  background: rgba(var(--gold-rgb), .13); border: none; border-radius: 2px;
  padding: 2px 6px; flex: 0 0 auto; margin-top: 1px; opacity: 0.95;
}
/* provenance / evidence line ("↳ you said …", "↳ 3 approvals in a row") — a dim aside above the
   buttons, never a bordered chip (that read as ANOTHER nested box). */
.turnin-evidence {
  grid-column: 1 / -1; justify-self: start; min-width: 0; overflow-wrap: anywhere;
  color: var(--ph-dim); font-size: 12px; line-height: 1.4; letter-spacing: .2px;
}
/* buttons inside a post-run beat card are secondary controls — compact, 1px chrome (the full-size
   2px consent buttons stay reserved for real approval prompts outside the turn-in family). */
.cmsg.turnin .consent-btn { font-size: 12px; padding: 2px 9px; border-width: 1px; }
.turnin-text {
  min-width: 0; color: var(--ph-bright); font-size: 14px; line-height: 1.35;
  overflow-wrap: anywhere; word-break: break-word;
}
.turnin-item .consent-btns { grid-column: 1 / -1; margin-top: 1px; display: flex; flex-wrap: wrap; gap: 6px; }
/* anything chat.js appends INTO the item grid AFTER the kind/text pair (the ↳ evidence line on
   study/trust cards, the verdict flash, an in-place rate control) must claim a full row. Auto-placed
   they land in the `auto` first column — a long evidence string then blows that column out to the
   container width and crushes the minmax(0,1fr) text column to ~1ch, wrapping the belief text one
   character per line down the window edge (and stretching the card to fill the whole panel). */
.turnin-item > .turnin-queue { grid-column: 1 / -1; justify-self: start; min-width: 0; overflow-wrap: anywhere; }
.turnin-item > .consent-result { grid-column: 1 / -1; min-width: 0; overflow-wrap: anywhere; }
.turnin-item > .turnin-rate { grid-column: 1 / -1; min-width: 0; }
.turnin-edit {
  grid-column: 1 / -1; width: 100%; box-sizing: border-box; min-width: 0;
  font: inherit; font-size: 14px; color: var(--ph-bright);
  background: var(--ink); border: 1px solid var(--ph-dim); border-radius: 2px; padding: 2px 6px;
}
.turnin-edit:focus { outline: none; border-color: var(--ph); }

/* ── THE OFFER CARD (recommendation spine) ────────────────────────────────────────────────────────────
   ONE shape for every proactive consent offer: eyebrow → evidence (the Commander's own words) → proposal
   → consent. It is the SAME gold-inset .turnin hardware, not a new visual language — only the internal
   rhythm is unified, which was the named friction (seven inconsistent card shapes). Matte throughout: no
   gloss, no second box, no OS-painted control anywhere; the only additions are a quieter eyebrow, a lit
   evidence line and a hairline rule that separates the citation from the ask. */
.cmsg.turnin.rec { padding-top: 8px; }
.cmsg.turnin.rec .body { gap: 5px; }
/* the eyebrow is a LABEL, not a sentence — dim, tracked-out, small. It must never out-shout the evidence. */
.rec-eyebrow {
  display: flex; align-items: center; gap: 6px;
  font-size: 10.5px; letter-spacing: 2.2px; line-height: 1;
  color: var(--gold); opacity: .72;
}
/* ◈ is a fallback-font glyph under VT323 — give it its own BOX and centre it there. Never derive the
   eyebrow's spacing from font-size: the two faces disagree and the label drifts off its baseline. */
.rec-glyph {
  display: inline-flex; align-items: center; justify-content: center;
  width: 11px; height: 11px; flex: 0 0 11px; font-size: 10px; line-height: 1; letter-spacing: 0;
}
/* THE EVIDENCE — the Commander's own words. It is the reason the card exists, so it reads as the lit
   line: full width, phosphor-bright, with a hairline beneath separating citation from proposal. */
.rec-item > .rec-evidence {
  grid-column: 1 / -1; min-width: 0; overflow-wrap: anywhere;
  color: var(--ph-bright); font-size: 13px; line-height: 1.45; letter-spacing: .2px;
  padding-bottom: 6px; margin-bottom: 1px;
  border-bottom: 1px solid rgba(var(--gold-rgb), .18);
}
.rec-item { gap: 5px 8px; grid-template-columns: minmax(0, 1fr); }
/* THE KIND RIDES THE EYEBROW. Named friction (2026-08-04): study / trust / thread all opened with the
   same "◈ NOVA NOTICED" line, and the one word that says WHICH decision this is (WORKING STYLE / GRANT /
   THREAD) sat four lines down as a chip in the item's first grid column — where it also crushed the
   proposal into a narrow second column that wrapped mid-phrase. Lifted into the eyebrow it identifies
   the card at a glance AND hands the proposal the card's full width. */
.rec-kind { color: var(--gold); opacity: 1; letter-spacing: 2.2px; }
.rec-who { opacity: .62; }
.rec-eyebrow { opacity: 1; }
/* the note ("raises the dial to FREE") is consequence, not evidence — it stays the dim aside it was. */
.rec-item > .turnin-evidence { margin-top: 1px; }
/* the ask itself carries the weight of a headline without any glow — matte CRT, not a web toast. */
.rec-item > .turnin-text { font-size: 14.5px; }
/* ENTRANCE — the mirror of .beat-vanish (.26s ease): a settled card leaves the way an offered card
   arrives, so the feed has one motion vocabulary. Slides down out of the reply above it, never a pop. */
@keyframes rec-card-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}
.cmsg.turnin.rec { animation: rec-card-in .26s var(--ease-out, ease) both; }
@media (prefers-reduced-motion: reduce) { .cmsg.turnin.rec { animation: none; } }

/* W3 — THE DELIVERY CARD (rendered inside the deliverable's OWN session, 2026-07-15). Rides the gold-inset
   turn-in family (.cmsg.turnin.workshop-return); a flat stack — headline, verify line, what/how lines, the
   Open-it action, the files pane, and one action row (optional message + Implement / Later / Discard). */
.ws-verline { display:block; font-size:13.5px; line-height:1.4; letter-spacing:.3px; margin-top:1px; }
.ws-verline.ok { color:var(--gold); }
.ws-verline.dim { color:var(--ph-dim); }
/* the flat delivery card (2026-07-15, premium pass 2026-07-16): labeled sections in the dossier grammar —
   a gold uppercase micro-label over a bright value (the same ws-k/ws-v voice the config cards speak). */
.workshop-return .ws-line { display:block; margin-top:4px; font-size:14px; line-height:1.45; overflow-wrap:anywhere; }
.workshop-return .ws-k { display:block; color:var(--gold); font-size:11px; letter-spacing:1.6px; text-transform:uppercase; opacity:.92; margin-bottom:2px; }
.workshop-return .ws-v { display:block; color:var(--ph-bright); }
.workshop-return .ws-how .ws-v, .workshop-return .ws-notver .ws-v, .workshop-return .ws-because .ws-v { color:var(--ph-dim); font-size:13.5px; line-height:1.45; }
.workshop-return .ws-because .ws-v { font-style:italic; }   /* provenance reads as a quoted aside, not a claim */
/* honest-brevity furniture (2026-07-17): verbose legacy manifests FOLD instead of walling — a quiet
   inline "+ more" reveal, and one gold-marked row per check-yourself item (the old semicolon join). */
.workshop-return .ws-more { background:none; border:0; padding:0; margin-left:2px; font:inherit; font-size:12px;
  letter-spacing:.5px; color:var(--gold); cursor:pointer; opacity:.85; }
.workshop-return .ws-more:hover { opacity:1; text-shadow:0 0 6px var(--ph-glow); }
.workshop-return .ws-nvitem { display:block; padding-left:13px; position:relative; }
.workshop-return .ws-nvitem::before { content:'▸'; position:absolute; left:0; color:var(--gold); opacity:.7; }
/* honest kind chip beside the headline — cf-badge vocabulary, gold edge */
.ws-kindchip { margin-left:8px; font-size:11px; letter-spacing:1.4px; text-transform:uppercase; color:var(--gold);
  border:1px solid rgba(var(--gold-rgb),.55); border-radius:2px; padding:0 5px; background:rgba(var(--gold-rgb),.06);
  vertical-align:2px; white-space:nowrap; }
.ws-pane { min-width:0; background:var(--ph-faint); border:1px solid var(--ph-glow2);
  border-radius:3px; padding:7px 8px; box-sizing:border-box; margin-top:7px; }
/* disk-proven input-capture notice (validateWorkshopManifest.capturesInput) — shown above the Open-it action */
.ws-capture-warn { display:block; margin-top:6px; font-size:13px; line-height:1.42; color:var(--gold);
  background:rgba(var(--gold-rgb),.07); border:1px solid rgba(var(--gold-rgb),.35); border-radius:3px; padding:4px 7px; }
.ws-try { margin-top:7px; }
/* the HERO action — the one click that runs the deliverable. Gold, bloomed, unmistakably primary. */
.consent-btn.ws-openit { color:var(--ph-bright); border-color:var(--gold); font-size:14px; padding:6px 16px;
  background:linear-gradient(180deg, rgba(var(--gold-rgb),.22), rgba(var(--gold-rgb),.13));
  box-shadow:inset 0 1px 0 rgba(var(--ph-bright-rgb),.15), 0 1px 0 rgba(0,0,0,.35);
  transition:background var(--t-fast) var(--ease-soft), color var(--t-fast) var(--ease-soft); }
.consent-btn.ws-openit:hover { color:var(--ph-bright); border-color:var(--gold);
  background:linear-gradient(180deg, rgba(var(--gold-rgb),.32), rgba(var(--gold-rgb),.21));
  box-shadow:inset 0 1px 0 rgba(var(--ph-bright-rgb),.15), 0 1px 0 rgba(0,0,0,.35); }
/* the ACCEPT action — gold means "this really implements"; a fallback save stays neutral by design */
.consent-btn.ws-primary { color:var(--ph-bright); border-color:var(--gold);
  background:linear-gradient(180deg, rgba(var(--gold-rgb),.20), rgba(var(--gold-rgb),.12));
  box-shadow:inset 0 1px 0 rgba(var(--ph-bright-rgb),.14), 0 1px 0 rgba(0,0,0,.35); }
.consent-btn.ws-primary:hover { color:var(--ph-bright);
  background:linear-gradient(180deg, rgba(var(--gold-rgb),.30), rgba(var(--gold-rgb),.20));
  box-shadow:inset 0 1px 0 rgba(var(--ph-bright-rgb),.14), 0 1px 0 rgba(0,0,0,.35); }
.ws-fhead { color:var(--gold); font-size:11px; letter-spacing:1.6px; text-transform:uppercase; margin-bottom:6px; }
.ws-flist { display:flex; flex-direction:column; gap:3px; margin-bottom:7px; }
.ws-filerow { display:flex; align-items:center; gap:6px; }
.ws-file { flex:1 1 auto; text-align:left; background:none; border:1px solid transparent; border-radius:2px; color:var(--ph);
  font-family:inherit; font-size:13.5px; padding:3px 5px; cursor:pointer; overflow-wrap:anywhere; }
.ws-file:hover { border-color:var(--ph-glow2); color:var(--ph-bright); }
.ws-file.sel { border-color:var(--gold); color:var(--ph-bright); background:rgba(var(--gold-rgb),.06); }
/* W7: "view source" is the demoted, opt-in code reader — small and dim beside the primary open action. */
/* demoted, but still READABLE — at .75 opacity on --ph-glow2 it had faded to near-invisible beside the
   larger filenames, which is a hidden control, not a quiet one. */
.ws-viewsrc { flex:0 0 auto; background:none; border:none; color:var(--ph-dim); font-family:inherit;
  font-size:11px; letter-spacing:.5px; padding:2px 4px; cursor:pointer; opacity:.9; }
.ws-viewsrc:hover { color:var(--ph-bright); opacity:1; text-decoration:underline; }
.ws-fview { margin:0; max-height:180px; overflow:auto; background:var(--ink); border:1px solid var(--ph-glow2);
  border-radius:2px; padding:7px 8px; color:var(--ph-bright); font-size:12.5px; line-height:1.45; white-space:pre-wrap;
  overflow-wrap:anywhere; }
/* the decide row — separated from the evidence above by a gold hairline; it is the card's consent moment */
.workshop-return .ws-acts { margin-top:9px; padding-top:9px; border-top:1px solid rgba(var(--gold-rgb),.22);
  gap:6px 8px; align-items:center; flex-wrap:wrap; }
.ws-acts { margin-top:8px; gap:6px 8px; align-items:center; flex-wrap:wrap; }
/* the optional tell-the-agent message — rides the decision as a real user turn in the delivery session */
.ws-msg { flex:1 1 200px; min-width:160px; width:auto; max-width:100%; }
/* the pre-click consequence line ("implement → applies to a new branch in …") — an inset gold consent strip,
   the same left-edge voice as .cmsg.consent: this line IS the consent copy */
.workshop-return .ws-plan { color:var(--gold); font-size:13px; line-height:1.45; margin-top:8px;
  border-left:2px solid var(--gold); border-radius:0 3px 3px 0; padding:5px 8px;
  background:rgba(var(--gold-rgb),.07); }
/* copy-path / open-folder chips on the decided outcome line */
.ws-destchips { display:inline-flex; gap:6px; margin-left:8px; vertical-align:middle; }
/* per-command verification detail: the actual commands run + each one's pass/fail from the manifest */
.ws-verdetail { margin-top:6px; }
.ws-verdetail > .ws-k { display:block; color:var(--gold); font-size:11px; letter-spacing:1.6px; text-transform:uppercase; margin-bottom:4px; }
.ws-vcmd { display:flex; align-items:baseline; gap:6px; font-size:13px; line-height:1.45; overflow-wrap:anywhere; }
.ws-vcmd .ws-vmark { flex:0 0 auto; }
.ws-vcmd.ok  .ws-vmark { color:var(--gold); }
.ws-vcmd.bad .ws-vmark { color:var(--bad); }
.ws-vcmdtext { color:var(--ph-bright); font-family:inherit; }
.ws-vexit { color:var(--bad); font-size:11.5px; }

/* SILENT-SAVE receipt: a PASSIVE log of memories reflection auto-saved — one compact "◈ remembered: …" line per
   memory, kind tag, and a small ✕ to veto (undo the save). Non-blocking: no buttons demand action, it stays as a
   quiet stream line (no auto-collapse). Shares the gold-inset turn-in family but reads calmer (thinner border,
   softer fill) — it's a notice, not a prompt. .receipts drops the deck's flex-gap so lines stack tight. */
.cmsg.turnin.receipts {
  border-left-color: rgba(var(--gold-rgb), .55);
  background: linear-gradient(180deg, rgba(var(--gold-rgb),.04), rgba(var(--gold-rgb),.015));
  box-shadow: none;
}
.cmsg.turnin.receipts .body { gap: 3px; }
/* one small caps header owns the "remembered" claim; the lines below are just the memories. */
.receipt-head {
  color: var(--gold); font-size: 11px; letter-spacing: 1.2px; text-transform: uppercase;
  opacity: .9; margin-bottom: 1px;
}
.receipt-item {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; gap: 4px 8px;
  padding: 1px 0; border-radius: 0; background: transparent; border-left: 0;
}
.receipt-text {
  min-width: 0; color: var(--ph); font-size: 13px; line-height: 1.4;
  overflow-wrap: anywhere; word-break: break-word;
}
.receipt-veto {
  flex: 0 0 auto; align-self: center; cursor: pointer; font: inherit; font-size: 12px; line-height: 1;
  color: var(--ph-dim); background: transparent; border: none; border-radius: 2px;
  padding: 1px 5px; opacity: .6; transition: color .12s ease, opacity .12s ease, background .12s ease;
}
.receipt-veto:hover { color: var(--bad); opacity: 1; background: rgba(255,90,90,.08); }
.receipt-veto:disabled { opacity: .35; cursor: default; }
.receipt-item.vetoed { opacity: .55; }
.receipt-item.vetoed .receipt-text { color: var(--ph-dim); text-decoration: line-through; }
.receipt-item.vetoed .turnin-kind { color: var(--ph-dim); background: var(--ph-faint); }

/* JUST-IN-TIME CURIOSITY nudge — a QUIET aside, not a second lit reply. It used to reuse .cmsg.agent.reply
   (the glowing headline), so it read as a duplicate answer competing with the real one. Now it shares the
   soft gold-inset language of the consent / turn-in beats: clearly "the station is asking", visually subordinate
   to the live reply above it. One post-run beat at a time (chat.js retires a nudge if a turn-in claims the moment). */
.cmsg.agent.nudge {
  border-left: 2px solid var(--gold); background: rgba(var(--gold-rgb),.05);
  padding: 8px 11px; margin: 6px 0 2px; border-radius: 0 5px 5px 0;
}
.cmsg.agent.nudge .who { display: none; }
.cmsg.agent.nudge .body { color: var(--ph-bright); font-size: 15px; }
/* A MULTI-LINE NUDGE IS A LIST. The marker owns a fixed box so the prose column can hang — a wrapped
   "✓ drafted the note — waiting in your outbox" now stays visibly ONE item instead of splitting into two
   at the left margin. Width is set in px, never derived from font-size: ✓ ✗ ⚒ render from the fallback
   face under VT323 and the two faces disagree (the symbol-glyph law). */
.nudge-lines { display: flex; flex-direction: column; gap: 4px; }
/* one column by default — an UNMARKED line is just a row of prose. The marker column is opt-in via .nl-mk,
   which chat.js sets only when it actually rendered a marker; keying it off the marker CLASSES instead let a
   nested-but-unmarked line claim the 14px column and crushed its text to one character per line. */
.nudge-line { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 8px; align-items: start; line-height: 1.42; }
.nudge-line.nl-mk { grid-template-columns: 14px minmax(0, 1fr); }
.nudge-line .nl-m { display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 1.42em; flex: 0 0 14px; font-size: 12px; line-height: 1; letter-spacing: 0; color: var(--gold); }
.nudge-line .nl-t { min-width: 0; overflow-wrap: anywhere; }
/* the opening line is the claim the whole beat makes — it reads first */
.nl-first .nl-t, .nl-head .nl-t { color: var(--ph-bright); }
.nl-first, .nl-head { margin-bottom: 2px; }
/* a completed act and a REFUSED one must never look alike on a truthfulness surface */
.nl-yes .nl-m { color: var(--gold); }
.nl-no .nl-m { color: var(--bad); }
.nl-no .nl-t { color: var(--ph-dim); }
.nl-build .nl-m, .nl-file .nl-m { color: var(--gold); }
/* nested rows sit under their parent's PROSE column, not under its marker */
.nudge-line.nl-sub { padding-left: 22px; }
.nudge-line.nl-sub .nl-t { color: var(--ph); }

/* RETIRING A SETTLED BEAT (chat.js vanish()): a decided memory card / answered nudge fades and collapses to
   nothing, then drops from the DOM — so the COMMS feed only ever holds the conversation and PENDING prompts,
   never a graveyard of "✕ discarded" husks. overflow:hidden lets max-height clip padding for a clean collapse. */
.turnin-item, .cmsg.turnin, .cmsg.nudge {
  overflow: hidden;
  transition: opacity .16s ease, max-height .26s ease, margin .26s ease, padding .26s ease, border-width .26s ease;
}
.beat-vanish {
  opacity: 0 !important;
  margin-top: 0 !important; margin-bottom: 0 !important;
  padding-top: 0 !important; padding-bottom: 0 !important;
  border-top-width: 0 !important; border-bottom-width: 0 !important;
}

/* Cortex MEMORY CORE (M-mem.6): the provenance panel — every belief, traceable, with pin/edit/forget */
.mc-note { font-size: 12px; color: var(--ph-dim); line-height: 1.4; margin: 2px 0 10px; }
.mc-note b { color: var(--ph); }
.mc-list { display: flex; flex-direction: column; gap: 8px; }
.mc-empty { padding: 10px 4px; }   /* dim grade + size via the grouped empty rule (motion.css) */
.mc-rec {
  padding: 8px 10px; border-radius: 3px; background: var(--ph-faint);
  border: 1px solid var(--ph-faint); border-left: 2px solid var(--ph-dim);
}
.mc-rec.pinned { border-left-color: var(--gold); }
/* the declined reject-list: dimmer + a muted red edge so it reads as "set aside, never proposed" (not an active belief) */
.mc-rec.mc-declined { opacity: 0.78; border-left-color: var(--bad); background: transparent; }
.mc-rec.mc-declined .mc-body { color: var(--ph-dim); }
.mc-head { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.mc-rectitle { color: var(--ph-bright); font-size: 13px; }
.mc-scope { color: var(--ph-dim); font-size: 10px; letter-spacing: 0.5px; border: 1px solid var(--ph-dim); border-radius: 2px; padding: 0 4px; opacity: 0.85; }
.mc-pinflag { margin-left: auto; color: var(--gold); font-size: 11px; letter-spacing: 0.5px; }
.mc-body { color: var(--ph-bright); font-size: 14px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; }
.mc-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin-top: 7px; font-size: 11px; color: var(--ph-dim); letter-spacing: 0.3px; }
.mc-prov { cursor: default; }
.mc-trust { display: inline-flex; align-items: center; gap: 6px; }
.mc-trk { display: inline-block; width: 54px; height: 5px; border-radius: 3px; background: var(--ph-faint); border: 1px solid var(--ph-dim); overflow: hidden; vertical-align: middle; }
.mc-fill { display: block; height: 100%; background: var(--gold); }
.mc-acts { margin-top: 8px; }
.mc-edit {
  width: 100%; box-sizing: border-box; font: inherit; font-size: 14px; color: var(--ph-bright);
  background: var(--ink); border: 1px solid var(--ph-dim); border-radius: 2px; padding: 4px 6px; min-height: 48px; resize: vertical;
}
.mc-edit:focus { outline: none; border-color: var(--ph); }

/* COMMANDER DOSSIER (Phase A): the station-wide glass box — one model of the user, every belief editable */
.cd-fam { display: inline-flex; align-items: center; gap: 7px; }
.cd-fk { display: inline-block; width: 120px; height: 6px; border-radius: 3px; background: var(--ph-faint); border: 1px solid var(--ph-dim); overflow: hidden; vertical-align: middle; }
.cd-ff { display: block; height: 100%; background: var(--gold); }
.cd-fpct { color: var(--ph-bright); font-size: 12px; letter-spacing: 0.5px; }
.cd-sub { font-size: 12px; color: var(--ph-dim); margin: 8px 0 6px; letter-spacing: 0.3px; }
.cd-sub b { color: var(--ph); }
/* STATION RECORD (G3a pride layer): the durable lifetime counters — a compact grid of honest numbers.
   Reads PrideStore.snapshot(); a counter with no real sample yet shows "—" (never a fabricated 0). */
.cd-record { margin: 12px 0 4px; padding: 9px 11px; background: var(--ph-faint); border-left: 2px solid var(--gold); border-radius: 0 5px 5px 0; }
.cd-record-h { font-family: inherit; font-size: 10px; letter-spacing: .16em; color: var(--gold); opacity: .9; margin-bottom: 8px; }
.cd-record-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px 14px; }   /* 4-up in the widened (760px) dossier; collapses via the narrow fallback below */
.cd-stat { display: flex; flex-direction: column; gap: 2px; }
.cd-stat-n { font-size: 21px; line-height: .95; color: var(--ph-bright); letter-spacing: .5px; text-shadow: 0 0 10px var(--ph-glow2); }
.cd-stat-n.dim { color: var(--ph-dim); text-shadow: none; }
.cd-stat-l { font-size: 10px; letter-spacing: .1em; color: var(--ph-dim); text-transform: uppercase; }
.cd-founded { margin-top: 9px; font-size: 11px; color: var(--ph-dim); letter-spacing: .3px; }
.cd-founded b { color: var(--ph); }
/* AGENT BRIEFING — the verbatim Commander block agents receive (the panel's practical payoff): a recessed
   terminal well in the CRT vocabulary; the text glows softly because it IS live prompt material. */
.cd-brief { margin: 12px 0 2px; padding: 10px 12px; background: var(--ink); border: 1px solid var(--ph-dim); border-radius: 5px; box-shadow: inset 0 0 18px rgba(var(--ph-rgb), .06); }
.cd-brief-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 7px; }
.cd-brief-h { font-size: 10px; letter-spacing: .16em; color: var(--ph-bright); }
.cd-brief-meter { font-size: 10px; color: var(--ph-dim); letter-spacing: .08em; white-space: nowrap; cursor: help; }
.cd-brief-text { margin: 0; font: inherit; font-size: 13px; line-height: 1.45; color: var(--ph); white-space: pre-wrap; word-break: break-word; max-height: 190px; overflow-y: auto; text-shadow: 0 0 4px var(--ph-glow2); }
.cd-brief-empty { font-style: italic; color: var(--ph-dim); font-size: 12px; }
.cd-brief-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 9px; flex-wrap: wrap; }
.cd-flows { display: flex; flex-wrap: wrap; gap: 5px; }
.cd-flow { font-size: 10px; letter-spacing: .06em; color: var(--ph-dim); border: 1px solid var(--ph-faint); border-radius: 3px; padding: 1px 7px; cursor: help; }
.cd-flow:hover { color: var(--ph); border-color: var(--ph-dim); }
.cd-brief-copy { white-space: nowrap; }
/* a belief the char cap shortened/dropped from the composed briefing — gold = "needs your attention" */
.cd-trim-tag { display: inline-block; margin-right: 6px; padding: 0 5px; font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--gold); border: 1px dashed rgba(var(--gold-rgb), .6); border-radius: 2px; cursor: help; }
/* the dimension sections: a two-column grid in the widened window (single column of short cards wasted it) */
.cd-dims { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px 22px; align-items: start; }
.cd-sec { margin-top: 14px; min-width: 0; }
.cd-sec.known .cd-sech { border-bottom-color: var(--ph-dim); }
@media (max-width: 820px) { .cd-dims { grid-template-columns: 1fr; } .cd-record-grid { grid-template-columns: repeat(2, 1fr); } }
.cd-sech { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; border-bottom: 1px solid var(--ph-faint); padding-bottom: 3px; }
.cd-dim { color: var(--ph-bright); font-size: 13px; letter-spacing: 0.5px; text-transform: uppercase; }
.cd-dn { color: var(--ph-dim); font-size: 11px; }
.cd-empty { font-style: italic; padding: 4px 2px 6px; }   /* dim grade + size via the grouped empty rule (motion.css) */
/* the curiosity question-state readout for a blank dimension: a quiet aside + the re-enable escape hatch */
.cd-curio { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 0 2px 6px; }
.cd-curio:empty { display: none; }
.cd-curio-lbl { color: var(--ph-dim); font-size: 11px; letter-spacing: 0.3px; }
.cd-reenable { font: inherit; font-size: 11px; color: var(--ph); background: transparent; border: 1px dashed var(--ph-dim); border-radius: 3px; padding: 2px 8px; cursor: pointer; letter-spacing: 0.4px; }
.cd-reenable:hover { border-color: var(--ph); border-style: solid; }
.cd-rec { padding: 7px 10px; border-radius: 3px; background: var(--ph-faint); border: 1px solid var(--ph-faint); border-left: 2px solid var(--ph-dim); margin-bottom: 7px; }
.cd-rec.pinned { border-left-color: var(--gold); }
.cd-rec.cd-observed { border-left-color: var(--ph-mid, var(--ph-dim)); border-left-style: dashed; }   /* GROWTH Tier 1: a station-observed (study) belief reads distinct from a Commander-authored one */
.cd-body { color: var(--ph-bright); font-size: 14px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; }
.cd-meta { margin-top: 6px; font-size: 11px; color: var(--ph-dim); letter-spacing: 0.3px; }
.cd-observed-tag { display: inline-block; margin-right: 6px; padding: 0 5px; font-size: 10px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink); background: var(--ph-dim); border-radius: 2px; vertical-align: 1px; }
.cd-acts { margin-top: 8px; }
/* guided starter chips (empty dims + the add editor): tap → the editor opens with the sentence started */
.cd-starters { display: flex; flex-wrap: wrap; gap: 5px; margin: 2px 0 7px; }
.cd-starter { font: inherit; font-size: 11px; letter-spacing: 0.4px; cursor: pointer; color: var(--ph-dim); background: transparent; border: 1px dashed var(--ph-faint); border-radius: 3px; padding: 2px 8px; }
.cd-starter:hover { color: var(--ph); border-color: var(--ph-dim); border-style: solid; }
.cd-add { margin: 2px 0 4px; }
.cd-addbtn { font: inherit; font-size: 12px; color: var(--ph-dim); background: transparent; border: 1px dashed var(--ph-dim); border-radius: 3px; padding: 3px 10px; cursor: pointer; letter-spacing: 0.5px; }
.cd-addbtn:hover { color: var(--ph); border-color: var(--ph); }
.cd-edit { width: 100%; box-sizing: border-box; font: inherit; font-size: 14px; color: var(--ph-bright); background: var(--ink); border: 1px solid var(--ph-dim); border-radius: 2px; padding: 4px 6px; min-height: 44px; resize: vertical; }
.cd-edit:focus { outline: none; border-color: var(--ph); }
.cd-actions-row { margin: 10px 0 2px; }
.cd-interview { width: 100%; font: inherit; font-size: 13px; letter-spacing: 0.6px; cursor: pointer; color: var(--ink); background: var(--ph); border: 1px solid var(--ph); border-radius: 3px; padding: 8px 12px; }
.cd-interview:hover { background: var(--ph-bright); border-color: var(--ph-bright); box-shadow: 0 0 10px var(--ph-glow2); }

/* THE AWAKENING — tappable suggestion chips in COMMS (the gamified first-meeting onboarding) */
.choice-row {
  display: flex; flex-wrap: wrap; gap: 6px; margin: 3px 0 6px;
  position: relative; z-index: 2; max-width: 100%;
}
.choice {
  font: inherit; font-size: 14px; letter-spacing: 0.5px; cursor: pointer;
  color: var(--ph); background: linear-gradient(180deg, rgba(var(--ph-rgb),.07), rgba(var(--ph-rgb),.02));
  border: 1px solid var(--ph-dim); padding: 5px 11px; border-radius: 4px; text-shadow: 0 0 5px var(--ph-glow2);
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.1), 0 1px 0 rgba(0,0,0,.35);
  transition: background-color .1s, color .1s, border-color .1s, box-shadow .1s, transform .1s;
}
.choice:hover, .choice:focus-visible {
  color: var(--ph-bright); border-color: var(--ph); background: var(--ph-faint); outline: none;
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.15), 0 0 9px var(--ph-glow2), 0 1px 0 rgba(0,0,0,.35);
}
.choice:active { transform: translateY(1px); }
/* T3.9 — the SECONDARY bug-report affordance under a failed turn: subdued so it never competes with the primary
   recovery chip (dashed, dimmer, smaller). Brightens to a normal chip on hover so it stays discoverable. */
.choice.quiet { font-size: 12px; letter-spacing: 0.3px; border-style: dashed; border-color: var(--ph-faint); color: var(--ph-dim); text-shadow: none; padding: 3px 8px; background: transparent; box-shadow: none; }
.choice.quiet:hover, .choice.quiet:focus-visible { color: var(--ph-bright); border-color: var(--ph-dim); border-style: solid; }
.choice.quiet:disabled { cursor: default; opacity: 0.7; }
/* TASK BRIEF — the host-validated recommended default on a task question: gold-marked so one tap accepts
   the suggestion. Rendered ONLY when the durable brief carries a REAL recommendation (the brief_ask path);
   marker-path questions carry no MODEL recommendation and stay plain chips there — but they may still wear a
   GROUNDED chip, which is derived from the Commander's own answered history rather than from the unvalidated
   question, so it cannot be fabricated. Accent via --gold-rgb, never a literal amber. */
.choice.suggested { color: var(--gold); border-color: var(--gold); text-shadow: 0 0 5px rgba(var(--gold-rgb), .45); }
.choice.suggested:hover, .choice.suggested:focus-visible {
  color: var(--gold); border-color: var(--gold); background: rgba(var(--gold-rgb), .08);
  box-shadow: inset 0 1px 0 rgba(var(--gold-rgb),.15), 0 0 9px rgba(var(--gold-rgb), .35), 0 1px 0 rgba(0,0,0,.35);
}
/* MULTI-SELECT task-question chips: toggled = matte fill (never a glow); the confirm chip speaks gold
   like every other primary, and stays quiet until at least one pick exists. */
.choice.sel {
  color: var(--ph-bright); border-color: var(--ph-bright);
  background: linear-gradient(180deg, rgba(var(--ph-rgb),.34), rgba(var(--ph-rgb),.24));
  box-shadow: inset 0 0 0 1px rgba(var(--ph-bright-rgb),.22), 0 1px 0 rgba(0,0,0,.35);
}
.choice.confirm { color: var(--ph-bright); border-color: var(--gold);
  background: linear-gradient(180deg, rgba(var(--gold-rgb),.20), rgba(var(--gold-rgb),.12)); }
.choice.confirm:disabled { color: var(--ph-dim); border-color: rgba(var(--ph-rgb), .28);
  background: none; cursor: default; box-shadow: none; text-shadow: none; }
.tq-reason { font-size: 13px; color: var(--ph-dim); letter-spacing: 0.3px; margin-top: 3px; }
/* a GROUNDED suggestion is provable (the Commander's own answered history, with a count), so it earns the
   gold voice the ★ chip wears; the model's unprovable guess stays in the dim register above. */
.tq-reason.grounded { color: var(--gold); }
.task-conversation { border:1px solid var(--ui-edge); border-left:3px solid var(--gold); border-radius:var(--r-sm); padding:16px 18px; margin:8px 0; background:var(--ui-face),var(--panel2); box-shadow:inset 0 1px 0 var(--ui-highlight),0 3px 10px rgba(0,0,0,.2); min-width:0; }
.task-conversation .tc-caption { color:var(--gold); font-size:11px; letter-spacing:1.7px; margin-bottom:8px; }
.task-conversation .tc-question { color:var(--ph-bright); font:inherit; font-size:18px; line-height:1.4; margin:0 0 10px; text-wrap:pretty; }
.task-conversation .tc-reason { color:var(--ph-dim); font-size:13px; line-height:1.5; margin:0 0 12px; }
.task-conversation .tc-understanding { border:1px solid var(--ui-edge); border-radius:var(--r-sm); background:var(--panel); box-shadow:var(--ui-well); padding:10px 12px; margin:14px 0; font-size:13px; }
.task-conversation summary { color:var(--ph); cursor:pointer; }
.task-conversation .tc-fact { margin:12px 0; }
.task-conversation .tc-fact p { margin:3px 0; }
.task-conversation .tc-dimension { color:var(--gold); font-size:11px; text-transform:uppercase; letter-spacing:1px; }
.task-conversation .tc-quote { color:var(--ph-dim); border-left:1px solid var(--ph-dim); margin:6px 0; padding-left:10px; font-size:12px; overflow-wrap:anywhere; }
.task-conversation .tc-next { color:var(--ph); margin-bottom:2px; }
.task-conversation .tc-sample { padding:12px; margin:12px 0; border:1px solid var(--ui-edge); border-radius:var(--r-sm); background:var(--panel); box-shadow:var(--ui-well); }
.task-conversation .tc-sample-text, .task-conversation .tc-answer-sent { white-space:pre-wrap; overflow-wrap:anywhere; line-height:1.5; margin-top:7px; }
.task-conversation .tc-answer-label { display:block; color:var(--ph-dim); font-size:12px; }
.task-conversation .tc-answer { display:block; box-sizing:border-box; width:100%; min-height:90px; resize:vertical; margin:8px 0 12px; padding:12px; border:1px solid var(--ui-edge); border-radius:var(--r-sm); background:var(--panel); box-shadow:var(--ui-well); color:var(--text); font:inherit; line-height:1.5; }
.task-conversation .tc-answer:focus { outline:1px solid var(--gold); outline-offset:2px; }
.task-conversation .tc-actions, .task-conversation .tc-shortcuts { display:flex; flex-wrap:wrap; gap:7px; margin:8px 0; }
.task-conversation .tc-shortcut { white-space:normal; text-align:left; }
.task-conversation .tc-skip { color:var(--ph-dim); }
.task-conversation .tc-feedback { color:var(--gold); font-size:12px; overflow-wrap:anywhere; }
.task-conversation .tc-feedback:empty { display:none; }
.task-conversation .tc-hint { color:var(--ph-dim); font-size:11px; line-height:1.5; }
.task-conversation.tc-answered { padding:8px 12px; border-left-color:var(--ph-dim); }
.task-conversation.tc-answered > :not(.tc-receipt) { display:none; }
.task-conversation .tc-receipt { font-size:13px; overflow-wrap:anywhere; }
@media(max-width:600px) { .task-conversation { padding:12px; } .task-conversation .tc-question { font-size:16px; } }
/* the typed-answer escape hatch: quieter than the suggestion (it is an affordance note, not content), but
   present — the chip row alone reads as "pick exactly one" when free text has always been accepted. */
.tq-hint { font-size: 12px; color: var(--ph-dim); letter-spacing: 0.3px; margin-top: 4px; opacity: .75; }

/* TASTE EXTRACTION "my read" card (chat.js briefReadCard) — the mid-run announce-and-act read.
   REGISTER (2026-07-24): this is a CARD, not a work-log micro-line. It states what the run believes it was
   asked to do and carries the ONLY mid-run correction affordance, so it wears the in-log card language
   .cmsg.consent/.cmsg.turnin already established (2px rail + inset wash + 15px) — but in PHOSPHOR, never
   gold: gold in this log means "a decision is on you", and this card is explicitly non-blocking. It rides
   .cmsg.tool for the beat plumbing (no copy button, no speaker header), and .cmsg.tool typesets 13.5px/dim
   — every size + colour rule below is what lifts it back out of that whisper register.
   The assumption chips are CORRECTABLE guesses, so they wear the quiet dashed chip look (never the
   bright primary .choice — tapping one starts a correction, it doesn't accept anything). */
.cmsg.tb-read {
  font-size: 15px; margin: 7px 0; padding: 9px 12px 10px;
  border-left: 2px solid var(--ph); border-radius: 0 5px 5px 0;
  background: linear-gradient(180deg, rgba(var(--ph-rgb),.07), rgba(var(--ph-rgb),.02));
  box-shadow: inset 0 1px 0 rgba(var(--ph-bright-rgb),.07), 0 0 10px var(--ph-glow2);
}
.tb-read-cap { font-size: 11px; letter-spacing: 2px; text-transform: uppercase; color: var(--ph-dim); }
.tb-read-cap2 { margin-top: 8px; }   /* the second caption ("assuming — …") opens a new block, so it needs air above */
/* the objective is the headline — the one line the Commander must be able to disagree with at a glance */
.tb-read-head {
  font-size: 15px; line-height: 1.3; letter-spacing: 0.3px; margin-top: 2px;
  color: var(--ph-bright); text-shadow: 0 0 5px var(--ph-glow2); overflow-wrap: anywhere;
}
/* meta = one ROW per field with the key as an INLINE prefix. A fixed key column looked tidier but at the
   default 360px COMMS width it stole ~74px from values that are full clauses, costing more wrapped lines
   than the column saved; inline keys keep the same scannable one-field-per-row read for 19px less height. */
.tb-read-meta { margin-top: 5px; display: flex; flex-direction: column; gap: 2px; }
.tb-read-mrow { font-size: 13px; line-height: 1.4; }
.tb-read-mk { color: var(--ph-dim); letter-spacing: 0.5px; opacity: .85; }
.tb-read-mv { color: var(--text); overflow-wrap: anywhere; }
.tb-read-assumps { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 2px; max-width: 100%; }
.tb-read-assump {
  font: inherit; font-size: 13px; letter-spacing: 0.3px; line-height: 1.25; cursor: pointer;
  text-align: left; max-width: 100%; overflow-wrap: anywhere;
  color: var(--ph); background: transparent; text-shadow: none;
  border: 1px dashed var(--ph-dim); border-radius: 4px; padding: 4px 9px;
  transition: color .1s, border-color .1s, background-color .1s;
}
.tb-read-assump:hover, .tb-read-assump:focus-visible {
  color: var(--ph-bright); border-color: var(--ph); border-style: solid; background: var(--ph-faint); outline: none;
}
/* a retired card (correction already folded in, or the run ended): the chips must stop advertising an action
   they can no longer perform — the input they wrote into is gone. */
.tb-read-assump:disabled { opacity: .45; cursor: default; }
.tb-read-assump:disabled:hover { color: var(--ph); border-color: var(--ph-dim); border-style: dashed; background: transparent; }
.tb-read-fix { margin-top: 7px; }
/* SPECIFICITY, not preference: the base field rule is `input:not([type])` (0,1,1) and this input carries no
   type attribute, so a bare `.tb-read-in` (0,1,0) LOSES — the correction box silently rendered at the 19px
   form scale, making the emptiest element in the card its loudest. Scope to the card to win at (0,3,0). */
.cmsg.tb-read .tb-read-in { width: 100%; font-size: 14px; padding: 6px 9px; }   /* box-sizing is global (style.css `* {}`) */
.tb-read-ack { font-size: 13px; line-height: 1.4; color: var(--ph); letter-spacing: 0.3px; }
.tb-read-ack.err { color: var(--bad, #d66); }
.tb-read-ack.closed { color: var(--ph-dim); }

/* ---- SPENT BRIEF (2026-07-27): the read's run has resolved, so the card stops being a control surface and
   becomes transcript. It recedes to the passive card weight (the live one keeps the lit phosphor edge — that
   edge is what says "this is still steerable"), and folds to caption + objective. Click the caption to get
   the full read back; nothing is deleted. Same fold idiom as the resolved run line's .run-fold. ---- */
.cmsg.tb-read.tb-read-spent { border-left-color: var(--ph-dim); box-shadow: none; background: rgba(var(--ph-rgb), .03); }
.tb-read-spent .tb-read-cap { cursor: pointer; display: flex; align-items: center; gap: 6px; }
.tb-read-spent .tb-read-cap:hover, .tb-read-spent .tb-read-cap:focus-visible { color: var(--ph); outline: 0; }
.tb-read-chev { display: none; font-size: 9px; line-height: 1; transition: transform var(--t-fast, 120ms) var(--ease-soft, ease); }
.tb-read-spent .tb-read-chev { display: inline-block; }
.tb-read-spent:not(.folded) .tb-read-chev { transform: rotate(90deg); }
.tb-read-spent.folded .tb-read-head { font-size: 13px; color: var(--ph); text-shadow: none;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.tb-read-spent.folded .tb-read-meta,
.tb-read-spent.folded .tb-read-cap2,
.tb-read-spent.folded .tb-read-assumps,
.tb-read-spent.folded .tb-read-fix { display: none; }

/* ===== THE DIALOGUE MODE (dialogue.js) — Fallout-style focused first-run conversation =====
   Mounts as a child overlay of #chat-panel, covering COMMS while the awakening/tutorial runs. One short
   line, selectable options, and a custom-text box — typed input is captured deterministically. */
#chat-panel.fnv-host { position: relative; }
.fnv-dialogue {
  position: absolute; inset: 0; z-index: 30; display: flex; flex-direction: column;
  padding: 18px 18px 16px; gap: 12px; box-sizing: border-box;
  background:
    radial-gradient(ellipse 130% 90% at 50% 0%, var(--ph-glow2), transparent 55%),
    linear-gradient(180deg, rgba(0,0,0,.94), rgba(0,0,0,.97));
  border-left: 2px solid var(--ph-dim);
  opacity: 0; transition: opacity .35s ease;
}
.fnv-dialogue.show { opacity: 1; }
.fnv-dialogue.no-anim { transition: none; }
/* A body-level fixed panel, so its viewport caps carry the --sn-unzoom reciprocal (see style.css
   `.term`): a bare 70vh renders as 101vh at HUGE, and this panel is anchored to the BOTTOM, so the
   overflow leaves by the top — it would grow past the frame exactly where the speaker line is.
   Measured at 1440x900: 874px at 145% against an 18px bottom offset, i.e. flush against 0. */
.fnv-floating { position: fixed; right: 18px; bottom: 18px; width: min(440px, calc(92vw * var(--sn-unzoom, 1))); height: auto; max-height: calc(70vh * var(--sn-unzoom, 1)); border: 2px solid var(--ph-dim); border-radius: var(--r-lg); }
.fnv-speaker {
  color: var(--ph-bright); font-size: 13px; letter-spacing: 3px; text-transform: uppercase;
  text-shadow: 0 0 8px var(--ph-glow2); padding-bottom: 8px; border-bottom: 1px solid var(--ph-dim); flex: 0 0 auto;
}
.fnv-line {
  flex: 1 1 auto; overflow-y: auto; color: var(--ph-bright); font-size: 18px; line-height: 1.5;
  text-shadow: 0 0 6px var(--ph-glow2); white-space: pre-wrap; min-height: 2.4em;
  display: flex; align-items: center;
}
/* the read-gate cue: hidden until a narration beat finishes typing; the beat then waits for the
   Commander, so the blink is an invitation, not a countdown. Blink dies under reduced motion. */
.fnv-more { display: none; flex: 0 0 auto; color: var(--ph-dim); font-size: 14px; letter-spacing: 1px; user-select: none; }
.fnv-more.show { display: block; animation: fnv-more-blink 1.2s steps(2, start) infinite; }
.fnv-dialogue.no-anim .fnv-more.show { animation: none; }
.fnv-dialogue.fnv-wait { cursor: pointer; }
@keyframes fnv-more-blink { 50% { opacity: .3; } }
/* the ink stamp: a quiet receipt that an answer just landed in the operating file. Dim phosphor,
   smaller than the beat line, fades in/out — a ledger note, never a toast. */
.fnv-ink {
  flex: 0 0 auto; color: var(--ph-dim); font-size: 13px; letter-spacing: .5px; user-select: none;
  opacity: 0; transition: opacity .5s ease; min-height: 1.2em; text-shadow: 0 0 4px var(--ph-glow2);
}
.fnv-ink.show { opacity: 1; }
.fnv-dialogue.no-anim .fnv-ink { transition: none; }
.fnv-opts { flex: 0 0 auto; display: flex; flex-direction: column; gap: 7px; }
.fnv-opt {
  display: flex; align-items: baseline; gap: 9px; width: 100%; text-align: left;
  font: inherit; font-size: 16px; letter-spacing: 0.4px; cursor: pointer;
  color: var(--ph); background: linear-gradient(180deg, rgba(var(--ph-rgb),.06), rgba(var(--ph-rgb),.015));
  border: 1px solid var(--ph-dim); padding: 9px 13px; border-radius: 5px; text-shadow: 0 0 5px var(--ph-glow2);
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.09), 0 1px 0 rgba(0,0,0,.35);
  transition: background-color .1s, color .1s, border-color .1s, box-shadow .1s;
}
.fnv-opt:hover, .fnv-opt:focus-visible {
  color: var(--ph-bright); border-color: var(--ph); background: var(--ph-faint); outline: none;
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.14), 0 0 10px var(--ph-glow2), 0 1px 0 rgba(0,0,0,.35);
}
.fnv-opt .fnv-num { color: var(--ph-dim); font-size: 14px; flex: 0 0 auto; min-width: 16px; }
.fnv-opt:hover .fnv-num { color: var(--ph); }
.fnv-opt.custom { border-style: dashed; }
.fnv-opt.skip { color: var(--ph-dim); }
.fnv-opt.skip:hover { color: var(--ph); }
.fnv-custom { display: flex; align-items: center; gap: 6px; }
.fnv-custom-in { flex: 1 1 auto; width: auto; min-width: 0; font-size: 16px; }
.fnv-custom-send {
  font-family: inherit; font-size: 18px; color: var(--ink);
  background: linear-gradient(180deg, var(--ph-bright) -40%, var(--ph) 60%);
  border: 1px solid var(--ph-bright); border-radius: 5px; padding: 7px 13px; cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 0 rgba(0,0,0,.45);
  transition: background-color .12s, box-shadow .12s;
}
.fnv-custom-send:hover { background: var(--ph-bright); border-color: var(--ph-bright); box-shadow: inset 0 1px 0 rgba(255,255,255,.3), 0 0 12px var(--ph-glow); }
.fnv-custom-back { font-family: inherit; font-size: 14px; color: var(--ph-dim); background: transparent; border: 1px solid var(--ph-dim); border-radius: 5px; padding: 7px 11px; cursor: pointer; transition: color .12s, border-color .12s; }
.fnv-custom-back:hover { color: var(--ph); border-color: var(--ph); }

#chat-inputrow {
  display: flex; align-items: center; gap: 6px; padding: 8px; border-top: 2px solid var(--ph-dim);
  position: relative; z-index: 4; background: var(--panel);
}   /* position anchors the slash palette above the row */
#chat-inputrow .prompt { color: var(--ph); }

/* SLASH COMMANDS — a filterable palette floating directly above the input. Phosphor list, selected row lit. */
.chat-slash {
  position: absolute; left: 6px; right: 6px; bottom: calc(100% + 4px); z-index: 30;
  max-height: 244px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--ph); border-radius: 6px;
  box-shadow: 0 -6px 20px rgba(0,0,0,.6), 0 0 0 2px rgba(0,0,0,.5);
}
.chat-slash[hidden] { display: none; }
.slash-head { padding: 4px 10px; color: var(--ph-dim); font-size: 11px; letter-spacing: 1.5px; border-bottom: 1px solid var(--ph-faint); position: sticky; top: 0; background: var(--panel); }
.slash-item { display: flex; align-items: baseline; gap: 9px; padding: 5px 10px; cursor: pointer; }
.slash-item .slash-name { color: var(--ph-bright); white-space: nowrap; flex: 0 0 auto; }
.slash-item .slash-desc { color: var(--ph-dim); font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.slash-item.sel { background: var(--ph-faint); }
.slash-item.sel .slash-desc { color: var(--ph); }
#chat-input { flex: 1; width: auto; min-width: 0; border: none; background: transparent; padding: 4px 2px; }
#chat-input:focus { box-shadow: none; }

/* TYPE-AHEAD queue — follow-ups typed while the agent is working, shown as pills above the input row and
   auto-sent in order as the stream frees. The strip collapses (no padding) when nothing is queued. */
.chat-queued { display: flex; flex-wrap: wrap; gap: 5px; padding: 0 8px; }
.chat-queued:empty { display: none; }
.chat-queued:not(:empty) { padding-top: 7px; }
.queued-pill {
  display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
  font-size: 13px; color: var(--ph); background: var(--panel2);
  border: 1px solid var(--ph-dim); border-radius: 4px; padding: 2px 5px 2px 7px;
}
.queued-pill .queued-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 210px; opacity: .9; }
/* .queued-x composes .x-btn (danger hover) — ghost delta only */
.queued-x { border: none; background: transparent; color: var(--ph-dim); font-size: 12px; padding: 0 2px; border-radius: 3px; }

/* INTERRUPT — the HALT KEY. The one danger control in the composer row, shown only while the displayed
   stream is running (chat.js toggles [hidden] via updateControls), so it has to read as ARMED without
   smearing. What it replaced (and why): a borderless, radius-0 button wearing a pulsing `box-shadow`
   painted a soft red RECTANGLE with no edge — a bleed, not a control — behind a ◉ ring-and-dot glyph
   that reads RECORD in every UI vocabulary there is, at 16px inside a 38x34 box it did not fill.
   The shape carries the meaning now: ONE solid halt square, 22px, nothing drawn around it — the plainest
   thing that can only mean stop, sitting in the same bare-glyph register as the paperclip and mic beside
   it. The live-run signal is a FLASH on the glyph, on the station's own terminal cadence: 1s `steps(1)`,
   the same hard snap as the COMMS caret (@keyframes blink) — no easing, because RobCo terminals snap.
   It flashes to a DIM FLOOR, never to 0: this is a control you are reaching for mid-run, and a target
   that blinks out from under the cursor is a target you misclick. Hover/press take the RobCo invert
   block (solid field, --ink glyph) like the voice chips, and HOLD the glyph lit — nothing you are
   actively clicking should still be blinking. */
.chat-stop {
  flex: 0 0 auto; font-size: 22px; line-height: 0; color: var(--bad);
  background: transparent; border: none; border-radius: 0;
  width: 38px; height: 34px; padding: 0; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; justify-content: center;
  transition: none;
}
.chat-stop[hidden] { display: none; }
.chat-stop svg {
  width: 1em; height: 1em; display: block; pointer-events: none;
  animation: stop-flash 1s steps(1) infinite;
}
.chat-stop:hover, .chat-stop:active { background: var(--bad); color: var(--ink); }
.chat-stop:hover svg, .chat-stop:active svg { animation: none; opacity: 1; }
@keyframes stop-flash { 50% { opacity: .22; } }
/* motion off: hold the square solid — the control must still read at full strength */
@media (prefers-reduced-motion: reduce) { .chat-stop svg { animation: none; } }

/* ---------- voice controls: a matched set of bordered phosphor chips in the COMMS input row.
   Defined dark boxes (not bare glyphs) so they punctuate the amber UI and read as real controls;
   active states invert/recolor so the live one stands out at a glance. ---------- */
/* ROBCO INVERT register (FNV terminal) — the controls are bare phosphor glyphs drawn ON the
   console glass, not widgets. "Selected" is the terminal selection block: an INSTANT solid
   phosphor invert (ink glyph on lit phosphor) with bloom. No borders, no rounding, no easing —
   RobCo terminals snap. Persistent active states (voice-mode live, dock open) hold the block. */
.mic-btn, .voice-toggle, .model-dock-toggle {
  flex: 0 0 auto; font-size: 16px; line-height: 0; color: var(--ph);
  background: transparent; border: none; border-radius: 0;
  width: 38px; height: 34px; padding: 0; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; justify-content: center;
  transition: none;
}
.mic-btn svg, .voice-toggle svg { width: 1em; height: 1em; display: block; pointer-events: none; filter: drop-shadow(0 0 3px var(--ph-glow2)); }
.mic-btn:hover, .voice-toggle:hover, .model-dock-toggle:hover {
  color: var(--ink); background: var(--ph);
  box-shadow: 0 0 16px var(--ph-glow);
  text-shadow: none;
}
.mic-btn:hover svg, .voice-toggle:hover svg { filter: none; }
.mic-btn:active, .voice-toggle:active, .model-dock-toggle:active { background: var(--ph-bright); }
.mic-btn:focus-visible, .voice-toggle:focus-visible, .model-dock-toggle:focus-visible { outline: 1px solid var(--ph-bright); outline-offset: 2px; }

.model-dock-toggle {
  width: auto; min-width: 54px; max-width: 172px; height: 34px; padding: 0 9px; gap: 6px;
  font-size: 13px; line-height: 1; letter-spacing: 0; text-shadow: 0 0 5px var(--ph-glow2);
  position: relative; overflow: visible;
}
/* hover invert swallows the chip's inner readouts too — everything goes ink on the block */
.model-dock-toggle:hover .model-dock-model-name,
.model-dock-toggle:hover .model-dock-effort-chip,
.model-dock-toggle:hover .model-dock-effort-chip::before { color: var(--ink); text-shadow: none; }
.model-dock-toggle.on {
  color: var(--ink); background: var(--ph);
  box-shadow: 0 0 16px var(--ph-glow);
  text-shadow: none;
}
/* NO SIGIL: the chip carries no icon — the model NAME is the readout, so it gets the type size. */
.model-dock-effort-chip {
  display: inline-block; max-width: 40px; overflow: hidden; text-overflow: clip;
  white-space: nowrap; font-size: 10px; letter-spacing: 0.6px; color: var(--ph-bright);
}
/* resting-state model short-name: legible at rest (full phosphor, not dimmed) so the chip reads
   "MODEL · TIER" without a click. With the sigil gone this IS the control's face, so it carries
   the chip's own 13px type — the effort chip stays small so the model name reads first.
   Still capped + truncated so the composer never grows. */
.model-dock-model-name {
  display: inline-block; max-width: 108px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: 13px; letter-spacing: 0.5px;
  color: var(--ph); line-height: 1;
}
.model-dock-model-name.empty { color: var(--ph-dim); opacity: 0.7; }
.model-dock-toggle.on .model-dock-model-name,
.model-dock-toggle.on .model-dock-effort-chip { color: var(--ink); }
.model-dock-toggle .model-dock-effort-chip::before {
  content: '·'; color: var(--ph-dim); margin: 0 3px 0 1px; font-weight: 400;
}
.model-dock-toggle.on .model-dock-effort-chip::before { color: var(--ink); opacity: .6; }
/* CRT tooltip — a molded phosphor card above the chip. pointer-events:none so it never eats
   the click that opens the dock; shown on hover AND keyboard focus (a11y); killed on open. */
.model-dock-tip {
  position: absolute; right: 0; bottom: calc(100% + 9px); z-index: 90;
  width: max-content; max-width: 244px; pointer-events: none;
  padding: 8px 11px; text-align: left; cursor: default;
  color: var(--ph); background: var(--panel);
  border: 2px solid var(--ph); border-radius: 4px;
  box-shadow: 0 0 0 2px var(--ink), 0 0 18px var(--ph-glow2);
  opacity: 0; transform: translateY(3px);
  transition: opacity .12s var(--ease-out), transform .12s var(--ease-out);
}
.model-dock-tip[hidden] { display: none; }
.model-dock-tip.show { opacity: 1; transform: translateY(0); }
.model-dock-tip::after {
  content: ''; position: absolute; right: 16px; top: 100%;
  border: 6px solid transparent; border-top-color: var(--ph);
}
.mdt-row { font-size: 12px; line-height: 1.35; white-space: normal; }
.mdt-model { color: var(--ph-bright); font-size: 13px; }
.mdt-prov { color: var(--gold); font-size: 10px; letter-spacing: 0.5px; margin-top: 1px; }
.mdt-sep { height: 1px; background: var(--ph-dim); opacity: 0.5; margin: 6px 0; }
.mdt-tier { color: var(--ph); }
.mdt-tier b { color: var(--ph-bright); }
.mdt-mean { color: var(--ph-dim); font-size: 11px; margin-top: 1px; }
.mdt-hint { color: var(--ph-dim); font-size: 10px; letter-spacing: 0.5px; margin-top: 6px; opacity: 0.85; }
.model-dock {
  position: absolute; right: 8px; bottom: calc(100% + 8px); z-index: 80;
  width: min(322px, calc(100vw - 34px)); max-height: min(70vh, 414px);
  display: flex; flex-direction: column; gap: 7px; padding: 9px;
  color: var(--ph); background: var(--panel);
  border: 2px solid var(--ph); border-radius: 4px;
  box-shadow: 0 0 0 2px var(--ink), 0 0 22px var(--ph-glow2);
}
.model-dock[hidden] { display: none !important; }
.model-dock-head {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px; align-items: center;
  border-bottom: 1px solid var(--ph-dim); padding-bottom: 6px; min-width: 0;
}
.model-dock-provider {
  color: var(--gold); font-size: 11px; white-space: nowrap;
}
.model-dock-current-model {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ph-bright); font-size: 13px; text-align: right;
}
.model-dock-search {
  width: 100%; min-width: 0; box-sizing: border-box; padding: 7px 9px;
  color: var(--ph-bright); background: var(--ink); border: 1px solid var(--ph-dim);
  border-radius: 4px; outline: none; font: inherit;
  box-shadow: inset 0 2px 5px rgba(0,0,0,.5);
  transition: border-color .12s, box-shadow .12s;
}
.model-dock-search:focus { border-color: var(--ph); box-shadow: inset 0 2px 5px rgba(0,0,0,.5), 0 0 9px var(--ph-glow2); }
.model-dock-search::placeholder { color: var(--ph-dim); opacity: 1; }
.model-dock-efforts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(42px, 1fr)); gap: 4px;
}
.model-dock-effort {
  min-width: 0; height: 28px; padding: 0 3px; cursor: pointer;
  color: var(--ph); background: transparent; border: 1px solid var(--ph-dim);
  border-radius: 3px; font: inherit; font-size: 10px; overflow: hidden; text-overflow: clip; white-space: nowrap;
}
.model-dock-effort:hover { color: var(--ph-bright); border-color: var(--ph); background: var(--ph-faint); }
.model-dock-effort.sel { color: var(--ink); background: var(--ph); border-color: var(--ph); }
.model-dock-list {
  min-height: 126px; max-height: 236px; overflow: auto; padding-right: 2px;
  border-top: 1px solid var(--ph-faint); border-bottom: 1px solid var(--ph-faint);
}
.model-dock-group {
  color: var(--gold); font-size: 11px; padding: 8px 3px 3px;
  border-bottom: 1px solid var(--ph-faint);
}
.model-dock-row {
  width: 100%; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px; align-items: center; padding: 7px 6px; cursor: pointer; text-align: left;
  color: var(--ph); background: transparent; border: 0; border-bottom: 1px solid var(--ph-faint);
  font: inherit;
}
.model-dock-row:hover { color: var(--ph-bright); background: var(--ph-faint); }
.model-dock-row.sel { color: var(--ink); background: var(--ph); }
/* E4: offline fallback — the live catalog was unreachable, these are a built-in unverified seed list.
   Amber group tag + dimmed rows so they never read as a verified live catalog. */
.model-dock-group-offline { color: var(--warn); font-size: 10px; font-style: normal; letter-spacing: 0.5px; }
.model-dock-row.fallback .model-dock-row-name { color: var(--ph-dim); }
.model-dock-row.fallback.sel .model-dock-row-name { color: var(--ink); }
.model-dock-row-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px;
}
.model-dock-row-effort {
  min-width: 38px; text-align: right; font-size: 10px;}
.model-dock-empty {   /* dim grade + size via the grouped empty rule (motion.css) */
  padding: 18px 6px; text-align: center;
}
/* inline no-key warning inside the dock — one tap opens SETTINGS. Only rendered when the active provider
   truly has no stored credential (truthful telemetry), so it can't lie about a connection that exists. */
.model-dock-keywarn {
  width: 100%; display: flex; gap: 7px; align-items: flex-start; text-align: left; cursor: pointer;
  margin: 2px 0 6px; padding: 7px 8px; font: inherit; font-size: 12px; line-height: 1.35;
  color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent);
  border: 1px solid var(--warn); border-radius: 3px;
}
.model-dock-keywarn:hover { background: color-mix(in srgb, var(--warn) 22%, transparent); }
.model-dock-keywarn .mdw-glyph { flex: 0 0 auto; }
.model-dock-keywarn .mdw-txt { min-width: 0; }
/* resting chip flags a missing key by burning warn-amber so it's noticeable before the dock is even opened */
.model-dock-toggle.needs-key { color: var(--warn); text-shadow: 0 0 6px color-mix(in srgb, var(--warn) 50%, transparent); }
.model-dock-toggle.needs-key .model-dock-model-name { color: var(--warn); }
.model-dock-foot {
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
}
.model-dock-foot button {
  min-width: 0; padding: 7px 5px; cursor: pointer;
  color: var(--ph); background: linear-gradient(180deg, color-mix(in srgb, var(--ph) 7%, var(--panel2)), var(--panel2));
  border: 1px solid var(--ph-dim);
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.1), 0 1px 0 rgba(0,0,0,.35);
  border-radius: 4px; font: inherit; font-size: 11px;}
.model-dock-foot button:hover { color: var(--ph-bright); border-color: var(--ph); background: var(--ph-faint); }

@media (max-width: 780px) {
  .model-dock-toggle { width: auto; min-width: 44px; max-width: 118px; padding: 0 5px; gap: 3px; }
  /* on the narrow composer the chip can only afford ONE readout. With no sigil the model name IS the
     control, so the TIER is what drops here (tooltip + dock still name it); the name only tightens. */
  .model-dock-model-name { max-width: 74px; font-size: 12px; letter-spacing: 0.3px; }
  .model-dock-effort-chip { display: none; }
  .model-dock-tip { max-width: min(74vw, 244px); }
  .model-dock { position: fixed; left: 8px; right: 8px; bottom: 120px; width: auto; max-height: min(62vh, 410px); }
  .model-dock-list { max-height: 220px; }
}

/* mic listening: red border + glow pulse so it's unmistakable the mic is hot */
/* mic HOT = the held selection block in red: solid inverted --bad with a breathing bloom */
.mic-btn.rec {
  color: var(--ink); background: var(--bad);
  animation: mic-pulse 1s infinite;
}
.mic-btn.rec svg { filter: none; }
@keyframes mic-pulse { 0%,100% { box-shadow: 0 0 4px var(--bad); } 50% { box-shadow: 0 0 16px var(--bad); } }

/* agent-voice MUTED: red so "the agent is silenced" is impossible to miss — a molded red-tinted
   chip (same bevel register as the rest of the cluster), not a bare red outline */
/* agent-voice MUTED: the glyph itself burns red on the glass; hover = red selection block */
.voice-toggle.off { color: var(--bad); }
.voice-toggle.off svg { filter: drop-shadow(0 0 3px color-mix(in srgb, var(--bad) 35%, transparent)); }
.voice-toggle.off:hover {
  color: var(--ink); background: var(--bad);
  box-shadow: 0 0 14px color-mix(in srgb, var(--bad) 55%, transparent);
}
.voice-toggle.off:hover svg { filter: none; }
/* agent-voice speaking: amber glow pulse on the chip */
.voice-toggle.speaking { animation: chip-spk .8s infinite; }
.voice-live[aria-pressed="true"] { color: var(--ink); background: var(--ph); box-shadow: 0 0 16px var(--ph-glow); }
.voice-live[aria-pressed="true"] svg { filter: none; }

/* Recovery candidate picker: explicit station generations, never an OS-native list control. */
.lineage-candidates { display:grid; gap:7px; margin-top:12px; }
.lineage-candidate { display:grid; grid-template-columns:1fr auto; gap:2px 12px; width:100%; padding:8px 10px; text-align:left; color:var(--ph-dim); border:1px solid color-mix(in srgb, var(--ph) 35%, transparent); background:color-mix(in srgb, var(--ink) 88%, var(--ph)); }
.lineage-candidate b { color:var(--ph); letter-spacing:.06em; }
.lineage-candidate span { grid-column:1/-1; overflow-wrap:anywhere; }
.lineage-candidate small { grid-column:1/-1; color:var(--ph-dim); }
.lineage-candidate.on, .lineage-candidate[aria-pressed="true"] { color:var(--ink); background:var(--ph); box-shadow:inset 0 0 0 2px var(--ink), 0 0 12px var(--ph-glow); }
.lineage-candidate.on b, .lineage-candidate[aria-pressed="true"] b, .lineage-candidate.on small, .lineage-candidate[aria-pressed="true"] small { color:var(--ink); }

/* ================= LOCAL LIVE VOICE — the pop-up mic module =================
   A SMALL WINDOW, not a typical one (Andrew, 2026-07-29). Two earlier passes missed on opposite
   sides: a 300-440px CRT panel with a bezel, a title bar and a three-row readout rail read as one
   more window to manage; a chromeless capsule glued to the dock stopped reading as its own thing
   at all. What this wants to be is a little piece of station hardware that POPS UP over the
   station — framed, floating, obviously separate — and is done in one glance.

   So it keeps a frame, a lift shadow and the curved-glass sheen (it is a screen), and drops every
   piece of window furniture: no title chip, no drag bar, no engraved rail, no window buttons.
   Every colour resolves from the phosphor tokens through one --lv-accent, and --lv-amp lights part
   of EVERY skin, so no variant can show a level the microphone is not producing. */
.live-voice-panel {
  --lv-amp: 0;                 /* live mic RMS, 0..1 — written by voice-live.js pushLevel() */
  --lv-dl: 0%;                 /* local-model download percent, when the sidecar reports one */
  --lv-accent: var(--ph-dim);  /* per-state accent; every lit part of the module reads this */
  --lv-talker: var(--lv-accent); /* current voice: user follows state; agent uses station gold */
  position: fixed; z-index: 275; left: 18px; bottom: 68px;
  width: 150px; max-width: calc(100vw - 16px);
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  padding: 5px 7px 6px;
  color: var(--text); border-radius: var(--r-lg); overflow: hidden;
  background:
    radial-gradient(ellipse 130% 90% at 50% 0%, var(--ph-glow2), transparent 46%),
    linear-gradient(180deg, rgba(9, 5, 2, 0.97), rgba(4, 2, 1, 0.985));
  /* a RIM and a LIFT, not a --bezel: one hairline of phosphor so it reads as hardware, and a deep
     drop so it floats above the station instead of sitting in it */
  box-shadow:
    inset 0 0 0 1px var(--ph-dim),
    inset 0 1px 0 rgba(var(--ph-bright-rgb), 0.08),
    0 14px 30px rgba(0, 0, 0, 0.62),
    0 0 22px var(--ph-glow2);
  transform-origin: left bottom; animation: lv-pop .2s var(--ease-out);
}
/* curved glass over the whole module — it is a screen, like every other StarNet surface */
.live-voice-panel::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; z-index: 6; background: var(--glass);
}
.live-voice-panel[hidden] { display: none; }
.live-voice-panel[data-dragging="true"] {
  animation: none;
  box-shadow: inset 0 0 0 1px var(--ph), 0 16px 34px rgba(0,0,0,.7), 0 0 26px var(--ph-glow2);
}
/* it POPS UP — a CRT power-on entrance on something this small reads as a glitch, not a boot */
@keyframes lv-pop {
  0%   { transform: scale(.9) translateY(4px); opacity: 0; }
  100% { transform: scale(1) translateY(0); opacity: 1; }
}

/* per-state accent — one variable, so state reads through the pip, the lamp and the level at once.
   Idle listening rests on the phosphor; only ACTIVELY hearing you climbs to --ph-bright, so the
   brightest the module ever gets means "your voice is landing right now". */
.live-voice-panel[data-state="listening"],
.live-voice-panel[data-state="speaking"] { --lv-accent: var(--ph); }
.live-voice-panel[data-state="hearing"] { --lv-accent: var(--ph-bright); }
.live-voice-panel[data-state="connecting"],
.live-voice-panel[data-state="warming"],
.live-voice-panel[data-state="thinking"],
.live-voice-panel[data-state="transcribing"],
.live-voice-panel[data-state="reconnecting"] { --lv-accent: var(--warn); }
.live-voice-panel[data-state="offline"] { --lv-accent: var(--bad); }
.live-voice-panel[data-talker="agent"] { --lv-talker: var(--gold); }

/* ---- head: the state line doubles as the grab handle (makeDraggable binds it), so no skin may
   collapse it to nothing — a pop-up you cannot move out of the way is worse than a window. ---- */
.lv-head {
  position: relative; z-index: 7; flex: 1 0 100%;
  display: flex; align-items: center; gap: 5px;
  min-height: 12px; padding: 0 1px 1px; cursor: grab; touch-action: none; user-select: none;
}
.live-voice-panel[data-dragging="true"] .lv-head { cursor: grabbing; }
/* the LOCAL LIVE title chip is most of what made this read as a window. The mode is already
   obvious from the composer's lit LIVE toggle — the module does not need to introduce itself. */
.lv-brand { display: none; }
.lv-pip {
  flex: 0 0 auto; width: 5px; height: 5px;
  background: var(--lv-accent); box-shadow: 0 0 6px var(--lv-accent);
  animation: lv-pip 1.3s infinite;
}
@keyframes lv-pip { 0%, 100% { opacity: 1; } 50% { opacity: .28; } }
/* the state WORD is gone from the resting module (Andrew, 2026-07-29). It was the widest thing in
   a 150px module and it repeated what the colour already says: the pip and the whole meter carry
   the state ladder. It comes back under the cursor, where the exact word is worth the room. */
.lv-state {
  display: none;
  flex: 1 1 auto; color: var(--lv-accent);
  font-size: 9px; letter-spacing: var(--ls-1); text-shadow: 0 0 8px var(--ph-glow2);
}
/* HOVER NO LONGER OPENS THE MODULE (Andrew, 2026-07-29). Passing the cursor over a thing is not a
   request to read it, and a module that grows under the pointer moves the target you were aiming at —
   the meter is also the barge-in button, so the resting box must hold still. Everything below opens on
   FOCUS only (click it, or tab to it), which is a deliberate act with a way back out. */
.live-voice-panel:focus-within .lv-state { display: block; }
/* ✕ stays reachable but stops taking resting-state real estate. pointer-events ride the opacity so
   an invisible button can never eat a click and drop you out of voice mode. */
.lv-x {
  flex: 0 0 auto; margin-left: auto; padding: 0 3px; font-size: 10px; background: none;
  opacity: 0; pointer-events: none; transition: opacity var(--t-fast) linear;
}
.live-voice-panel:hover .lv-x,
.live-voice-panel:focus-within .lv-x { opacity: .75; pointer-events: auto; }
.lv-x:hover { opacity: 1; color: var(--bad); border-color: var(--bad); background: none; }

@media (hover: none), (pointer: coarse) {
  /* Touch has no hover reveal. Keep close reachable and make the whole top strip an honest drag target. */
  .lv-x { opacity: .75; pointer-events: auto; min-width: 24px; min-height: 20px; }
  .lv-head { min-height: 24px; }
}

/* ---- the level IS the control (Andrew, 2026-07-29: no icon, only the volume indicator). Every
   face we tried — orb, lamp, mic glyph, vu column, aperture, slit — was a second thing to look at
   sitting beside a meter that already said everything this module knows. So the meter is the whole
   instrument AND the whole button: press anywhere on your own voice to cut in. That keeps the
   barge-in reachable by mouse, touch and keyboard without drawing a single icon. ---- */
.lv-stage {
  position: relative; z-index: 5; flex: 1 0 100%; min-width: 0;
  display: flex; align-items: center; padding: 0;
}
.lv-meter {
  position: relative; overflow: hidden; flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; height: 24px; padding: 0 5px;
  color: inherit; font-family: inherit; cursor: pointer;
  background: linear-gradient(180deg, rgba(0,0,0,.6), rgba(0,0,0,.36));
  border: 1px solid var(--ph-faint); border-radius: var(--r-sm);
  box-shadow: var(--well), 0 0 calc(2px + var(--lv-amp) * 13px) color-mix(in srgb, var(--lv-talker) 32%, transparent);
  transition: border-color var(--t-med) var(--ease-soft), box-shadow var(--t-fast) linear, transform var(--t-fast) var(--ease-out);
}
.lv-meter:hover { border-color: var(--lv-talker); }
/* it travels when you press it — a control that does not move is a picture of a control */
.lv-meter:active { transform: translateY(1px); box-shadow: inset 0 2px 7px rgba(0,0,0,.92); }
.lv-meter:focus-visible { outline: 2px solid var(--ph); outline-offset: 2px; }

/* level — a rolling window of real RMS, oldest at the left, TYPED in the station's own block ramp
   (▁▂▃▄▅▆▇█, the same glyphs the link-status bars use). Scaled rectangles read as a widget drawn
   over the terminal; glyphs read as the terminal itself printing what it hears. voice-live.js
   writes one character per column — nothing here interpolates, so a step you see is a step the
   microphone actually sent. */
.lv-wave {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: flex-end; gap: 2px;
  /* the glyph must be NARROWER than its cell or the columns touch and fuse into one shape — a
     block character fills its own em box by definition, so the gap has to come from the cell */
  font-size: 13px; line-height: .8; letter-spacing: 0;
  color: var(--lv-accent); text-shadow: 0 0 calc(3px + var(--lv-amp) * 8px) var(--ph-glow2);
  transition: text-shadow 70ms linear;
}
/* one equal CELL per column with a hairline between: butted up against each other the block glyphs
   fuse into one solid shape and the meter stops reading as columns at all */
.lv-wave i {
  flex: 1 1 0; min-width: 0; text-align: center; font-style: normal; opacity: .92;
}
/* The history is one strip, but every cell retains who produced it. This makes turn ownership
   readable without a legend or a second meter: user samples use the live state color, agent
   samples use the station's established gold voice color. */
.lv-wave i[data-src="self"] { color: var(--lv-accent); }
.lv-wave i[data-src="agent"] { color: var(--gold); text-shadow: 0 0 7px color-mix(in srgb, var(--gold) 42%, transparent); }

/* working states (connecting / warming / thinking / transcribing): the station is BUSY, not
   hearing. A sweep across the meter says so WITHOUT touching the bars — the bars are the
   microphone and must never move to a timer, or the module would claim to hear a silent room. */
.live-voice-panel[data-state="connecting"] .lv-meter::after,
.live-voice-panel[data-state="warming"] .lv-meter::after,
.live-voice-panel[data-state="thinking"] .lv-meter::after,
.live-voice-panel[data-state="transcribing"] .lv-meter::after {
  content: ''; position: absolute; inset: 0; width: 45%;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--ph-glow2), transparent);
  animation: lv-sweep 1.6s var(--ease-soft) infinite;
}
@keyframes lv-sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(240%); }
}

/* ---- the one line of text worth carrying: it answers "did it hear me right" without opening
   COMMS. The reply line only exists once there IS a reply. ---- */
/* the transcript is the thing you read ONCE, not the thing you watch — at rest it costs two rows
   for a line COMMS already owns, so it waits under the cursor with the state word and the rail */
.lv-heard {
  display: none;
  position: relative; z-index: 5; flex: 1 0 100%; margin: 1px 1px 0;
  -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  color: var(--ph-bright); font-size: var(--fs-0); line-height: 1.3; letter-spacing: var(--ls-1);
  text-shadow: 0 0 8px var(--ph-glow2);
}
.live-voice-panel:focus-within .lv-heard { display: -webkit-box; }
.live-voice-panel[data-state="hearing"] .lv-heard,
.live-voice-panel[data-state="transcribing"] .lv-heard { display: -webkit-box; }
.lv-say { display: none; }
.lv-say.on { display: none; }
.live-voice-panel .lv-say.on {
  position: relative; z-index: 5; flex: 1 0 100%; margin: 1px 1px 0;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  color: var(--ph-dim); font-size: 10px; line-height: 1.3; letter-spacing: var(--ls-1);
}
.lv-say.on::before { content: '▸ '; color: var(--ph); }

.lv-end-control {
  position: relative; z-index: 7; flex: 1 0 100%; min-width: 0;
  display: flex; align-items: center; gap: 5px; margin: 1px 1px 0;
  color: var(--ph-dim); font-size: 9px; letter-spacing: var(--ls-2);
}
.lv-end-control span { flex: 0 0 auto; opacity: .65; }

/* Conversation controls and recognized words stay readable without chasing hover/focus. */
.live-voice-panel { min-width: 260px; max-width: calc(100vw - 16px); }
.live-voice-panel .lv-state { display: block; }
.live-voice-panel .lv-x { opacity: .75; pointer-events: auto; }
.live-voice-panel .lv-heard { display: -webkit-box; -webkit-line-clamp: 4; }
.lv-controls { display: flex; gap: 6px; flex: 1 0 100%; position: relative; z-index: 7; }
.lv-controls .bb { flex: 1; min-height: 28px; font-size: 10px; }
.lv-controls .bb:disabled { opacity: .35; cursor: default; }
.lv-details { position: relative; z-index: 7; flex: 1 0 100%; color: var(--ph-dim); font-size: 9px; }
.lv-details summary { cursor: pointer; padding: 4px 0; }
.live-voice-panel[data-state="paused"] { --lv-accent: var(--ph-dim); }

/* ---- ROUTE / SPEECH / TASK are diagnostics, not glanceable state, and carrying them at rest is
   what forced a 300px panel. Hidden at rest, wrapped onto one line on hover — HIDDEN, never faked:
   this hover is now the only place a model download reports itself, so the fill stays real. ---- */
.lv-rail { display: none; }
.live-voice-panel .lv-rail {
  position: relative; z-index: 5; flex: 1 0 100%;
  display: flex; flex-flow: row wrap; gap: 1px 9px; margin: 3px 1px 0; padding-top: 4px;
  border-top: 1px solid var(--ph-faint);
  background: none; box-shadow: none; overflow: visible;
}
.lv-row {
  position: relative; display: flex; align-items: baseline; gap: 5px;
  min-width: 0; padding: 0; border-bottom: 0;
}
.lv-row dt { flex: 0 0 auto; color: var(--ph-dim); opacity: .5; font-size: 9px; letter-spacing: var(--ls-2); }
.lv-row dd {
  flex: 0 1 auto; min-width: 0; margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ph); font-size: 9px; letter-spacing: var(--ls-1);
}
.lv-endpoint {
  flex: 1 1 auto; min-width: 0; max-width: 98px; padding: 0 13px 0 2px; border: 0; border-bottom: 1px solid var(--ph-faint);
  color: var(--ph-bright); background: transparent; box-shadow: none; border-radius: 0;
  font: inherit; letter-spacing: inherit; line-height: 1.2; appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ph-dim) 50%),
    linear-gradient(135deg, var(--ph-dim) 50%, transparent 50%);
  background-position: calc(100% - 6px) 45%, calc(100% - 3px) 45%;
  background-size: 3px 3px, 3px 3px; background-repeat: no-repeat;
}
.lv-endpoint:focus { outline: 1px solid var(--ph); outline-offset: 1px; }
/* model download: a real fill sweeps the SPEECH row, driven by the sidecar's reported percent */
.lv-row-dl::before {
  content: ''; position: absolute; left: -3px; top: -1px; bottom: -1px; width: var(--lv-dl);
  background: linear-gradient(90deg, var(--ph-glow2), transparent);
  transition: width var(--t-slow) var(--ease-soft); pointer-events: none;
}
.lv-task.busy { color: var(--ph-bright); text-shadow: 0 0 8px var(--ph-glow2); }
.lv-task.pending { color: var(--warn); text-shadow: 0 0 8px var(--ph-glow2); }

/* ---- a fault is the one thing allowed to grow the module, because a silent dead mic is this
   feature's worst failure. Still a strip, not a card. ---- */
.lv-error {
  position: relative; z-index: 5; flex: 1 0 100%; margin: 4px 1px 0;
  padding: 4px 0 0; border-top: 1px solid var(--bad);
  color: var(--bad); background: none; box-shadow: none;
  font-size: 9px; line-height: 1.35; letter-spacing: var(--ls-1);
}
/* [hidden] is the UA's display:none — a bare `display: block` here silently OUT-SPECIFIES it and
   left TRY AGAIN sitting under a healthy session. Restate the hidden case. */
.lv-retry[hidden] { display: none; }
.lv-retry {
  position: relative; z-index: 5; display: block; flex: 0 0 auto; margin: 5px 1px 0 auto;
  padding: 2px 9px; cursor: pointer;
  color: var(--ph); background: var(--panel2);
  border: 1px solid var(--ph-dim); border-radius: var(--r-sm);
  font-family: inherit; font-size: 9px; letter-spacing: var(--ls-2); text-shadow: none;
  box-shadow: var(--raise);
}
.lv-retry:hover { color: var(--ph-bright); border-color: var(--ph); box-shadow: var(--raise), 0 0 10px var(--ph-glow2); }


@media (prefers-reduced-motion: reduce) {
  .live-voice-panel { animation: none; }
  .lv-pip { animation: none !important; }
  .lv-meter::after { animation: none !important; opacity: .5; }
}

@keyframes chip-spk { 0%,100% { box-shadow: 0 0 2px var(--ph-glow2); } 50% { box-shadow: 0 0 12px var(--ph-glow); } }

/* ---------- onboarding / save additions ---------- */
/* (title-screen rules removed with the title screen; #data-status now styled by .title-status at the top of this file) */
/* (deleted: .lead-banner — the old create-screen orchestrator framing banner, unused since the ov-* redesign) */
/* section subheads dominate the field labels they group (labels are 16px). A trailing dashed rule runs
   the head out to the column edge — terminal-readout texture without another heavy border. */
.cc-head {
  display: flex; align-items: center; gap: 9px;
  margin: 12px 0 4px; color: var(--ph); letter-spacing: 2px; font-size: 17px;
  text-shadow: 0 0 8px var(--ph-glow2);
}
.cc-head::after { content: ''; flex: 1; height: 0; border-top: 1px dashed var(--ph-faint); }
.cc-head .dim { letter-spacing: .5px; font-size: 14px; flex: 0 0 auto; }
.byok-note { font-size: 12px; margin-top: 6px; line-height: 1.28; opacity: .9; }
/* progressive-disclosure toggle for the key-safety note — a quiet inline link, expands the note on demand */
.byok-disclose { margin-top: 6px; }
.byok-toggle { display: inline-block; font: inherit; font-size: 12px; letter-spacing: .4px; color: var(--ph-dim); background: none; border: none; padding: 2px 0; cursor: pointer; text-align: left; }
.byok-toggle:hover { color: var(--ph); }
.byok-toggle:focus-visible { outline: 2px solid var(--ph); outline-offset: 2px; }
.byok-disclose .byok-note { margin-top: 5px; }

/* SKIN picker — thumbnail grid on the LEFT, a LIVE preview stage on the RIGHT. At 40px a
   chunky pixel sprite is unreadable, so the stage plays the picked (or hovered) skin's real
   walk cycle big enough to actually tell a bear from a capybara before committing. */
/* % of the screen, not vh: `.screen` is `position:fixed; inset:0`, which under the TEXT SIZE body
   zoom is viewport-exact, while a bare 96vh would render as 139vh at HUGE. #screen-connect resets
   this to height:100% below, so the cap that survives is the forward-version gate's card. */
.connect-panel { max-height: 96%; overflow-y: auto; }   /* desktop console: a touch taller cap so the two-column body lands without a scrollbar */

/* ===== CREATE YOUR OVERSEER (v4) — a FULL-SCREEN console: hero pod | identity | brain =====
   The genesis moment owns the whole display. Three numbered columns read left→right as the three
   decisions: 01 APPEARANCE · 02 IDENTITY · 03 THE BRAIN (the only required one, beside WAKE).
   The forward-version gate (#screen-future) keeps the old centered 980px card — scoped below. */
#screen-connect.active { padding: 14px; }
#screen-connect .ov-panel {
  max-width: 1780px; width: 100%; height: 100%; max-height: none;
  display: flex; flex-direction: column; margin: 0 auto;
}
#screen-connect .ov-grid { flex: 1; min-height: 0; overflow-y: auto; }
#screen-future .ov-panel { max-width: 980px; align-self: center; margin: auto; }
.ov-head { padding: 12px 22px 0; }
.ov-title-row { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-top: 8px; }
.ov-title-row h2 { margin: 0; }
.ov-sub { color: var(--ph-dim); font-size: 14px; letter-spacing: .4px; }
.ov-sub b { color: var(--ph-bright); }

.ov-grid {
  display: grid; grid-template-columns: minmax(340px, 430px) 1fr 1fr;
  grid-template-areas: "hero id brain";
}
.ov-hero {
  grid-area: hero; display: flex; flex-direction: column;
  position: relative; padding: 14px 22px 16px; border-right: 1px solid var(--ph-faint);
  background: radial-gradient(ellipse 90% 60% at 50% 22%, var(--ph-glow2), transparent 62%);
}
.ov-col-id { grid-area: id; border-right: 1px solid var(--ph-faint); }
.ov-col-brain { grid-area: brain; display: flex; flex-direction: column; }
.ov-col-brain .ov-field-brain { flex: 1; }

/* numbered step heads — the eye's rail across the three columns */
.ov-step {
  display: flex; align-items: center; gap: 9px; margin: 0 0 10px;
  color: var(--ph); letter-spacing: 2px; font-size: 16px; text-transform: uppercase;
  text-shadow: 0 0 8px var(--ph-glow2); flex-wrap: wrap;
}
.ov-step b {
  color: var(--ink); background: var(--ph); padding: 0 7px; letter-spacing: 1px;
  text-shadow: none; font-weight: normal;
}
.ov-step .dim { font-size: 13px; letter-spacing: .4px; text-transform: none; }
.ov-step::after { content: ''; flex: 1; min-width: 12px; height: 0; border-top: 1px dashed var(--ph-faint); }
.ov-step .ov-req { order: 9; }   /* the REQUIRED tag rides at the rule's far end */
.ov-leadtag {
  display: inline-flex; align-items: center; gap: 7px; color: var(--gold); font-size: 12px; letter-spacing: 2px;
  border: 1px solid var(--ph-dim); border-radius: 20px; padding: 2px 11px; margin-bottom: 12px; background: rgba(var(--ph-rgb),.05);
}
.ov-pod {
  position: relative; flex: 1 1 auto; min-height: 208px; border: 1px solid var(--ph-dim); border-radius: 8px; overflow: hidden;
  background: radial-gradient(ellipse 75% 60% at 50% 52%, var(--ph-glow2), transparent 60%), #060403;
  box-shadow: inset 0 0 60px rgba(0,0,0,.9), 0 0 0 1px rgba(0,0,0,.8), 0 0 24px var(--ph-glow2);
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
}
.ov-pod::before { content: ""; position: absolute; inset: 0; background: repeating-linear-gradient(0deg, transparent 0 22px, rgba(var(--ph-rgb),.04) 22px 23px); pointer-events: none; }
.ov-stage { margin: 0 0 20px; display: flex; align-items: flex-end; justify-content: center; z-index: 1; }
.ov-stage-frame { display: flex; align-items: flex-end; justify-content: center; filter: drop-shadow(0 0 14px var(--ph-glow)); }
.ov-stage-frame img { height: clamp(136px, 26vh, 260px); width: auto; image-rendering: pixelated; image-rendering: crisp-edges; }
.ov-platform { position: absolute; bottom: 16px; width: 150px; height: 14px; border-radius: 50%; background: radial-gradient(ellipse, var(--ph-glow), transparent 70%); opacity: .55; }

.ov-nameplate { margin-top: 12px; text-align: center; }
.ov-np-name { color: var(--ph-bright); font-size: 30px; letter-spacing: 4px; text-shadow: 0 0 12px var(--ph-glow); line-height: 1.1; }
.ov-np-role { color: var(--gold); font-size: 14px; letter-spacing: 2px; margin-top: 2px; }
.ov-np-role .ov-dot { color: var(--ph-dim); }
.ov-skinname { color: var(--ph-dim); font-size: 12px; letter-spacing: 1px; margin-top: 3px; }

.ov-pickrow { margin-top: 12px; display: flex; align-items: center; gap: 10px; }
.ov-pick-lab { color: var(--ph-dim); font-size: 13px; letter-spacing: 1.5px; min-width: 42px; }
.ov-skins { flex: 1; }

.ov-cfg { padding: 16px 22px 18px; min-width: 0; }
.ov-field { margin-bottom: 14px; }
.ov-field:last-child { margin-bottom: 0; }
.ov-cfg label { display: block; }
.ov-nm-in { letter-spacing: 3px; }
.ov-req { color: var(--gold); font-size: 11px; letter-spacing: 1px; vertical-align: 1px; margin-left: 8px; }

.ov-cards { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
.ov-cards.ov-approve { grid-template-columns: 1fr 1fr; }
.ov-approve .ov-card { padding: 11px 13px 10px; }
.ov-approve .ov-card-ds { font-size: 12.5px; margin-top: 3px; }
.ov-card {
  font: inherit; position: relative; border: 1px solid var(--ph-faint); border-radius: 6px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--ph) 6%, var(--panel2)), var(--panel2));
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.08), 0 1px 0 rgba(0,0,0,.35);
  padding: 10px 11px 9px; cursor: pointer; text-align: left;
  transition: border-color .12s, background-color .12s, box-shadow .12s;
}
.ov-card:hover { border-color: var(--ph-dim); background: var(--ph-faint); box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.12), 0 0 10px var(--ph-glow2), 0 1px 0 rgba(0,0,0,.35); }
.ov-card.sel { border-color: var(--ph); background: rgba(var(--ph-rgb),.07); box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.14), 0 0 16px var(--ph-glow2); }
.ov-card-ic { color: var(--ph); font-size: 19px; line-height: 1; text-shadow: 0 0 8px var(--ph-glow); }
.ov-card.sel .ov-card-ic { color: var(--ph-bright); }
.ov-card-nm { color: var(--ph-bright); font-size: 15px; letter-spacing: 1px; margin-top: 5px; }
.ov-card-ds { color: var(--ph-dim); font-size: 12px; letter-spacing: .2px; line-height: 1.12; margin-top: 2px; }
.ov-card-pick { position: absolute; top: 6px; right: 8px; color: var(--gold); font-size: 13px; opacity: 0; }
.ov-card.sel .ov-card-pick { opacity: 1; }
.ov-crewnote { margin-top: 8px; color: var(--ph-dim); font-size: 12px; letter-spacing: .3px; }
.ov-crewnote b { color: var(--ph); }

.ov-vchips { display: flex; flex-wrap: wrap; gap: 7px; }
.ov-vchip {
  font-family: inherit; font-size: 14px; color: var(--ph);
  background: linear-gradient(180deg, color-mix(in srgb, var(--ph) 7%, var(--panel2)), var(--panel2));
  border: 1px solid var(--ph-faint); border-radius: 999px; padding: 5px 15px; cursor: pointer;
  letter-spacing: .6px; text-shadow: 0 0 5px var(--ph-glow2);
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.1), 0 1px 0 rgba(0,0,0,.35);
  transition: background-color .1s, color .1s, border-color .1s, box-shadow .1s;
}
.ov-vchip:hover { border-color: var(--ph-dim); color: var(--ph-bright); box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.14), 0 0 9px var(--ph-glow2), 0 1px 0 rgba(0,0,0,.35); }
.ov-vchip.sel {
  background: linear-gradient(180deg, var(--ph-bright) -40%, var(--ph) 60%); color: var(--ink); border-color: var(--ph);
  text-shadow: none; box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 0 9px var(--ph-glow2);
}
/* the UNHINGED chip's armed state (two-press confirm — it swears for real): warn tint until the second press.
   `.armed` is the same state under the shared ArmConfirm helper's class name — a chip wired through that
   helper (the PERMISSIONS reach/approval chips) must wear the same warn face, not sit visually at rest. */
.ov-vchip.armed,
.ov-vchip.arm {
  color: var(--warn); border-color: var(--warn);
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.1), 0 0 9px color-mix(in srgb, var(--warn) 40%, transparent), 0 1px 0 rgba(0,0,0,.35);
}
.ov-vpreview { margin-top: 8px; }

.ov-field-brain { margin-bottom: 0; }
.ov-brain { border: 1px solid var(--ph-dim); border-radius: 7px; background: linear-gradient(180deg, rgba(var(--ph-rgb),.06), rgba(var(--ph-rgb),.025)); box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.08); padding: 11px 13px; }
/* (deleted: .ov-vtune + .ov-brain-lead — the voice fine-tune well and the brain lead-in paragraph are both
   gone; the hero ChatGPT card now carries the beginner message, so the lead-in was redundant.) */
.ov-brain .provider-row { margin: 0 0 8px; }
.ov-brain input { margin-bottom: 8px; }
.ov-brain .model-picks { margin: 2px 0 6px; }
.ov-brain .byok-note { margin-bottom: 0; }
.ov-model-lab label { margin-top: 2px; }

/* ===== themed grouped model popover (#model-pop) — replaces the native <datalist>. position:fixed so it
   escapes the .ov-grid overflow clip; app.js positions it under (or above) #in-model. Reuses the model-dock
   listbox vocabulary so the two model surfaces read the same. ===== */
.ov-mdl-wrap { margin-bottom: 8px; }
.ov-mdl-wrap input { margin-bottom: 0; }
.ov-mdl-pop {
  position: fixed; z-index: 60; max-height: 306px; overflow-y: auto; overscroll-behavior: contain;
  background: var(--panel); border: 2px solid var(--ph); border-radius: 4px;
  box-shadow: 0 0 0 2px var(--ink), 0 0 22px var(--ph-glow2);
}
.ov-mdl-pop[hidden] { display: none; }
.ov-mdl-group {
  position: sticky; top: 0; z-index: 1; color: var(--gold); font-size: 12px; letter-spacing: 1px;
  padding: 7px 10px 3px; background: var(--panel); border-bottom: 1px solid var(--ph-faint);
}
.ov-mdl-off { color: var(--warn); font-size: 11px; font-style: normal; letter-spacing: .4px; }
.ov-mdl-row {
  display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 10px; align-items: baseline;
  padding: 6px 10px; cursor: pointer; border-bottom: 1px solid var(--ph-faint);
}
.ov-mdl-row:last-child { border-bottom: 0; }
.ov-mdl-row.hi, .ov-mdl-row:hover { background: var(--ph-faint); }
.ov-mdl-row.sel { background: var(--ph); }
.ov-mdl-row.sel .ov-mdl-name, .ov-mdl-row.sel .ov-mdl-meta { color: var(--ink); text-shadow: none; }
.ov-mdl-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ph-bright); font-size: 15px; letter-spacing: .3px; }
.ov-mdl-meta { color: var(--ph-dim); font-size: 12px; letter-spacing: .3px; white-space: nowrap; }
.ov-mdl-none { color: var(--ph-dim); font-size: 13px; padding: 12px 10px; line-height: 1.3; }

.ov-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 22px; border-top: 1px solid var(--ph-faint); background: rgba(0,0,0,.32); flex-wrap: wrap; }
.ov-foot-data { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ov-foot-cap { color: var(--ph-dim); font-size: 12px; letter-spacing: .3px; }
.ov-actions { display: flex; align-items: center; gap: 12px; }

/* ===== RESUME / RECOVERY banner — shown when re-entering an existing station whose creds went missing.
   Phosphor-styled (gold inset rule + bloom), it reassures the Commander the agent is safe and locks the
   identity fields below so a resume can't silently re-spec the agent. ===== */
.ov-recovery {
  margin: 10px 22px 0; padding: 9px 14px; font-size: 16px; letter-spacing: .4px;
  color: var(--ph-bright); border: 1px solid var(--gold);
  border-left: 4px solid var(--gold); border-radius: 3px;
  background: rgba(var(--ph-rgb),.07); text-shadow: 0 0 6px var(--ph-glow2);
  box-shadow: inset 0 0 22px rgba(0,0,0,.5), 0 0 12px var(--ph-glow2);
}
.ov-recovery b { color: var(--gold); }

/* read-only identity fields in RESUME mode: visibly muted + inert, but kept in the DOM (onWake still reads
   the seeded values). The skin/voice/approval pickers are non-text containers, so block pointer events too. */
.field-locked { opacity: .42; filter: grayscale(.35); pointer-events: none; }
input.field-locked { opacity: .55; filter: none; cursor: not-allowed; background: #0a0805; }
#screen-connect.recovery .ov-leadtag { opacity: .5; }

/* footer-resident validation: sits BESIDE the WAKE button it explains */
.ov-actions .msg { margin: 0; text-align: right; max-width: 520px; }
.ov-actions .msg:empty { min-height: 0; }

/* the MORE reveal on the provider row — dashed, quieter than a real provider chip */
.prov-more {
  font-family: inherit; font-size: 14px; color: var(--ph-dim); background: none;
  border: 2px dashed var(--ph-faint); padding: 6px 12px; cursor: pointer;
  letter-spacing: 1px; text-transform: uppercase; line-height: 1;
}
.prov-more:hover { color: var(--ph); border-color: var(--ph-dim); }
.provider-row.collapsed .prov.tail { display: none; }

/* short displays (720p-class windows): tighten the vertical rhythm so all three columns land
   without the grid scrolling — the genesis screen should never need a scrollbar to see WAKE */
@media (max-height: 800px) {
  .ov-cfg { padding: 10px 18px 12px; }
  .ov-hero { padding: 10px 18px 12px; }
  .ov-field { margin-bottom: 9px; }
  .ov-step { margin-bottom: 7px; }
  .ov-pod { min-height: 148px; }
  .ov-np-name { font-size: 24px; }
  .ov-leadtag { margin-bottom: 8px; }
  .vp-quote { font-size: 15px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .ov-crewnote { display: none; }   /* the approval cards' own descriptions carry the meaning on short displays */
  .byok-note { line-height: 1.2; }
  .ov-brain { padding: 8px 10px; }
  .ov-crewnote, .ov-brain-lead { line-height: 1.2; }
  .ov-approve .ov-card { padding: 8px 11px 7px; }
  .ov-foot { padding: 8px 22px; }
  #screen-connect input[type=text], #screen-connect input[type=password], #screen-connect input:not([type]) { padding: 5px 10px; font-size: 18px; }
  .ov-vpreview, .ov-vtune { margin-top: 5px; }
  .ov-cfg label { margin: 6px 0 3px; }
}

@media (max-width: 1240px) {
  .ov-grid { grid-template-columns: minmax(320px, 400px) 1fr; grid-template-areas: "hero id" "brain brain"; }
  .ov-hero { border-bottom: 1px solid var(--ph-faint); }
  .ov-col-id { border-right: none; }
  .ov-col-brain { border-top: 1px solid var(--ph-faint); }
}
@media (max-width: 900px) {
  .ov-grid { grid-template-columns: 1fr; grid-template-areas: "hero" "id" "brain"; }
  .ov-hero { border-right: none; border-bottom: 1px solid var(--ph-faint); }
  .ov-col-brain { border-top: none; }
  .ov-cards { grid-template-columns: 1fr 1fr; }
}
.skin-picker {
  flex: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: 6px;
  align-content: flex-start; max-height: 156px; overflow-y: auto; padding-right: 2px;
}
.skin-thumb {
  width: 44px; height: 44px; padding: 2px; cursor: pointer; border-radius: 5px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--ph) 6%, var(--panel2)), var(--panel2));
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.08), 0 1px 0 rgba(0,0,0,.35);
  border: 1px solid var(--ph-dim); display: flex; align-items: center; justify-content: center;
}
.skin-thumb img {
  width: 100%; height: 100%; object-fit: contain;
  image-rendering: pixelated; image-rendering: crisp-edges;
}
.skin-thumb:hover { border-color: var(--ph); background: var(--ph-faint); }
.skin-thumb.sel { border-color: var(--ph-bright); box-shadow: 0 0 10px var(--ph-glow); }

/* (deleted: the genesis .skin-stage/.skin-stage-frame preview — the ov-hero pod now uses .ov-stage/.ov-stage-frame;
   the Recruitment Bay's SUMMON preview is the separate mkt-skin-stage* in marketplace.css.) */
.skin-stage-name { color: var(--ph-bright); letter-spacing: 1px; font-size: 14px; text-align: center; }

/* NOTE: the Recruitment Bay's SUMMON skin bar (.mkt-skinbar / .mkt-skin-section / .mkt-skin-stage) is styled
   in marketplace.css — a self-contained bespoke picker with its own large wells + live SkinStage preview.
   The earlier orphan .mkt-skin-section rules that used to sit here (never emitted by any JS) were removed. */

/* (deleted: the archetype/arch-name card grid — genesis personality is the .ov-vchip chip row now; the live
   .voice-preview below is still used by renderVoicePreview.) */
.voice-preview {
  margin-top: 9px; border: 1px dashed var(--ph-dim); border-radius: 5px;
  padding: 8px 10px; background: rgba(0,0,0,.3);
}
.vp-quote { color: var(--ph-bright); font-size: 15px; line-height: 1.25; }
.vp-meta { color: var(--ph-dim); font-size: 12px; letter-spacing: .5px; margin-top: 4px; text-transform: uppercase; }

/* (deleted: the whole FINE-TUNE voice UI — voice-traits, the dial-row/dial-cell segments, voice-toggles,
   vtoggle and voice-custom. The voice fine-tune dials + free-text note were removed from genesis 2026-07-09;
   the archetype chip is the entire voice choice now. Resumed agents keep saved voiceTraits/customVoice.) */

/* ============================================================
   STATION HUD — top bar · crew manifest · bottom bar · panels.
   The phosphor/CRT chrome (#topbar/#left/#bottombar bezels, .bb,
   .term, .crew-*, .kb-*, .ag-*, .set-*, .nf, .perk-*) is reused
   verbatim from the v7 style.css already loaded above; this block
   only positions the game-screen grid and adds the few new bits.
   ============================================================ */
#topbar { grid-area: top; }
#left { grid-area: left; }
#stage-wrap { grid-area: center; position: relative; z-index: 0; }
#chat-panel { grid-area: chat; position: relative; z-index: 8; }
#bottombar { grid-area: bot; }

/* ---------- COMMS / stage divider: drag left to widen COMMS (chatresize.js) ----------
   A thin handle living in the 9px grid gap on the COMMS panel's left edge. Invisible until
   hovered/dragged, then a phosphor grip lights up so it reads as a real control. */
.col-resizer {
  position: absolute; top: 0; bottom: 0; left: -10px; width: 12px; z-index: 25;
  cursor: col-resize; display: flex; align-items: center; justify-content: center;
  touch-action: none;
}
.col-resizer-grip {
  width: 2px; height: 46px; border-radius: 2px; background: var(--ph-dim); opacity: 0;
  transition: opacity .12s, background .12s, box-shadow .12s;
}
.col-resizer:hover .col-resizer-grip,
body.col-resizing .col-resizer-grip {
  opacity: 1; background: var(--ph); box-shadow: 0 0 8px var(--ph-glow2);
}
/* ---------- CREW / stage divider: drag right to widen the crew rail (leftrail.js) ----------
   THE HIT ZONE MUST SIT ON THE SEAM THE EYE SEES. #comms-resizer is a child of its panel at
   left:-10px, so it straddles the 9px grid gap. The same trick on the left is impossible from
   inside #stage-wrap — the tube casing is overflow:hidden and cuts away anything outside it —
   and the first pass put the handle INSIDE the tube at left:0 instead. It worked, but the
   cursor only turned to ↔ once you were already past the seam; Andrew: "it's a bit misaligned
   when u put your mouse in between."
   So it is a GRID CHILD sharing the centre area with the tube: justify-self pins it to the
   stage's left edge, the negative margin walks it back over the gap. No arithmetic against
   padding/gap/row heights — the grid places it, so it follows every breakpoint for free.
   Same invisible-until-hovered grip vocabulary as .col-resizer. */
.crew-resizer {
  grid-area: center; justify-self: start; align-self: stretch;
  position: relative; margin-left: -10px; width: 12px; z-index: 25;
  cursor: col-resize; display: flex; align-items: center; justify-content: center;
  touch-action: none; background: transparent; border: 0; padding: 0;
}
/* the rail is GONE, not narrow — there is no seam to drag, and the ▸ CREW tab owns this edge */
body.crew-rail-off .crew-resizer { display: none; }
/* cinema mode collapses both side columns to 0 and hides #left (warroom.css) — a seam over the
   fullscreen feed would look like a control and move nothing */
#screen-game.cinema .crew-resizer { display: none; }
/* ≤1000px the columns are hard-coded to keep the stage alive (see the breakpoints below); the var
   the seam writes has no effect there, so the handle must not pretend otherwise */
@media (max-width: 1000px) { .crew-resizer { display: none; } }
/* while dragging: column-resize cursor everywhere + no text selection + canvas ignores the pointer */
body.col-resizing { cursor: col-resize; user-select: none; }
body.col-resizing #stage { pointer-events: none; }

/* ---------- CREW / SESSIONS divider: drag the rail's own horizontal split (leftrail.js) ----------
   Same law as the column seams: THE HIT ZONE MUST SIT ON THE SEAM THE EYE SEES. Here the seam the
   eye reads is the 10px gap between the recessed WORKING/IDLE strip and the framed SESSIONS module
   (panelchrome.css §4) — so the handle is a ZERO-HEIGHT flex child sitting between them, and its
   ::before straddles that gap. The flex flow puts it exactly on the boundary at every roster
   height, so there is no arithmetic against the strip's margin or the module's frame; ::after is
   the same invisible-until-hovered grip the column seams use, turned 90°. */
.row-resizer {
  flex: 0 0 auto; height: 0; position: relative; z-index: 12; touch-action: none;
}
/* the hit strip: the whole gap, plus reach ABOVE it into the (inert) counter strip rather than
   below it — 2px of stolen SESSIONS head is 2px the tabs no longer get */
.row-resizer::before {
  content: ''; position: absolute; left: 6px; right: 6px; top: -6px; height: 16px;
  cursor: row-resize;
}
.row-resizer::after {                       /* the grip */
  content: ''; position: absolute; left: 50%; top: 4px; width: 46px; height: 2px;
  margin-left: -23px; border-radius: 2px; background: var(--ph-dim); opacity: 0;
  pointer-events: none; transition: opacity .12s, background .12s, box-shadow .12s;
}
.row-resizer:hover::after, body.row-resizing .row-resizer::after {
  opacity: 1; background: var(--ph); box-shadow: 0 0 8px var(--ph-glow2);
}
body.row-resizing { cursor: row-resize; user-select: none; }
/* the roster dragged fully SHUT (leftrail.js writes --crew-cap:0 and this class together). The cap
   is a max-height, which panelchrome's 1px/3px padding sits OUTSIDE — so without this the closed
   list still paints a 4px sliver under the header and reads as a rendering fault, not a choice.
   id+class outranks panelchrome's later `#crew` rule, so load order does not matter here. */
#crew.shut { padding: 0; }

/* the v7 screen-flicker animation targets #app (which this app doesn't
   have) — re-point it at the live station so the SETTINGS toggle works */
body:not(.no-flicker) #screen-game.active { animation: flick 7s infinite; }

/* top bar: reuse the v7 logo (#logo, .logo-*) and .tb-stat; subtitle fit */
#topbar .logo-sub { white-space: nowrap; }

/* CREW header — the same solid ink-on-phosphor bar COMMS (#chat-panel h3) and the
   SESSIONS rail head (.ws-head) wear; the old engraved-rule h3 read as leftover v7 chrome */
#left h3 {
  justify-content: space-between; align-items: baseline; flex-shrink: 0;
  color: var(--ink); background: var(--ph); padding: 4px 12px; letter-spacing: 2px;
  border-radius: 7px 7px 0 0;
}
#left h3::after { display: none; }   /* the global engraved-rule ::after doesn't belong on a solid bar */
#left h3 .h3-aux { color: var(--ink); opacity: .7; text-shadow: none; font-size: 12px; letter-spacing: 1px; order: 0; }   /* order reset: the global .h3-aux order:2 would shove the count past the ◂ button */
/* right cluster: agent count + the hide control, so space-between keeps them together */
.crew-h3-right { display: inline-flex; align-items: center; gap: 7px; }
/* ◂ hide — same ink-bordered button vocabulary as the SESSIONS head's + NEW */
.crew-hide {
  font-family: inherit; font-size: 12px; line-height: 1.2; color: var(--ink);
  background: transparent; border: 1px solid rgba(0,0,0,.45); border-radius: 3px;
  cursor: pointer; padding: 1px 6px;
}
.crew-hide:hover { background: rgba(0,0,0,.2); }
/* ▸ CREW restore — a slim phosphor drawer handle on the stage's left edge while the rail is hidden.
   Sits at z4, above the tube-face overlay (z2) and cam HUD (z3, pointer-events:none). */
.crew-show {
  position: absolute; left: 0; top: 50%; transform: translateY(-50%); z-index: 4;
  writing-mode: vertical-rl; text-orientation: mixed;
  font-family: inherit; font-size: 12px; letter-spacing: 2px; color: var(--ph);
  background: rgba(5,3,1,.82); border: 1px solid var(--ph-dim); border-left: 0;
  border-radius: 0 5px 5px 0; padding: 10px 3px; cursor: pointer;
  text-shadow: 0 0 6px var(--ph-glow2);
  transition: color .12s, border-color .12s, box-shadow .12s;
}
.crew-show:hover { color: var(--ph-bright); border-color: var(--ph); box-shadow: 0 0 10px var(--ph-glow2); }
.crew-show[hidden] { display: none; }

/* crew manifest empty state */
.crew-empty { padding: 14px 10px; font-size: 13px; color: var(--ph-dim); }

/* bottom-bar right cluster status pill (honest activity light, not a number) */
#status-pill {
  background: linear-gradient(180deg, rgba(var(--ph-rgb),.07), rgba(0,0,0,.5));
  color: var(--ph); font-size: 14px; letter-spacing: 2px;
  padding: 2px 13px; border: 1px solid var(--ph); border-radius: 999px; text-shadow: 0 0 5px var(--ph-glow2);
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.14), 0 0 12px var(--ph-glow2), inset 0 0 10px rgba(0,0,0,0.6);
}
#status-pill.working { color: var(--gold); border-color: var(--gold); box-shadow: 0 0 12px rgba(var(--gold-rgb),0.35); }
#status-pill.thinking { color: var(--ph-bright); }
/* E1: LINK DOWN — the pill can only read ONLINE while the sidecar bridge is actually up. When the
   link dies it flips to a red-phosphor OFFLINE, matching the canvas LINK DOWN marker (#ff6a4c) with
   a slow breathing pulse so it reads as a live fault, never a frozen label. */
#status-pill.down {
  color: var(--link-down); border-color: rgba(255,80,60,0.6);
  text-shadow: 0 0 6px rgba(255,80,60,0.9);
  box-shadow: 0 0 12px rgba(255,80,60,0.35), inset 0 0 10px rgba(0,0,0,0.6);
  animation: pill-down-breathe 2.2s ease-in-out infinite;
}
@keyframes pill-down-breathe { 0%,100% { opacity: 0.62; } 50% { opacity: 1; } }
/* STANDBY: the bridge isn't proven up yet — a dim, neutral pill (never the confident ONLINE green). */
#status-pill.standby {
  color: var(--ph-dim); border-color: var(--ph-dim);
  text-shadow: none; box-shadow: inset 0 0 10px rgba(0,0,0,0.6);
}

/* ============================================================
   GROUPED BOTTOM-BAR DOCKS (navdock.js) — the flat panel
   buttons folded into 4 labelled triggers (CREW/WORK/BUILD/SYSTEM),
   each opening an upward popover of its items. Kills the cryptic
   one-row glyph wall. Two DISTINCT trigger signals (never one
   overloaded dot): a gold underline = "a panel from this group is
   open"; the SYSTEM trigger's warn chip = the unread NOTIFS count.
   ============================================================ */
#bottombar { gap: 4px; }
.bb-group { position: relative; display: flex; align-items: center; }
.bb-grp {
  font-family: inherit; color: var(--ph); background: transparent;
  border: 1px solid var(--ph-faint); padding: 0 16px; cursor: pointer;
  letter-spacing: 2px; font-size: 16px; text-shadow: inherit;
  display: inline-flex; align-items: center; gap: 8px;
  /* FIXED trigger box: the four group glyphs render from different fallback fonts with
     different intrinsic heights, so line-height:normal made the buttons 32/33/35/33px tall
     and visibly misaligned. A hard height + line-height:1 pins every trigger to one box. */
  height: 32px; line-height: 1;
}
/* the trigger glyph in its own fixed cell so uneven glyph advance widths can't shove labels around */
.bb-gi { display: inline-block; width: 18px; text-align: center; flex: 0 0 auto; }
.bb-grp:hover { color: var(--ph-bright); border-color: var(--ph); background: var(--ph-faint); }
.bb-group.open .bb-grp { color: var(--ink); background: var(--ph); border-color: var(--ph); text-shadow: none; }

/* "panel open here" = gold underline engraved into the trigger's bottom edge */
.bb-group.has-active .bb-grp { box-shadow: inset 0 -3px 0 var(--gold), 0 0 10px rgba(var(--gold-rgb),.25); }
.bb-group.open.has-active .bb-grp { box-shadow: inset 0 -3px 0 var(--ink); }

/* unread NOTIFS count mirrored onto the SYSTEM trigger (navdock.syncGroupState) */
.bb-grp-badge {
  min-width: 16px; padding: 0 4px; border-radius: 8px; flex: 0 0 auto;
  font-size: 11px; line-height: 15px; letter-spacing: 0; text-align: center;
  color: var(--ink); background: var(--warn); text-shadow: none;
  box-shadow: 0 0 8px var(--warn); animation: bb-alert 1.4s ease-in-out infinite;
}
.bb-group.open .bb-grp-badge { animation: none; box-shadow: none; }
@keyframes bb-alert { 0%,100% { opacity: 1; } 50% { opacity: .45; } }

/* the popover: opens UPWARD from the trigger, styled as a small CRT panel.
   Items are TWO-LINE (.bb-i glyph cell · .bb-tx name + dim description) so the menu
   explains each destination instead of listing bare nouns. */
.bb-menu {
  display: none; position: absolute; left: 0; bottom: calc(100% + 7px); z-index: 60;
  min-width: 300px; flex-direction: column; gap: 2px; padding: 6px;
  background: rgba(4,2,1,.98); border: 2px solid var(--ph); border-radius: 7px;
  box-shadow: 0 0 0 3px rgba(0,0,0,.9), 0 -6px 26px rgba(0,0,0,.75), 0 0 20px var(--ph-glow2);
}
.bb-group.open .bb-menu { display: flex; }
.bb-menu .bb {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  border: 1px solid transparent; padding: 6px 10px; font-size: 15px; letter-spacing: 1.5px;
}
.bb-menu .bb .bb-i { flex: 0 0 20px; width: 20px; text-align: center; font-size: 15px; }
.bb-menu .bb .bb-tx { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.bb-menu .bb .bb-tx b { font-weight: inherit; font-size: 15px; letter-spacing: 1.5px; }
.bb-menu .bb .bb-tx small {
  font-size: 12px; letter-spacing: .4px; color: var(--ph-dim); text-shadow: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bb-menu .bb:hover { background: var(--ph-faint); color: var(--ph-bright); border-color: var(--ph-dim); }
.bb-menu .bb:hover .bb-tx small, .bb-menu .bb:focus-visible .bb-tx small { color: var(--ph); }
.bb-menu .bb:hover::before, .bb-menu .bb:hover::after { content: none; }  /* no pip-boy [..] brackets inside the menu */
.bb-menu .bb.active { background: var(--ph-faint); color: var(--ph-bright); box-shadow: inset 0 0 0 2px var(--ph); }

/* MOBILE CONTROL REACHABILITY: navdock.js supplies the final zoom-aware horizontal
   clamp because each trigger has a different wrapped-row origin. CSS owns the maximum
   hardware width and ellipsis so the menu itself can never demand a wider viewport. */
@media (max-width: 600px) {
  .bb-menu {
    min-width: 0;
    width: min(300px, calc(100vw - 16px));
    max-width: calc(100vw - 16px);
    box-sizing: border-box;
  }
  .bb-menu .bb { min-width: 0; }
}

/* one-time "what now" coach pointing at the new docks */
.nav-coach {
  position: fixed; left: 14px; bottom: 58px; z-index: 70; max-width: 470px;
  display: flex; align-items: center; gap: 12px; padding: 9px 13px;
  font-size: 14px; color: var(--text); letter-spacing: .4px; line-height: 1.35;
  background: rgba(4,2,1,.97); border: 2px solid var(--ph); border-radius: 7px;
  box-shadow: 0 0 0 3px rgba(0,0,0,.9), 0 6px 24px rgba(0,0,0,.7), 0 0 20px var(--ph-glow2);
  animation: coach-in .35s ease-out;
}
.nav-coach[hidden] { display: none; }
.nav-coach b { color: var(--ph-bright); font-weight: normal; }
.nav-coach-x {
  flex: 0 0 auto; font-family: inherit; font-size: 12px; letter-spacing: 1px; cursor: pointer;
  color: var(--ink); background: var(--ph); border: none; border-radius: 3px; padding: 4px 10px;
}
.nav-coach-x:hover { background: var(--ph-bright); }
.nav-coach-arrow { position: absolute; left: 32px; bottom: -15px; color: var(--ph); font-size: 17px; text-shadow: 0 0 7px var(--ph-glow); }
@keyframes coach-in { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }

/* TASK BOARD — add-row + per-card actions (new controls over v7 .kb-* cards) */
.kb-add { display: flex; gap: 8px; margin-bottom: 10px; }
.kb-add input { flex: 1; font-size: 15px; padding: 5px 8px; }
.kb-acts { display: flex; gap: 4px; margin-top: 5px; flex-wrap: wrap; align-items: center; }
/* the meta glyphs (rename/pin/archive) are ONE right-aligned cluster (.kb-meta-keys) that wraps as
   a unit: verbs read left, housekeeping right, and a narrow card gets two structured rows — never a
   stranded ⌫. Glyph-only keys sit in a tighter, equal box (single fallback-font symbols —
   box-centred, never padded from font metrics). */
.kb-acts .kb-meta-keys { margin-left: auto; display: inline-flex; gap: 4px; }
.kb-acts .kb-meta-keys button { padding: 1px 0; min-width: 24px; }
/* the card keys wear the SAME molded matte keycap as the console's .bb.sm/.bb.xs (the app.css:100
   material block): top light, phosphor body, shaded base, contact shadow, press sink. Only the
   surface + radius — the compact metrics stay exactly as they were so no card reflows. The base
   shade is 1px (not the sm key's 2px): these keys are ~20px tall and a 2px base reads as a ledge. */
.kb-acts button {
  font-family: inherit; color: var(--ph-dim);
  border: 1px solid var(--ph-faint); cursor: pointer; font-size: 12px; padding: 1px 7px; letter-spacing: 1px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 3px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ph) 6%, var(--panel2)) 0%,
    var(--panel2) 58%,
    color-mix(in srgb, #000 20%, var(--panel2)) 100%);
  box-shadow:
    inset 0 1px 0 rgba(var(--ph-rgb), .08),
    inset 0 -1px 0 rgba(0,0,0,.28),
    0 1px 2px rgba(0,0,0,.45);
}
.kb-acts button:hover {
  color: var(--ph-bright); border-color: var(--ph);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ph) 11%, var(--panel2)) 0%,
    var(--panel2) 62%,
    color-mix(in srgb, #000 16%, var(--panel2)) 100%);
  box-shadow:
    inset 0 1px 0 rgba(var(--ph-rgb), .12),
    inset 0 -1px 0 rgba(0,0,0,.28),
    0 1px 2px rgba(0,0,0,.45),
    0 0 10px var(--ph-glow2);
}
/* pressed = the key is DOWN: the seat sinks and the base light goes out (same verb as .bb.sm) */
.kb-acts button:active { transform: translateY(1px); box-shadow: inset 0 2px 4px rgba(0,0,0,.42); }
.kb-acts .assign { color: var(--gold); border-color: var(--ph-dim); }
/* empty-state wrapper: NO dimming — the .empty-state component (glyph + copy + CTA) is already designed at the
   right strength; the old opacity:0.4 ghosted even the + ADD ONE call-to-action. */
.kb-empty-col { padding: 6px 2px; }
/* bound-agent chip on a board card — a colored dot (the agent's roster color) + its name. Truthful telemetry:
   sourced from the stream's real agentId resolved against the live roster. */
.kb-agent { display: inline-flex; align-items: center; gap: 4px; color: var(--ph); }
.kb-agent-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; box-shadow: 0 0 5px currentColor; }
.kb-deliv { color: var(--gold); }
/* IN-PROGRESS run-state chip — truthful: RUNNING only while Channels.isBusy, else DONE — REVIEW & SHIP.
   Reuses the rail's live pulse (.ws-dot.running / @keyframes wsPulse) so the world speaks one visual language. */
.kb-state { display: flex; align-items: center; gap: 5px; margin-top: 4px; font-size: 11px; letter-spacing: 1px; }
.kb-state.running { color: var(--ph-bright); }
.kb-state.done { color: var(--gold); }
.kb-state.failed { color: var(--bad); }
.kb-live { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--ph-bright); animation: wsPulse 1.15s ease-in-out infinite; }

/* SKILLS — honest capability readout (reuses v7 .perk-grid/.perk) */
.term.skills-term { max-height: var(--term-band, calc(100vh * var(--sn-unzoom, 1) - 96px)); }
.term.skills-term .term-body { flex: 1 1 auto; min-height: 0; }
.sk-note { font-size: 12px; opacity: 0.62; margin-top: 12px; line-height: 1.45; }
/* the locked status line ("○ NO CABINET AT DESK") reads at --ph, not --ph-dim: at the perk's 0.65 opacity a dim
   grey vanished — this is the honest "why it's off", it has to be readable. */
.perk-stat { font-size: 10px; color: var(--ph); margin-top: 2px; letter-spacing: 1px; }
.perk.on .perk-stat { color: var(--ok); }
.perk.on .perk-stat.ask { color: var(--warn); }   /* granted, but pauses for one-click consent */
/* a locked capability card is a live deep-link into REFIT to place its missing prop — show it, and surface the
   affordance so the click is discoverable (it brightens on hover). */
.perk-locked { cursor: pointer; transition: opacity var(--t-fast, .12s) ease, border-color var(--t-fast, .12s) ease; }
.perk-locked:hover, .perk-locked:focus-visible { opacity: 0.9; outline: none; border-color: var(--ph-dim); }
.perk-place { font-size: 10px; color: var(--gold); letter-spacing: 1px; margin-top: 3px; opacity: 0.72; transition: opacity var(--t-fast, .12s) ease; }
.perk-locked:hover .perk-place, .perk-locked:focus-visible .perk-place { opacity: 1; }

/* SKILL LIBRARY — pre-installed, capability-gated recipe packs (the browser in the SKILLS panel) */
.sk-lib-intro { margin-top: 4px; }
.sk-lib { margin-top: 8px; }
.sk-loading { font-size: 12px; opacity: 0.6; padding: 10px 2px; }
.sk-lib-sum { font-size: 11px; letter-spacing: 0.5px; color: var(--ph-dim); margin: 2px 0 8px; }
.sk-lib-sum b { color: var(--ok); }
.sk-cat { font-size: 10px; letter-spacing: 2px; color: var(--gold); opacity: 0.85; margin: 13px 0 5px; text-transform: uppercase; }
.sk-card { border: 1px solid var(--ph-faint); border-radius: var(--r-sm); margin-bottom: 6px; background: var(--panel2); box-shadow: var(--raise); overflow: hidden; transition: border-color 0.15s; }
.sk-card.on { border-color: var(--ph-dim); box-shadow: var(--raise), inset 0 0 14px var(--ph-glow2); }
.sk-card.want { border-color: var(--warn); }
.sk-card-head { display: flex; align-items: flex-start; gap: 9px; padding: 8px 9px; }
.sk-toggle { flex: 0 0 auto; width: 26px; height: 26px; border: 1px solid var(--ph-dim); border-radius: 50%; background: transparent; color: var(--ph-dim); font-size: 15px; cursor: pointer; line-height: 1; padding: 0; transition: all 0.15s; }
.sk-card.on .sk-toggle { color: var(--ok); border-color: var(--ok); box-shadow: 0 0 8px var(--ph-glow2); }
.sk-card.want .sk-toggle { color: var(--warn); border-color: var(--warn); }
.sk-toggle:hover { background: var(--ph-faint); }
.sk-toggle.busy { opacity: 0.4; pointer-events: none; }
.sk-card-main { flex: 1 1 auto; min-width: 0; }
.sk-name-row { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.sk-name { font-size: 13px; color: var(--ph-bright); }   /* emphasis via bright phosphor, not synthesized bold (VT323 is single-weight) */
.sk-reqs { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.sk-badge { font-size: 10px; letter-spacing: 0.5px; padding: 1px 5px; border-radius: 2px; border: 1px solid var(--ph-faint); color: var(--ph-dim); white-space: nowrap; }
.sk-badge.have { color: var(--ok); border-color: var(--ok); }
.sk-badge.miss { color: var(--warn); border-color: var(--warn); }
.sk-badge.free { opacity: 0.5; }
.sk-badge.cat { color: var(--ph); border-color: var(--ph-dim); opacity: 0.85; }   /* A4: category tag (also makes category searchable) */
.sk-desc { font-size: 11.5px; opacity: 0.78; margin-top: 3px; line-height: 1.4; }
.sk-stat { font-size: 10.5px; letter-spacing: 1px; margin-top: 5px; color: var(--ph-dim); }
.sk-stat.on { color: var(--ok); }
.sk-stat.want { color: var(--warn); }
.sk-expand { flex: 0 0 auto; background: transparent; border: none; color: var(--ph-dim); font-size: 14px; cursor: pointer; padding: 2px 4px; line-height: 1; }
.sk-expand:hover { color: var(--gold); }
.sk-body { display: none; border-top: 1px solid var(--ph-faint); padding: 8px 10px; background: rgba(0, 0, 0, 0.25); }
.sk-card.open .sk-body { display: block; }
.sk-body pre { white-space: pre-wrap; word-wrap: break-word; font-size: 11px; line-height: 1.45; color: var(--text); opacity: 0.92; margin: 0; font-family: inherit; max-height: 340px; overflow-y: auto; }
.sk-attr { font-size: 10.5px; opacity: 0.5; margin-top: 8px; font-style: italic; }
/* WITHHELD: the skill guard is keeping this saved skill out of the agent's prompt. Reads as a
   quarantine, not a decoration — warn phosphor on the card edge, and the reason line is the one
   .sk-attr that is NOT dimmed to a whisper (it is the whole reason the card looks different). */
.sk-card.held { border-color: var(--warn); }
.sk-card.held .sk-toggle { color: var(--warn); border-color: var(--warn); }
.sk-attr.sk-held { opacity: 0.95; font-style: normal; color: var(--warn); letter-spacing: 0.3px; line-height: 1.4; }

/* SKILL EXCHANGE — inspect-first distribution. Provenance is allowed to wrap instead of truncating:
   the URL and digest are the evidence that the preview and installed bytes are the same artifact. */
.sk-exchange-form { margin: 12px 0 10px; }
.sk-exchange-form label { display: block; margin-bottom: 5px; color: var(--gold); font-size: 10px; letter-spacing: 1.8px; }
.sk-exchange-row { display: flex; align-items: stretch; gap: 7px; }
.sk-exchange-row input[type=url] { min-width: 0; font-size: 14px; padding: 7px 9px; }
.sk-exchange-row .consent-btn { flex: 0 0 auto; margin: 0; }
.sk-exchange-preview { margin-top: 8px; }
.sk-prov { display: grid; grid-template-columns: 112px minmax(0, 1fr); gap: 8px; margin: 0 0 6px; font-size: 10.5px; line-height: 1.35; }
.sk-prov > span { color: var(--gold); letter-spacing: 0.8px; }
.sk-prov a, .sk-prov code { color: var(--ph); font: inherit; overflow-wrap: anywhere; word-break: break-word; }
.sk-prov a:hover { color: var(--ph-bright); }
.sk-prov + .sk-body pre, .sk-prov ~ pre { margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--ph-faint); }
.sk-exchange-note { margin-top: 9px; padding: 7px 8px; border: 1px solid var(--ph-faint); background: rgba(0,0,0,.22); font-size: 11px; line-height: 1.4; }
.sk-exchange-note.ok, .sk-exchange-done { color: var(--ok); border-color: var(--ok); }
.sk-exchange-note.warn { color: var(--warn); border-color: var(--warn); }
.sk-exchange-note.bad { color: var(--danger); border-color: var(--danger); }
.sk-exchange-done { padding: 10px; border: 1px solid; font-size: 12px; letter-spacing: .4px; }
.sk-update-slot:empty { display: none; }
.sk-update-slot:not(:empty) { margin-top: 10px; }
.sk-update-slot .sk-card { margin: 0; }
@media (max-width: 620px) {
  .sk-exchange-row { flex-direction: column; }
  .sk-prov { grid-template-columns: 1fr; gap: 2px; }
}

/* SKILLS redesign (legibility) — a real ON/OFF pill SWITCH (the user's choice) + a SEPARATE readiness chip (the
   floor's grant), so the two axes never collapse into one ambiguous string. State sections group the library by
   READY / NEEDS GEAR / OFF; the PLACE button deep-links a locked card's missing gear into the real REFIT surface. */
/* the one-line mental-model strip at the top of CAPABILITIES: the causal chain, in the panel's eerie sk-note voice. */
.sk-chain { font-size: 10px; letter-spacing: 2px; color: var(--ph-dim); opacity: 0.8; margin: 0 0 10px; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.sk-chain-arr { color: var(--gold); opacity: 0.7; }
.sec.sk-state-sec { margin-top: 12px; }
.sec.sk-state-sec:first-child { margin-top: 4px; }
/* the pill switch — unmistakably a control, not a status dot. Track slides a knob; the ON/OFF word is the label. */
.sk-switch { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; background: transparent; border: none; padding: 2px 0; cursor: pointer; line-height: 1; }
.sk-sw-track { position: relative; width: 30px; height: 16px; border-radius: 9px; border: 1px solid var(--ph-dim); background: rgba(0,0,0,0.3); transition: all 0.15s; }
.sk-sw-knob { position: absolute; top: 1px; left: 1px; width: 12px; height: 12px; border-radius: 50%; background: var(--ph-dim); transition: all 0.15s; }
.sk-switch.on .sk-sw-track { border-color: var(--ok); box-shadow: 0 0 8px var(--ph-glow2); }
.sk-switch.on .sk-sw-knob { left: 15px; background: var(--ok); }
.sk-sw-label { font-size: 10px; letter-spacing: 1.5px; color: var(--ph-dim); min-width: 20px; }
.sk-switch.on .sk-sw-label { color: var(--ok); }
.sk-switch:hover .sk-sw-track { border-color: var(--ph); }
.sk-switch.busy { opacity: 0.4; pointer-events: none; }
/* readiness chip — the floor's grant, held apart from the switch. READY = green, NEEDS <GEAR> = amber. */
.sk-ready { font-size: 10px; letter-spacing: 1px; padding: 1px 6px; border-radius: 2px; border: 1px solid var(--ph-faint); color: var(--ph-dim); white-space: nowrap; }
.sk-ready.ok { color: var(--ok); border-color: var(--ok); }
.sk-ready.gear { color: var(--warn); border-color: var(--warn); }
/* PLACE button — lands the user in the real REFIT placement surface for the missing object. */
.sk-place-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.sk-place { font-size: 10.5px; letter-spacing: 0.5px; padding: 3px 8px; border-radius: var(--r-sm); border: 1px solid var(--warn); background: transparent; color: var(--warn); cursor: pointer; transition: all 0.15s; }
.sk-place:hover { background: var(--warn); color: var(--bg, #000); box-shadow: 0 0 8px var(--warn); }
.sk-reqs { margin-top: 5px; }

/* CONNECTIONS — PROVIDERS + API KEYS sections inside the SETTINGS panel.
   Real BYOK state: a green dot/glow means actually connected; dim cards are
   honestly-locked "coming soon" transports (same treatment as locked SKILLS). */
.prov-list, .key-list { display: flex; flex-direction: column; gap: 6px; }
.prov-card, .key-row {
  display: flex; align-items: center; gap: 10px;
  border: 1px solid var(--ph-dim); background: rgba(0, 0, 0, 0.3); padding: 7px 10px;
  border-radius: var(--r-sm); box-shadow: var(--raise);
}
.prov-card.soon { opacity: 0.45; border-color: var(--ph-faint); }          /* not-yet-shipped: fully locked */
.prov-card.avail { opacity: 0.8; border-color: var(--ph-dim); }            /* live but keyless: actionable, not locked */
.prov-card.conn { box-shadow: var(--raise), inset 0 0 12px var(--ph-glow2); }
.prov-select {
  display: flex; align-items: center; gap: 10px; flex: 1 1 auto; min-width: 0;
  border: 0; background: transparent; padding: 0; color: inherit; font: inherit;
  text-align: left; cursor: pointer;
}
.prov-select:focus-visible { outline: 1px solid var(--gold); outline-offset: 3px; }
.conn-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ph-dim); flex: none; }
.prov-card.conn .conn-dot, .key-row .conn-dot { background: var(--ok); box-shadow: 0 0 8px rgba(123, 200, 138, 0.7); }
.prov-main, .key-main { flex: 1; min-width: 0; }
.prov-select .prov-main, .prov-select .prov-ep { display: block; }
.prov-name { font-size: 14px; color: var(--ph-bright); letter-spacing: 1px; display: flex; align-items: center; gap: 8px; }
.prov-badge, .key-stat.on {
  font-size: 10px; letter-spacing: 1px; color: var(--ink); background: var(--gold);
  padding: 0 5px; text-shadow: none;
}
.prov-ep { font-size: 11px; color: var(--ph-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* a flex ROW, not baseline-flowed inline content: the label and its action key centre against each
   other by layout. As inline siblings the key sat a measured 2.50px off the label's centre on every
   provider row, and no margin can fix that (the key's own label mixes two fonts — see COMPACT
   CONSOLE KEYS). The text keeps its own span so the "N keys" <i> still stacks UNDER the label. */
.prov-stat {
  display: flex; align-items: center; justify-content: flex-end; gap: 12px;
  font-size: 11px; color: var(--ph); text-align: right; letter-spacing: 1px; white-space: nowrap;
}
.prov-stat-t { min-width: 0; }
.prov-card.soon .prov-stat { color: var(--ph-dim); }                       /* locked: muted */
.prov-card.avail .prov-stat { color: var(--gold); }                        /* "NO KEY" is an action prompt, not a disabled state */
.prov-stat i { display: block; font-style: normal; color: var(--ph-dim); font-size: 10px; }

.key-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.key-prov { font-size: 12px; color: var(--ph); letter-spacing: 1px; }
.key-mask {
  font-family: inherit; font-size: 13px; color: var(--ph-bright); letter-spacing: 1px;
  background: rgba(0, 0, 0, 0.4); border: 1px solid var(--ph-faint); padding: 0 6px;
}
.key-meta { font-size: 11px; color: var(--ph-dim); margin-top: 2px; }
.key-meta b { color: var(--ph); font-weight: normal; }
.key-stat { font-size: 11px; color: var(--ph-dim); letter-spacing: 1px; }
.key-acts { display: flex; flex-direction: column; gap: 4px; flex: none; }
.key-edit { display: flex; gap: 6px; margin-top: 4px; }
.key-edit[hidden] { display: none; }
.key-input { flex: 1; font-size: 13px; padding: 4px 8px; }
.key-empty { font-size: 13px; color: var(--ph); padding: 4px 2px; display: flex; flex-direction: column; gap: 6px; }
.key-empty p { margin: 0; font-size: 12px; color: var(--text); opacity: 0.75; }

/* settings-truth: a KNOWN-dead ChatGPT sign-in (the sidecar recorded a consumed/invalid refresh token).
   --bad is the sole semantic red; the card/row must read as a fault demanding RECONNECT, never as SIGNED IN. */
.prov-card.expired { opacity: 1; border-color: var(--bad); }
.prov-card.expired .prov-stat { color: var(--bad); }
.prov-card.expired .conn-dot, .key-row.expired .conn-dot { background: var(--bad); box-shadow: 0 0 8px rgba(200, 90, 80, 0.7); }
.key-stat.bad { color: var(--bad); }
.key-mask.key-mask-dead { color: var(--bad); border-color: var(--bad); }
.key-edit.codex-inline { flex-wrap: wrap; align-items: center; font-size: 12px; }
.key-edit.codex-inline code { font-size: 15px; letter-spacing: 2px; }

/* ============================================================
   CANONICAL DANGER-ARMED SKIN — the ONE "click again to confirm" look.
   Every destructive arm/confirm button inverts to a filled --bad chip
   (ink on red). --bad stays the sole semantic red so it reads correctly
   in every theme. !important so the armed state always wins over the
   button's resting/hover skin regardless of source file. The gold
   consent-armed (.mkt-cta*) and the .bb.danger.armed pulse animation
   (motion.css) are intentionally separate; per-button extras (the
   .ws-menu glyph tint, .ag-del glow, .q-dismiss padding) sit below.
   ============================================================ */
.mkt-del.armed, .mkt-recipe-del.armed, .mkt-fam-forget.armed,
.bb.sm.danger.armed, .ws-menu-item.danger.armed, .ag-del.armed, .q-dismiss.armed,
.mkt-del.armed:hover, .mkt-recipe-del.armed:hover, .mkt-fam-forget.armed:hover,
.bb.sm.danger.armed:hover, .ws-menu-item.danger.armed:hover, .ag-del.armed:hover, .q-dismiss.armed:hover {
  color: var(--ink) !important; background: var(--bad) !important;
  border-color: var(--bad) !important; text-shadow: none !important;
}
.key-row.rm-armed { border-color: var(--bad); box-shadow: var(--raise), inset 0 0 0 1px var(--bad); }
.key-row.rm-armed .key-main { opacity: 0.72; }
.rm-hint { font-size: 10px; letter-spacing: 1px; color: var(--bad); text-align: right; margin-top: 2px; }

/* settings-truth: inline "+ ADD KEY" on a NO-KEY provider card — a collapsible paste-and-save row that reuses the
   same Harness.setKey path as the key list, so a beginner never hunts for where keys live. The card wraps so the
   editor sits on its own full-width line below the name/status; gold accent = actionable, matching "NO KEY". */
.prov-card { flex-wrap: wrap; }
.prov-addkey { flex: none; }  /* .prov-stat's flex gap owns the spacing now — a margin here would re-break the centring */
.prov-key-edit { flex-basis: 100%; margin-top: 6px; }
.prov-key-edit[hidden] { display: none; }

/* settings-truth: per-field source badge (BUDGET) — is this limit a value you SAVED here, or the environment default
   it currently follows? Honest, non-blocking. Env default is a neutral gold-inset chip (it is NOT "ignored" — env is
   the fallback, and a saved value always wins); a saved value reads calmer. Never a literal amber — --gold-rgb only. */
.src-badge {
  font-size: 10px; letter-spacing: 1px; text-transform: uppercase; padding: 0 5px; border-radius: 2px;
  border: 1px solid var(--ph-faint); color: var(--ph-dim); vertical-align: middle; white-space: nowrap;
}
.src-badge[hidden] { display: none; }
.src-badge.src-env { color: var(--gold); border-color: rgba(var(--gold-rgb), .55); background: rgba(var(--gold-rgb), .06); }
.src-badge.src-saved { color: var(--ph); border-color: var(--ph-faint); background: rgba(0, 0, 0, .25); }

/* ---------- WORKSTREAMS rail (left) ---------- */
/* takes the left rail's slack (see the RAIL SPACE SPLIT note on #crew in style.css) */
#ws-wrap { margin-top: 12px; display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
/* THE STRIP MAY NARROW, IT MAY NOT FOLD. SESSIONS/PROJECTS + NEW is three fixed labels in a
   column the grid can make 200px wide (the ≤1120px breakpoint) and that the CREW seam can now
   land anywhere in — and a squeezed flex item wraps its OWN text before the row ever admits it
   is out of room: "+ NEW" broke across two lines, doubling the strip's height and blowing it
   out of the molded head. Andrew: "it condenses horrendously." nowrap on the labels makes the
   overflow HONEST (a strip that stays one line and tightens) instead of a folded button, and
   the tabs give up their padding first because a view switcher survives being snug — the
   action button does not survive being two lines. */
.ws-head {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: nowrap; gap: 6px; min-width: 0;
  color: var(--ink); background: var(--ph); padding: 3px 10px; letter-spacing: 1px;
  font-size: 14px; border-radius: 6px 6px 0 0;
}
.ws-head .ws-view { flex: 0 1 auto; min-width: 0; }
.ws-head .ws-head-acts { flex: 0 0 auto; }
.ws-head .ws-tab, .ws-head .ws-head-acts > button { white-space: nowrap; }
/* + NEW joins the tabs' control family. Measured off the live rail before touching it: the tabs
   stood 15.97px with 3px rounded outer ends, + NEW stood 14.00px with SQUARE corners (it declares
   none, and the control floor at the top of this file deliberately paints no radius) — two corner
   languages and two heights inside one 22px strip. Matching padding/radius puts all three chips on
   one baseline; the raised top hairline (against .ws-tab.on's inset well) is what now separates the
   ACTION from the view switcher, so they can share a silhouette without reading as three tabs.
   HORIZONTAL padding stays 6px/6px on purpose. A draft widened it to 8px/7px to optically centre
   the label against letter-spacing's trailing track, and the narrow rail priced it: + NEW is
   `flex: 0 0 auto`, so every pixel it gains comes straight out of the tab group that yields first.
   Measured at a 900px viewport, .ws-view went 117.70px -> 114.70px — the switcher pays for the
   action button's cosmetics by clipping its own labels. Not worth 1px of centring.
   The extra 1px VERTICAL padding is what lifts 14.00px to the tabs' 15.97px, and costs the row
   nothing: .ws-head stays 21.94px and one line at every width down to 820px. */
#ws-new {
  font-family: inherit; font-size: 12px; letter-spacing: 1px; color: var(--ink);
  background: transparent; border: 1px solid rgba(0,0,0,.45); cursor: pointer;
  padding: 1px 6px; border-radius: 3px;
  box-shadow: inset 0 1px 0 rgba(var(--ph-bright-rgb), .22);
  white-space: nowrap; flex: 0 0 auto;
}
#ws-new:hover { background: rgba(0,0,0,.2); }
/* RAIL SEARCH — search lives ON the rail (the SESSION TOOLS window is retired; its .ws-tools-* CSS is
   gone with it). type=search misses the base themed-input rule (which covers text/password/untyped
   only), so without this skin the input renders as a raw white browser default in the middle of the
   phosphor rail. Compact to sit between the SESSIONS head and the session rows. */
.ws-rail-search { padding: 5px 0 0; }
#ws-search {
  -webkit-appearance: none; appearance: none;
  box-sizing: border-box; width: 100%; min-width: 0;
  font: inherit; font-size: 13px; letter-spacing: 1px; color: var(--ph-bright);
  background: linear-gradient(180deg, #000, color-mix(in srgb, var(--ph) 3%, #000));
  border: 1px solid var(--ph-dim); border-radius: 3px; padding: 4px 8px; outline: none;
  text-shadow: 0 0 5px var(--ph-glow2);
  box-shadow: inset 0 2px 6px rgba(0,0,0,.7);
  transition: border-color .12s, box-shadow .12s;
}
/* id selector outranks the global input:focus rule, so restate the full focus shadow here */
#ws-search:focus { border-color: var(--ph); box-shadow: inset 0 2px 6px rgba(0,0,0,.7), 0 0 10px var(--ph-glow2); }
#ws-search::placeholder { color: var(--ph-dim); opacity: 1; }
#ws-search::-webkit-search-cancel-button { -webkit-appearance: none; }  /* Esc clears; the gray UA ✕ breaks the CRT look */
#ws-search-results { list-style: none; margin: 0; padding: 0; max-height: 150px; overflow-y: auto; }
#ws-search-results:empty { display: none; }
.ws-search-hit { display: flex; flex-direction: column; gap: 1px; padding: 4px 7px; cursor: pointer; border-left: 2px solid var(--ph-dim); }
.ws-search-hit:hover, .ws-search-hit:focus { color: var(--ph); background: var(--ph-faint); border-left-color: var(--ph); outline: 0; }
.ws-search-hit b { color: var(--ph); font-weight: normal; }
.ws-search-hit small { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* Diagnostics on-screen report block (Settings → SYSTEM, clipboard-failure fallback): the raw <pre>
   and its button bar previously rendered with browser defaults. Same well + border language as the
   other settings panels; buttons are the shared .bb.sm skin. */
.diag-block { margin-top: 8px; }
.diag-pre { font-family: inherit; font-size: 11px; line-height: 1.4; color: var(--ph); margin: 0;
  padding: 8px 10px; border: 1px solid var(--ph-dim); border-radius: 2px; background: rgba(0,0,0,.25); }
.diag-block-bar { display: flex; gap: 8px; margin-top: 6px; }
/* fills whatever #ws-wrap was given rather than a fixed 40vh slice — the old cap made the list scroll
   while empty rail sat above it. min-height:0 so the flex child may actually shrink and scroll. */
#workstreams {
  list-style: none; margin: 0; padding: 6px 0 0; overflow-y: auto;
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 4px;
}
/* display:flex above beats the UA [hidden] rule — without this, the PROJECTS tab couldn't actually
   hide the sessions list (both lists rendered stacked, so PROJECTS "showed the same sessions"). */
#workstreams[hidden] { display: none; }
.ws-row {
  display: flex; align-items: center; gap: 8px; padding: 5px 8px; cursor: pointer;
  border-left: 2px solid var(--ph-dim); background: rgba(0,0,0,.28);
}
.ws-row:hover { background: var(--ph-faint); border-left-color: var(--ph); }
.ws-row.sel { border-left-color: var(--ph-bright); background: var(--ph-faint); }
.ws-row.busy { border-left-color: var(--ph-bright); }
/* ---------- THE RAIL DOT: whose turn is it? ----------
   THREE states and nothing else, so one glance down the rail sorts the list:
     FADED   (.seen)     — you have seen everything here; nothing owed either way.
     BRIGHT  (.unseen)   — finished, output NOT seen yet. Steady: it waits as long as you need.
     FLASHING            — live right now. .working = the agent has it; .needsyou = it is paused
                           on YOU, and that is the only state on a fail-closed deny timer.
   Colour carries whose turn: GOLD is yours, PHOSPHOR is the agent's, DIM is nobody's. That rule is
   why the kanban lane no longer tints this dot — gold meant approval AND review AND 'shipped', so it
   meant nothing, and a read 'active' session outshone an unread one. Lane is in the row tooltip now.
   (.kb-live on the task board keeps its own copy of the pulse; it shares only @keyframes wsPulse.) */
.ws-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--ph-dim); }
.ws-dot.seen { background: var(--ph-dim); box-shadow: none; }
.ws-dot.unseen { background: var(--gold); box-shadow: 0 0 7px rgba(var(--gold-rgb), .6); }
.ws-dot.working { background: var(--ph-bright); animation: wsPulse 1.15s ease-in-out infinite; }
/* NEEDS YOU gets its own RHYTHM, not just its own colour — faster, and dipping harder. The six
   preset themes separate --gold from --ph-bright by ΔE 38-57, so colour alone would do there; the
   CUSTOM theme does not. Its --gold is the base hue rotated +42°, and hue is meaningless at low
   saturation, so a user who drags SATURATION toward 0 collapses the pair to two greys (measured:
   ΔE 65 at sat 100, 44 at 60, 23 at 25, 17 at 0). Both states pulse, so at that end the ONLY thing
   telling "the agent is working" from "it is blocked on you" would be a small lightness step.
   Rhythm survives every palette, and this is the one state on a fail-closed deny timer — so the
   more urgent beat is honest rather than decorative. */
.ws-dot.needsyou { background: var(--gold); animation: wsPulseUrgent .72s ease-in-out infinite; }
@keyframes wsPulseUrgent {
  0%, 100% { opacity: 1; box-shadow: 0 0 8px rgba(var(--gold-rgb), .7); }
  50% { opacity: .18; box-shadow: 0 0 2px rgba(var(--gold-rgb), .3); }
}
/* the right-edge token joins whatever the dot is saying, so the two never disagree */
.ws-dot.unseen ~ .ws-meta { color: var(--gold); letter-spacing: .5px; text-shadow: 0 0 6px rgba(var(--gold-rgb), .45); }
@keyframes wsPulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 7px var(--ph-glow); }
  50% { opacity: .3; box-shadow: 0 0 2px var(--ph-glow); }
}
.ws-title { flex: 1; min-width: 0; font-size: 14px; color: var(--ph); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ws-row.sel .ws-title { color: var(--ph-bright); }
/* UNREAD: the stream did real work since the Commander last had it open (lastActiveAt > lastReadAt) —
   a small gold spark beside the meta + a lit title. Clears the moment the row is opened. */
/* The separate gold unread spark is RETIRED. It existed because the dot was busy encoding the lane
   and could not say "not seen yet"; now that the dot says exactly that, the spark repeated it 200px
   away in the same row — two marks for one fact, which the one-voice rule forbids and which is worse
   than useless on a rail you are meant to scan. The brightened title stays: that is the thing you
   actually read, and it reinforces the dot rather than duplicating it. */
.ws-row.unread .ws-title { color: var(--ph-bright); }
/* compact right-edge meta: live elapsed while busy (bright / gold when it needs you), a dim "last worked" stamp when idle */
.ws-meta { flex: 0 0 auto; font-size: 11px; line-height: 1; color: var(--ph-dim); letter-spacing: .3px; font-variant-numeric: tabular-nums; }
.ws-meta:empty { display: none; }
.ws-row.busy .ws-meta { color: var(--ph-bright); }
.ws-row.attn .ws-meta { color: var(--gold); }

/* ---------- SESSION ROWS = INBOX (StationUI setting `sessionRow`, body.rows-inbox) ----------
   The SAME .ws-row markup, re-laid as a three-line card. The two extra spans (.ws-agent, .ws-receipt)
   are ALWAYS in the DOM and simply display:none in COMPACT, so switching the setting is a repaint —
   renderRail is never re-run, and rail focus, scroll position and an in-place rename all survive it.

   Three lines, not four: a preview of the last message is the line that clips to uselessness in a rail
   that is 232px and does not widen (--crew-w), so what the row gains instead is provenance —
   WHO ran it, WHAT it is, and WHICH model over HOW MANY messages. */
.ws-agent, .ws-receipt { display: none; }
/* SCOPED TO #workstreams ON PURPOSE. The PROJECTS rail reuses this exact row vocabulary in its own
   <ul id="projects"> (see the .ws-row.proj-row block below), so an unscoped `body.rows-inbox .ws-row`
   re-grids the project list too — a rail the setting says nothing about. Every rule here names the
   sessions list; .ws-search-hit is a separate class and is untouched either way. */
body.rows-inbox #workstreams .ws-row {
  display: grid; align-items: center;
  /* the top line gets REAL columns rather than stacking the pin and the unread spark into the agent
     cell — three end-justified children in one cell overlap each other and clip the agent name */
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  grid-template-areas:
    "dot   agent pin   meta"
    "title title title title"
    "recpt recpt recpt recpt";
  gap: 1px 8px; padding: 6px 8px 7px;
}
body.rows-inbox #workstreams .ws-row > .ws-dot { grid-area: dot; }
body.rows-inbox #workstreams .ws-agent {
  display: block; grid-area: agent; min-width: 0;
  font-size: 12px; letter-spacing: 1.6px; color: var(--ph-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.rows-inbox #workstreams .ws-row.sel .ws-agent,
body.rows-inbox #workstreams .ws-row.busy .ws-agent { color: var(--ph); }
body.rows-inbox #workstreams .ws-pin { grid-area: pin; }
body.rows-inbox #workstreams .ws-row > .ws-meta { grid-area: meta; }
body.rows-inbox #workstreams .ws-title { grid-area: title; font-size: 15px; line-height: 1.1; }
/* RENAME-IN-PLACE swaps the title span for an input (app.js replaceWith), and an element with no
   grid-area gets AUTO-PLACED — in a 3-row template that lands it outside the title row entirely
   (measured: 112x48 in a 175px row, and the row grew 76->83px). It inherits the title's cell so
   editing a name looks like editing a name. Its `flex: 1` is inert here; the cell owns the width. */
body.rows-inbox #workstreams .ws-rename { grid-area: title; }
body.rows-inbox #workstreams .ws-receipt {
  display: block; grid-area: recpt; min-width: 0;
  font-size: 11px; line-height: 1.1; color: var(--ph-dim);
  letter-spacing: .8px; font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* the kebab overlays the meta corner on hover exactly as it does in COMPACT (see the rule below that
   swaps .ws-meta for .ws-kebab) — same cell, so the row height never changes when the mouse enters */
body.rows-inbox #workstreams .ws-kebab { grid-area: meta; }

/* ---- SESSIONS ↔ PROJECTS view toggle (NS-5c): a segmented control in the rail head, ink-on-phosphor ---- */
.ws-view { display: inline-flex; align-items: center; gap: 0; }
.ws-tab {
  font-family: inherit; font-size: 12px; letter-spacing: 1px; color: var(--ink);
  background: transparent; border: 1px solid rgba(0,0,0,.45); cursor: pointer; padding: 1px 8px;
  opacity: .55;
}
.ws-tab:first-child { border-radius: 3px 0 0 3px; border-right-width: 0; }
.ws-tab:last-child { border-radius: 0 3px 3px 0; }
.ws-tab:hover { background: rgba(0,0,0,.2); opacity: .8; }
/* SELECTED = A RECESSED WELL, not just darker paint. Everything else in the panel chrome says
   "pressed" with an inset shadow (#crew-sum's counter strip, #ws-wrap's housing); the segmented
   control was the one place selection was flat fill, so SESSIONS read as a third chip rather than
   the one that is down. The inset also lets the ON tab keep the same ink as the OFF ones without
   the opacity ramp doing all the work. */
.ws-tab.on {
  background: rgba(0,0,0,.34); opacity: 1;
  box-shadow: inset 0 1px 3px rgba(0,0,0,.5), inset 0 -1px 0 rgba(var(--ph-bright-rgb), .12);
}
/* + ADD swaps in for + NEW when the rail shows PROJECTS — it is the same chip in the same slot,
   so it gets the same metrics. Skipping it would make the head change shape on a view switch. */
#ws-addproject {
  font-family: inherit; font-size: 12px; letter-spacing: 1px; color: var(--ink);
  background: transparent; border: 1px solid rgba(0,0,0,.45); cursor: pointer;
  padding: 1px 6px; border-radius: 3px;
  box-shadow: inset 0 1px 0 rgba(var(--ph-bright-rgb), .22);
}
#ws-addproject:hover { background: rgba(0,0,0,.2); }
#ws-addproject[hidden] { display: none; }

/* the PROJECTS list reuses the SESSIONS rail row vocabulary (.ws-row / .ws-dot / .ws-title / .ws-meta /
   .ws-kebab) but each project is a TWO-LINE card: name line + the honest path line (the path used to hide
   in a tooltip, which read as a bare, cheap row). Sessions preview as an indented phosphor thread. */
/* same slack rule as #workstreams — the PROJECTS tab shares the rail's lower half */
#projects {
  list-style: none; margin: 0; padding: 6px 0 0; overflow-y: auto;
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 0;
}
#projects[hidden] { display: none; }
.ws-row.proj-row { align-items: flex-start; padding: 6px 8px 7px; margin-top: 5px; }
#projects > .proj-row:first-child { margin-top: 0; }
.proj-row > .ws-dot { margin-top: 5px; }
.proj-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.proj-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* line 2 — the full folder path, always visible (LTR ellipsis: the parent dirs are the distinctive part;
   the tail is already the row's name). A dim "N sessions" count sits at its right edge. */
.proj-path {
  flex: 1 1 auto; min-width: 0; font-size: 10px; letter-spacing: .3px; color: var(--ph-dim); opacity: .8;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.proj-sess-n { flex: 0 0 auto; font-size: 10px; letter-spacing: .5px; color: var(--ph-dim); }
/* the dot doubles as a git badge: a blessed git repo glows phosphor; a plain blessed folder is a soft dim dot */
.ws-dot.proj-git { background: var(--ph-bright); box-shadow: 0 0 6px var(--ph-glow); }
.ws-dot.proj-plain { background: var(--ph-dim); }
/* REVOKED (blessed:false) — truthful telemetry: the remembered root is shown, not hidden, and reads as un-trusted */
.ws-row.proj-revoked { opacity: .5; }
.ws-row.proj-revoked .ws-dot { background: var(--ph-dim); box-shadow: none; }
.ws-row.proj-revoked:hover { opacity: .85; }
.proj-tag {
  flex: 0 0 auto; font-size: 10px; letter-spacing: 1px; color: var(--warn);
  border: 1px solid currentColor; border-radius: 2px; padding: 0 3px; line-height: 1.3;
}
.proj-git-badge {
  flex: 0 0 auto; font-size: 9px; letter-spacing: 1px; text-transform: uppercase; line-height: 1.5;
  color: var(--ph); border: 1px solid var(--ph-faint); border-radius: 2px; padding: 0 4px;
  background: rgba(0,0,0,.25);
}
/* ref-parity drill-in: breadcrumb back-row + entered-project header + full session rows */
.proj-crumb { padding: 0 0 3px; }
.proj-back {
  font-family: inherit; font-size: 11px; letter-spacing: 1px; color: var(--ph-dim);
  background: transparent; border: 0; cursor: pointer; padding: 2px 7px; border-radius: 3px;
}
.proj-back:hover { color: var(--ph); background: var(--ph-faint); }
.ws-row.proj-head {
  margin-top: 0; cursor: default; padding: 7px 8px 8px;
  background: linear-gradient(180deg, rgba(0,0,0,.4), rgba(0,0,0,.22)); border-left: 2px solid var(--ph);
}
.ws-row.proj-head .ws-title { color: var(--ph-bright); letter-spacing: .5px; text-shadow: 0 0 6px var(--ph-glow2); }
/* full session rows inside an entered project: indented, otherwise the sessions rail's own vocabulary.
   These rows carry no ⋯ button, so the shared hover rule (meta hides to yield the kebab's slot) would
   just blank the time stamp — keep it visible here. */
.ws-row.proj-sess-full { margin-left: 14px; margin-top: 4px; }
.ws-row.proj-sess-full:hover .ws-meta, .ws-row.proj-sess-full:focus-within .ws-meta { display: block; }
/* sessions attached to a project — an indented thread under the card (the REAL w.projectRoot link);
   a faint left rule replaces the old '└' glyph. */
.proj-sess {
  display: flex; align-items: center; gap: 6px; margin-left: 16px; padding: 2px 8px 2px 10px;
  cursor: pointer; font-size: 12px; color: var(--ph-dim); border-left: 1px solid var(--ph-faint);
}
.proj-sess:hover { color: var(--ph); background: var(--ph-faint); border-left-color: var(--ph-dim); }
.proj-sess.on { color: var(--ph); border-left-color: var(--ph); }
.proj-sess .ws-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: inherit; }
.proj-sess .ws-meta { flex: 0 0 auto; font-size: 10px; }
/* the ">3 previews" overflow row — entering the project shows the rest */
.proj-sess.proj-more { font-size: 10px; letter-spacing: 1px; }
/* the empty states + the inline "add a folder" editor card */
.proj-empty { padding: 10px 8px; }   /* dim grade + size via the grouped empty rule (motion.css) */
.proj-empty-hero { text-align: center; padding: 18px 10px 16px; }
.proj-empty-hero .pe-glyph { display: block; font-size: 20px; opacity: .55; margin-bottom: 6px; text-shadow: 0 0 8px var(--ph-glow2); }
.proj-empty-hero b { display: block; font-weight: normal; color: var(--ph); font-size: 13px; letter-spacing: 1px; margin-bottom: 3px; }
.proj-empty-hero span { display: block; font-size: 11px; line-height: 1.45; }
/* gold accent = actionable (the settings vocabulary): blessing a folder is a trust decision.
   Layout: label + ✕ head row, full-width path input, then BROWSE… ↔ ADD button row — never a
   single crammed line, and no emoji glyphs (CRT chrome is text-only). */
.proj-add {
  display: flex; flex-direction: column; gap: 6px; padding: 8px 9px 9px; margin-bottom: 6px;
  background: rgba(var(--gold-rgb), .05); border-left: 2px solid var(--gold); border-radius: 0 3px 3px 0;
}
.proj-add-head { display: flex; align-items: center; justify-content: space-between; }
.proj-add-lbl { font-size: 10px; letter-spacing: 1.5px; color: var(--gold); opacity: .9; }
.proj-add-row { display: flex; gap: 8px; align-items: center; justify-content: space-between; }
.proj-add input {
  width: 100%; box-sizing: border-box; min-width: 0; font-family: inherit; font-size: 13px; color: var(--ph-bright);
  background: linear-gradient(180deg, #000, color-mix(in srgb, var(--ph) 3%, #000));
  border: 1px solid var(--ph-dim); outline: none; padding: 5px 8px; border-radius: 3px;
  box-shadow: inset 0 2px 6px rgba(0,0,0,.7);
}
.proj-add input:focus { border-color: var(--ph); box-shadow: inset 0 2px 6px rgba(0,0,0,.7), 0 0 10px var(--ph-glow2); }
.proj-add input::placeholder { color: var(--ph-dim); opacity: .7; }
.proj-add button {
  font-family: inherit; font-size: 11px; letter-spacing: 1px; color: var(--ph);
  background: transparent; border: 1px solid var(--ph-dim); cursor: pointer; padding: 3px 10px; border-radius: 3px;
}
.proj-add button:hover { border-color: var(--ph); color: var(--ph-bright); }
/* ADD = the primary action, in the card's own gold voice */
.proj-add button.proj-add-go {
  color: var(--gold); border-color: rgba(var(--gold-rgb), .55); background: rgba(var(--gold-rgb), .06);
}
.proj-add button.proj-add-go:hover { color: var(--gold); border-color: var(--gold); box-shadow: 0 0 8px rgba(var(--gold-rgb), .35); }
/* the head ✕ is a quiet bare glyph, not a boxed button */
.proj-add button.proj-add-cancel { border: 0; padding: 0 2px; font-size: 11px; color: var(--ph-dim); }
.proj-add button.proj-add-cancel:hover { color: var(--ph-bright); }
.proj-discover-results {
  max-height: 180px; overflow: auto; display: flex; flex-direction: column; gap: 4px;
  padding: 4px; border: 1px solid var(--ph-faint); background: rgba(0,0,0,.2);
}
.proj-discover-results[hidden] { display: none; }
.proj-add button.proj-discover-pick {
  display: grid; grid-template-columns: 1fr auto; gap: 1px 8px; width: 100%; text-align: left;
  padding: 5px 7px; border-color: var(--ph-faint); text-transform: none; letter-spacing: 0;
}
.proj-discover-pick b { color: var(--ph-bright); font-weight: normal; }
.proj-discover-pick span { grid-column: 1 / -1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ph-dim); font-size: 10px; }
.proj-discover-pick em { grid-column: 2; grid-row: 1; color: var(--gold); font-size: 9px; font-style: normal; text-transform: uppercase; letter-spacing: 1px; }
.proj-add-hint { font-size: 11px; color: var(--ph-dim); line-height: 1.3; }
.proj-add-hint.err { color: var(--warn); }

/* ---- session row actions: pin marker · hover ⋯ · archived state · in-place rename ---- */
.ws-head-acts { display: inline-flex; align-items: center; gap: 6px; }
/* archived reveal — a QUIET footer line at the end of the sessions list, never a header button
   (the rail head stays SESSIONS/PROJECTS + NEW, nothing else). Shown only when ≥1 archived exists. */
.ws-arch-row {
  padding: 3px 8px 4px; cursor: pointer; font-size: 11px; letter-spacing: 1px;
  color: var(--ph-dim); background: transparent; border-left: 2px solid transparent;
}
.ws-arch-row:hover { color: var(--ph); background: var(--ph-faint); }
.ws-arch-row.on { color: var(--ph); }
/* pinned rows sort to the top AND carry a small gold star so the "kept" state is legible at a glance */
.ws-pin { flex: 0 0 auto; font-size: 10px; line-height: 1; color: var(--gold); text-shadow: 0 0 6px rgba(var(--gold-rgb),.5); }
/* the ⋯ button is the discoverable twin of right-click: hidden until the row is hovered, where it takes the meta slot */
.ws-kebab {
  display: none; flex: 0 0 auto; font-family: inherit; font-size: 15px; line-height: 1; letter-spacing: 1px;
  color: var(--ph-dim); background: transparent; border: 0; cursor: pointer; padding: 0 2px;
}
.ws-row:hover .ws-meta, .ws-row:focus-within .ws-meta { display: none; }
.ws-row:hover .ws-kebab, .ws-row:focus-within .ws-kebab { display: inline-block; }
.ws-kebab:hover { color: var(--ph-bright); text-shadow: 0 0 6px var(--ph-glow); }
.ws-row.renaming .ws-kebab, .ws-row.renaming .ws-meta { display: none; }
/* archived (put-away) streams show dimmed only while the ARCHIVED toggle is on */
.ws-row.archived { opacity: .5; }
.ws-row.archived .ws-title { font-style: italic; }
.ws-row.archived:hover { opacity: .85; }
/* rename-in-place: the title becomes a compact phosphor input inside the same row.

   SPECIFICITY, and it matters: app.js builds this with `input.type = 'text'`, so the row's editor
   matches the input FOUNDATION at the top of this file (`input[type=text], …`) — an attribute
   selector at (0,1,1). A bare `.ws-rename` class is (0,1,0) and LOSES, so the compact metrics
   declared here were dead the day that foundation landed: the editor rendered at the foundation's
   19px / 8px 11px / width:100% and shoved the rail row from 35px to 61px mid-edit (and 76→102 in
   INBOX). Scoping to #workstreams makes this (1,1,0) so the intended metrics actually apply.
   Fixed HERE rather than in the foundation on purpose — the foundation and the control floor below
   it are shared safety nets, and this is one surface's skin, which is where material belongs. */
#workstreams .ws-rename {
  flex: 1; min-width: 0; width: auto; font-family: inherit; font-size: 14px; color: var(--ph-bright);
  background: var(--panel2); border: 1px solid var(--ph); outline: none; padding: 1px 4px; border-radius: 3px;
  box-shadow: none; text-shadow: none;
}

/* ---- the floating actions menu (phosphor chrome, never the native context menu) ---- */
.ws-menu {
  position: fixed; z-index: 140; min-width: 170px; padding: 4px; border-radius: 6px;
  background: var(--panel); border: 1px solid var(--ph);
  box-shadow: 0 0 0 1px var(--ph-dim), 0 8px 24px rgba(0,0,0,.7), 0 0 18px var(--ph-glow2);
  display: flex; flex-direction: column; gap: 1px; animation: wsMenuIn .09s ease-out;
}
@keyframes wsMenuIn { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
.ws-menu-item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left; border-radius: 4px;
  font-family: inherit; font-size: 13px; letter-spacing: .3px; color: var(--ph);
  background: transparent; border: 0; cursor: pointer; padding: 7px 10px;
}
.ws-menu-item:hover { background: var(--ph-faint); color: var(--ph-bright); }
.ws-menu-glyph { flex: 0 0 auto; width: 14px; text-align: center; font-size: 12px; color: var(--ph-dim); }
.ws-menu-item:hover .ws-menu-glyph { color: var(--ph-bright); }
.ws-menu-item.danger { color: var(--bad); }
.ws-menu-item.danger .ws-menu-glyph { color: var(--bad); }
.ws-menu-item.danger:hover { background: rgba(255,92,77,.14); color: var(--bad); }
/* danger-armed skin is the canonical rule above; only the glyph tint is a per-button extra */
.ws-menu-item.danger.armed .ws-menu-glyph { color: var(--ink); }
.ws-menu-sep { height: 1px; margin: 3px 6px; background: var(--ph-dim); opacity: .5; }

#chat-panel h3 .h3-right { display: inline-flex; align-items: baseline; gap: 8px; min-width: 0; flex: 0 1 auto; overflow: hidden; }
#chat-panel h3 #chat-status { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
#chat-status.status-thinking { color: var(--ph-bright); text-shadow: 0 0 7px var(--ph-glow2); }
/* pre-confirmation: the sidecar hasn't acknowledged the run yet — dimmer than thinking (an honest "in transit") */
#chat-status.status-connecting { color: var(--ph); opacity: .8; }
#chat-status.status-working, #chat-status.status-approval { color: var(--gold); text-shadow: 0 0 7px rgba(var(--gold-rgb),.4); }
#chat-status.status-stopping { color: var(--bad); text-shadow: 0 0 7px rgba(255,74,61,.35); }
/* F2: bridge provably dead (World.linkState E1 — same predicate as the topbar LINK DOWN / canvas marker):
   the idle label can't honestly read 'online', so it reads 'station unreachable' in the fault palette. */
#chat-status.status-down { color: #ff6a4c; text-shadow: 0 0 7px rgba(255,106,76,.35); }

/* COMMS PROCESSING TIMER — a small engraved readout in the amber header that counts a turn's REAL
   wall-clock (thinking → working → done). Monospace with a fixed min-width so the ticking seconds
   never jog the layout, and a pulsing dot that stands in for the harness "spinner". Stays empty
   between turns (:empty → display:none), so it simply appears when a run starts and vanishes when it ends. */
.chat-elapsed {
  display: none; align-items: center; gap: 5px; flex: 0 0 auto;
  font-family: 'VT323', 'Courier New', monospace; font-size: 15px; line-height: 1; letter-spacing: .5px;
  color: var(--ink); padding: 1px 7px 0; border-radius: 4px;
  background: rgba(0,0,0,.16); box-shadow: inset 0 1px 2px rgba(0,0,0,.32);
  font-variant-numeric: tabular-nums;
}
.chat-elapsed:not(:empty) { display: inline-flex; }
.chat-elapsed > .ce-num { min-width: 30px; text-align: right; }
.chat-elapsed::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--ink);
  animation: chat-elapsed-pulse 1.1s ease-in-out infinite;
}
@keyframes chat-elapsed-pulse { 0%, 100% { opacity: .3; transform: scale(.82); } 50% { opacity: 1; transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .chat-elapsed::before { animation: none; opacity: .8; } }

/* ============ AGENT DOSSIER — redesigned character sheet ============ */

/* hero header: bigger portrait + info block side by side */
.ag-hero {
  display: flex; gap: 14px; align-items: flex-start;
  padding-bottom: 10px; margin-bottom: 2px;
  border-bottom: 1px solid var(--ph-faint);
}
/* 100px, matching PORTRAIT_W in stationui.js — the well is sized so the largest shipped character clears
   an integer ×2 nearest-neighbour upscale (see the PORTRAIT_W comment; dev/skin-bounds.mjs measures it). */
.ag-portrait-wrap { flex: 0 0 100px; }
/* recessed portrait WELL (Phase-2 dossier hero): a dark inset floor the body glows out
   of, corner ticks, and a slow scan-sweep. Cosmetic overlays are pointer-events:none. */
.ag-portrait-well {
  position: relative; width: 100px; height: 108px; border-radius: var(--r-sm); overflow: hidden;
  background: linear-gradient(180deg, #0a0602, #050301);
  box-shadow: var(--well), 0 0 11px var(--ph-glow2);
}
.ag-portrait-well canvas { image-rendering: pixelated; display: block; position: relative; z-index: 1; }
.ag-ptick { position: absolute; width: 8px; height: 8px; border: 1px solid var(--ph); opacity: 0.65; z-index: 3; pointer-events: none; }
.ag-ptick.a { top: 3px; left: 3px; border-right: 0; border-bottom: 0; }
.ag-ptick.b { top: 3px; right: 3px; border-left: 0; border-bottom: 0; }
.ag-ptick.c { bottom: 3px; left: 3px; border-right: 0; border-top: 0; }
.ag-ptick.d { bottom: 3px; right: 3px; border-left: 0; border-top: 0; }
.ag-psweep {
  position: absolute; left: 0; right: 0; height: 20px; z-index: 2; pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(255, 205, 130, 0.16), transparent);
  animation: ag-psweep 2.8s linear infinite;
}
@keyframes ag-psweep { 0% { top: -20px; } 100% { top: 112px; } }
.ag-info { flex: 1; min-width: 0; }

/* live status line with pulsing dot */
.ag-role-line {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--ph); letter-spacing: 1.5px;
  margin: 3px 0 6px;
}
.ag-sdot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ph-dim); flex: none;
}
.ag-sdot.on { background: var(--ok); box-shadow: 0 0 5px rgba(123,200,138,.55); }
.ag-sdot.working { background: var(--gold); box-shadow: 0 0 7px rgba(var(--gold-rgb),.7); animation: sdot-pulse 1.1s infinite; }
.ag-sdot.thinking { background: var(--ph-bright); box-shadow: 0 0 6px var(--ph-glow); animation: sdot-pulse .7s infinite; }
/* E2: link down → OFFLINE dot, red-phosphor breathing, matching the canvas LINK DOWN marker */
.ag-sdot.down { background: var(--link-down); box-shadow: 0 0 6px rgba(255,80,60,.75); animation: sdot-pulse 2.2s infinite; }
@keyframes sdot-pulse { 0%,100%{ opacity:1; } 50%{ opacity:.3; } }

/* 3-cell RPG stat grid */
.stat-grid {
  display: grid; grid-template-columns: 1fr 1fr 1fr;
  gap: 6px; margin: 10px 0 8px;
}
/* inside the dossier hero the wells share the info column with the name/status/model tags, so they sit tighter
   and shed the bottom margin the hero's own padding already provides. */
.ag-info > .stat-grid { margin: 9px 0 0; }
.ag-info > .stat-grid .stat-cell { padding: var(--s-2) var(--s-2); }
.stat-cell {
  background: linear-gradient(180deg, #0a0602, #050301); box-shadow: var(--well);
  padding: var(--s-3) var(--s-2); text-align: center; border-radius: var(--r-sm);
}
.stat-val { font-size: var(--fs-4); color: var(--ph-bright); letter-spacing: 1px; line-height: 1; text-shadow: 0 0 12px var(--ph-glow2); }
.stat-val.pos { color: var(--gold); }
.stat-lbl { font-size: 10px; color: var(--ph-dim); letter-spacing: 2px; margin-top: 4px; }

/* mission block */
.ag-mission {
  border-left: 2px solid var(--ph); padding: 6px 10px;
  margin: 0 0 8px; background: rgba(0,0,0,.22);
}
.ag-mission-lbl { font-size: 10px; color: var(--ph-dim); letter-spacing: 2px; margin-bottom: 4px; }
.ag-mission-text { font-size: 13px; color: var(--text); line-height: 1.5; user-select: text; }
.ag-mission-cta { font-size: 12px; color: var(--ph-dim); font-style: italic; line-height: 1.45; }

/* BRIEF section rules — the landing tab now carries three blocks (stats+purpose, SET UP AS, CAN DO), so it
   needs the same .sec divider vocabulary the other panes use. Extra top margin only; .sec owns the rest. */
.ag-brief-sec { margin-top: 16px; }

/* SET UP AS — the five runtime decisions, shown as VALUES you can read, each a door into the CONFIG card that
   owns it. A matte well per row (no gloss, no hover lift): this is a readout first and a link second. */
.ag-setup { display: grid; gap: 3px; margin: 2px 0 4px; }
.ag-setup-row {
  display: grid; grid-template-columns: 84px 1fr 12px; align-items: baseline; gap: 10px;
  width: 100%; text-align: left; font: inherit; cursor: pointer;
  background: rgba(0,0,0,.22); border: 1px solid transparent; border-left: 2px solid var(--ph-dim);
  padding: 5px 8px; border-radius: var(--r-sm);
}
.ag-setup-row:hover, .ag-setup-row:focus-visible {
  background: var(--ph-faint); border-left-color: var(--ph-bright); color: var(--ph-bright);
}
.ag-setup-row:focus-visible { outline: 2px solid var(--ph-bright); outline-offset: 1px; }
.ag-setup-lbl { font-size: 10px; letter-spacing: 2px; color: var(--ph-dim); }
.ag-setup-val { font-size: 13px; color: var(--ph-bright); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ag-setup-val.dim { color: var(--ph-dim); }
.ag-setup-go { font-size: 13px; color: var(--ph-dim); text-align: right; }
.ag-setup-row:hover .ag-setup-go { color: var(--ph-bright); }

/* footer row */
.ag-foot-row { font-size: 11px; color: var(--ph-dim); letter-spacing: .4px; margin-top: 4px; }
.ag-foot-row b { color: var(--ph); font-weight: normal; }

/* COMMANDER CONTROLS — dossier BRIEF: change skin + delete agent */
.ag-command { margin: 10px 0 8px; display: flex; flex-direction: column; gap: 12px; }
.ag-cmd-sec { border-top: 1px solid var(--hair, rgba(255,255,255,.08)); padding-top: 8px; }
.ag-cmd-lbl { font-size: 10px; color: var(--ph-dim); letter-spacing: 2px; margin-bottom: 6px; }
/* SKIN: a scrolling well of thumbs on the left, the LIVE preview stage pinned right — a 44px still of a
   chunky sprite is unidentifiable, so the stage plays the picked/hovered skin's real walk cycle. Same
   shape + material as the Recruitment Bay's SUMMON stage (.mkt-skin-stage in marketplace.css), sized for
   the narrower dossier pane; the well's height is matched to the stage so neither side reads lopsided. */
.ag-skin-section { display: flex; gap: 12px; align-items: flex-start; }
.ag-skin-row { max-height: 156px; }   /* reuse .skin-picker/.skin-thumb; a shorter well in the narrower dossier pane */
.ag-skin-stage { flex: 0 0 124px; margin: 0; display: flex; flex-direction: column; align-items: center; gap: 5px; }
.ag-skin-stage-frame { width: 124px; height: 124px; box-sizing: border-box; overflow: hidden;
  background-image: radial-gradient(ellipse 64% 44% at 50% 86%, var(--ph-glow2), transparent 72%);
  background-color: var(--panel2); border: 1px solid var(--ph-dim); border-radius: 6px;
  box-shadow: inset 0 1px 0 rgba(var(--ph-rgb),.08), 0 1px 0 rgba(0,0,0,.35);
  display: flex; align-items: flex-end; justify-content: center; }
/* smooth-downscale the ~92px sprite master (repo law: never nearest-neighbour crush a large sprite) */
.ag-skin-stage-frame img { width: 88%; height: 88%; object-fit: contain; object-position: center bottom;
  image-rendering: auto; }
.ag-skin-stage-name { color: var(--ph-bright); letter-spacing: 1px; font-size: 12px; text-align: center;
  line-height: 1.25; text-shadow: 0 0 6px var(--ph-glow2); }
.ag-stage-lbl { color: var(--ph-dim); letter-spacing: 1.5px; font-size: 10px; }
/* narrow dossier pane: drop the stage below the wells so neither crushes */
@media (max-width: 720px) {
  .ag-skin-section { flex-direction: column; }
  .ag-skin-stage { flex: 0 0 auto; align-self: center; }
}
.ag-cmd-danger .ag-cmd-lbl { color: var(--bad); }
.ag-del-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ag-del { border-color: var(--bad) !important; color: var(--bad) !important; }
.ag-del:hover:not(:disabled) { background: rgba(var(--bad-rgb),.12) !important; }
.ag-del:disabled { opacity: .4; cursor: not-allowed; border-color: var(--ph-dim) !important; color: var(--ph-dim) !important; }
/* danger-armed skin is the canonical rule above; only the danger glow is a per-button extra */
.ag-del.armed { box-shadow: 0 0 12px rgba(var(--bad-rgb), .5); }
.ag-del-why { font-size: 11px; color: var(--ph-dim); font-style: italic; }

/* ============ PREMIUM GROWTH DOSSIER — the GROWTH tab ============
   Tactical-ops grid discipline, refined with hairline rules + engraved labels and breathing room.
   Honest gauges (confidence reads "—" while calibrating). Theme-aware: every colour rides the
   --ph / --gold / --ink vars so the green/blue/white themes hold; all VT323 (one house typeface). */
.gx { margin: 4px 0 2px; }
.gx-mono { font-family: inherit; }

/* justify-content:flex-end — the dossier UX pass stripped the left half of this header (a kicker + the agent
   name, both already carried by the window title and the roster rail), leaving only the LEVEL / TRACED chip. */
.gx-head { display:flex; align-items:flex-start; justify-content:flex-end;
  border-bottom:1px solid var(--ph-glow2); padding-bottom:11px; margin-bottom:15px; }
.gx-kicker { font-family:inherit; font-size:10px; letter-spacing:.16em; color:var(--ph-dim); opacity:.85; margin-bottom:5px; }
.gx-name { font-size:26px; line-height:.9; color:var(--ph-bright); letter-spacing:1px; text-shadow:0 0 12px var(--ph-glow2); }
.gx-clear { display:inline-flex; align-items:baseline; gap:7px; background:var(--ph-faint);
  border:1px solid var(--gold); border-radius:2px; padding:4px 10px; }
.gx-clear .k { font-family:inherit; font-size:11px; letter-spacing:.14em; color:var(--gold); }
.gx-clear .v { font-family:inherit; font-size:23px; line-height:1; color:var(--gold); text-shadow:0 0 10px rgba(var(--gold-rgb),.45); }

.gx-2 { display:grid; grid-template-columns:1fr 1fr; gap:18px; margin-bottom:16px; }

.gx-sec { display:flex; align-items:center; gap:7px; height:18px; border-bottom:1px solid var(--ph-glow2); margin-bottom:11px; }
.gx-ref { font-family:inherit; font-size:11px; letter-spacing:.1em; color:var(--ink); background:var(--ph); padding:1px 6px; border-radius:2px; }
.gx-ref.gold { background:var(--gold); }
.gx-title { font-family:inherit; font-size:12px; letter-spacing:.16em; color:var(--ph); text-transform:uppercase; }
.gx-tag { margin-left:auto; font-family:inherit; font-size:10px; letter-spacing:.1em; color:var(--ph-dim); }

.gx-lbl { font-family:inherit; font-size:10px; letter-spacing:.12em; color:var(--ph-dim); text-transform:uppercase; }
.gx-val { font-family:inherit; color:var(--ph-bright); }
.gx-row { display:flex; align-items:baseline; justify-content:space-between; }
.gx-dim { color:var(--ph-dim); }

.gx-trk { position:relative; height:13px; background:var(--ph-faint); border:1px solid var(--ph-glow2); border-radius:2px; overflow:hidden; box-shadow:inset 0 1px 2px rgba(0,0,0,.55); }
.gx-fill { position:absolute; top:0; left:0; bottom:0; background:var(--ph); opacity:.9; box-shadow:0 0 8px var(--ph-glow); }
.gx-gfill { position:absolute; top:0; left:0; bottom:0; background:var(--gold); box-shadow:0 0 8px rgba(var(--gold-rgb),.4); }
.gx-mark { position:absolute; top:-3px; bottom:-3px; width:2px; background:var(--ph-bright); box-shadow:0 0 0 1px rgba(0,0,0,.45); }

.gx-confnum { font-family:inherit; font-size:32px; line-height:.8; color:var(--ph-bright); text-shadow:0 0 12px var(--ph-glow2); }
.gx-confnum.cal { color:var(--ph-dim); }
.gx-band { font-family:inherit; font-size:11px; letter-spacing:.14em; color:var(--ink); background:var(--ph); padding:2px 7px; border-radius:2px; align-self:center; }
.gx-band.cal { color:var(--ph-dim); background:transparent; border:1px solid var(--ph-glow2); }
.gx-gauge { position:relative; }
.gx-zones { display:grid; grid-template-columns:45fr 20fr 20fr 15fr; height:12px; background:var(--ph-faint); border:1px solid var(--ph-glow2); border-radius:2px; overflow:hidden; box-shadow:inset 0 1px 2px rgba(0,0,0,.55); }
.gx-zones > i { display:block; background:var(--ph); }
.gx-zones > i:nth-child(1){ opacity:.16; }
.gx-zones > i:nth-child(2){ opacity:.34; border-left:1px solid var(--bg); }
.gx-zones > i:nth-child(3){ opacity:.58; border-left:1px solid var(--bg); }
.gx-zones > i:nth-child(4){ opacity:.95; border-left:1px solid var(--bg); }
.gx-marknum { position:absolute; top:-14px; transform:translateX(-50%); font-family:inherit; font-size:10px; color:var(--ph-bright); }
.gx-zlabels { display:grid; grid-template-columns:45fr 20fr 20fr 15fr; font-family:inherit; font-size:10.5px; letter-spacing:.03em; color:var(--ph-dim); text-align:center; margin-top:4px; }
.gx-zlabels .hot { color:var(--ph); }

.gx-well { display:flex; align-items:center; justify-content:space-between; border:1px solid var(--ph-glow2); border-radius:2px; padding:8px 11px; margin-top:11px; background:rgba(0,0,0,.2); }
.gx-well.gold { border-color:var(--gold); background:var(--ph-faint); }
.gx-well .v { font-family:inherit; font-size:19px; color:var(--ph-bright); }
.gx-well.gold .v { color:var(--gold); }

.gx-trohead { display:flex; align-items:baseline; justify-content:space-between; margin:18px 0 9px; }
.gx-tros { display:grid; grid-template-columns:repeat(4,1fr); gap:7px; }
.gx-tro { border:1px solid var(--ph-glow2); border-radius:2px; padding:8px 9px; background:rgba(0,0,0,.18); }
.gx-tro.on { border-color:var(--gold); background:var(--ph-faint); }
.gx-tro .gl { font-size:13px; line-height:1; }
.gx-tro.on .gl { color:var(--gold); text-shadow:0 0 7px rgba(var(--gold-rgb),.55); }
.gx-tro.off .gl { color:var(--ph-dim); opacity:.5; }
.gx-tro .nm { font-family:inherit; font-size:11px; letter-spacing:.03em; margin-top:5px; }
.gx-tro.on .nm { color:var(--gold); }
.gx-tro.off .nm { color:var(--ph-dim); opacity:.75; }
.gx-tro .sub { font-family:inherit; font-size:10px; letter-spacing:.04em; margin-top:3px; color:var(--ph-dim); opacity:.7; }

/* ---- THE GOAL TRACK (2026-08-13) ----------------------------------------------------------------
   The active goal drawn as a path: filled nodes behind you, the node you are on, the steps ahead. It is
   the panel's hero band, so it is the one place in the log that carries a lit rail and a real meter.
   NOT a battle pass in the part that matters — nothing is locked, so there is no padlock, no tier
   number, and no unlock language; an upcoming node is simply not yet, and it says so. */
.q-track { border: 1px solid var(--ph-dim); border-left: 2px solid var(--gold); border-radius: var(--r-sm);
  background: linear-gradient(180deg, rgba(var(--gold-rgb), .05), rgba(0,0,0,.34) 62%);
  box-shadow: var(--raise); padding: 12px 14px 13px; margin-bottom: 13px; }
.q-track-head { display: flex; align-items: baseline; gap: 10px; }
.q-track-goal { flex: 1 1 auto; min-width: 0; font-size: 14px; line-height: 1.35; color: var(--gold);
  text-shadow: 0 0 8px rgba(var(--gold-rgb), .3); letter-spacing: .02em; }
.q-track-pct { flex: 0 0 auto; font-size: 15px; color: var(--gold); font-variant-numeric: tabular-nums;
  letter-spacing: .04em; }
.q-track-bar { height: 7px; margin-top: 9px; }
.q-track-bar .q-bar-fill { box-shadow: 0 0 10px rgba(var(--gold-rgb), .45); transition: width var(--t-med) var(--ease-out); }

/* THE NODE ROW — one node per milestone (the engine caps a goal at 3-5, so they always fit unstretched). */
.q-nodes { display: flex; align-items: flex-start; gap: 0; margin: 12px 0 0; padding: 0; list-style: none; }
.q-node { position: relative; flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
  align-items: center; text-align: center; padding: 0 4px; }
/* the connector: drawn from THIS node's dot to the next one's, lit only where the path is already walked */
.q-node::after { content: ''; position: absolute; top: 10px; left: 50%; width: 100%; height: 2px;
  background: var(--ph-glow2); z-index: 0; }
.q-node:last-child::after { display: none; }
.q-node-done::after { background: var(--gold); opacity: .55; }
.q-node-dot { position: relative; z-index: 1; width: 21px; height: 21px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1;
  border: 1px solid var(--ph-dim); background: var(--panel2, #0b0b0b); color: var(--ph-dim); }
.q-node-label { margin-top: 7px; font-size: 10px; line-height: 1.35; color: var(--ph-dim); opacity: .75;
  overflow-wrap: anywhere; }
/* BEHIND YOU — real, finished work; the only nodes that carry the gold fill. */
.q-node-done .q-node-dot { border-color: var(--gold); color: var(--ink, #000); background: var(--gold); }
.q-node-done .q-node-label { color: var(--ph); opacity: .6; }
/* THE NODE YOU ARE ON — the one actionable front. Lit, ringed, and named in words. */
.q-node-now .q-node-dot { border-color: var(--gold); color: var(--gold); background: rgba(var(--gold-rgb), .12);
  box-shadow: 0 0 0 3px rgba(var(--gold-rgb), .14), 0 0 12px rgba(var(--gold-rgb), .4); }
.q-node-now .q-node-label { color: var(--gold); opacity: 1; }
.q-node-tag { margin-top: 4px; font-size: 8px; letter-spacing: .14em; color: var(--gold); opacity: .9;
  border: 1px solid rgba(var(--gold-rgb), .45); border-radius: 2px; padding: 0 4px; white-space: nowrap; }
/* AHEAD — visible and numbered, never locked: the order is information, not a gate. */
.q-node-ahead .q-node-dot { border-style: dashed; }
/* the payoff line — what finishing the path advances the station to. Sits under the nodes, above the
   action, so the path reads: here is the route, here is where it leads, here is the next step. */
.q-track-payoff { margin-top: 13px; padding-top: 10px; border-top: 1px solid var(--ph-glow2);
  font-size: 11px; opacity: .9; }
.q-track-payoff b { color: var(--gold); letter-spacing: .06em; }
.q-track-payoff .dim { opacity: .55; }
.q-track-acts { display: flex; margin-top: 12px; }
.q-track-acts .q-arc-accept { flex: 1 1 auto; text-align: center; }
.q-track-running { display: block; margin-top: 10px; color: var(--gold); opacity: .8; }
/* the whole path walked — the meter reads full and the frame answers in kind */
.q-track-done { border-left-color: var(--gold); box-shadow: var(--raise), inset 0 0 26px rgba(var(--gold-rgb), .1); }
/* NO GOAL YET — a slim invitation that teaches what the track is, never an empty frame */
.q-track-empty { display: flex; align-items: center; gap: 12px; border-left-color: var(--ph-dim);
  background: linear-gradient(180deg, rgba(var(--ph-rgb), .04), rgba(0,0,0,.3) 62%); }
/* a path already finished: the band keeps the gold rail it earned rather than dimming to a cold prompt */
.q-track-reached-band { border-left-color: var(--gold);
  background: linear-gradient(180deg, rgba(var(--gold-rgb), .05), rgba(0,0,0,.32) 62%); }
.q-track-reached, .q-track-stage { color: var(--gold); }
.q-track-empty .sub { flex: 1 1 auto; font-size: 11px; line-height: 1.45; opacity: .75; }
.q-track-empty .q-go { flex: 0 0 auto; margin-left: 0; font-size: 10px; letter-spacing: .1em; padding: 5px 10px; }
.q-track-saved { border-left-color: var(--gold); }
.q-track-saved .q-track-goal { color: var(--gold); font-size: 12px; }
.q-milestones { margin: 14px 0 4px; border-top: 1px solid var(--ph-glow2); padding-top: 9px; }
.q-milestones > summary { display: flex; align-items: center; gap: 8px; cursor: pointer; color: var(--ph); font-size: 10px; letter-spacing: .14em; }
.q-milestones > summary .dim { margin-left: auto; letter-spacing: .03em; text-transform: none; }
.q-milestones[open] > summary { margin-bottom: 10px; }
.q-track-sec { margin-top: 0; }

/* ---- THE QUEST CARD (2026-08-13) ---------------------------------------------------------------
   The card's SKIN lives here; its GRID lives in motion.css (`.gx-quests .q-grid`), which loads after
   this file and outranks it — putting a second `grid-template-columns` here would just create a
   silent second painter for the same property. The card takes the station's existing card language —
   one top-left light, matte, `--raise`, no gloss — so it belongs to the same family as the catalog
   and deliverable cards instead of inventing a look for quests alone. */
.q-grid .q-card { display: flex; flex-direction: column; gap: 3px; padding: 11px 13px 12px;
  height: 100%; position: relative; overflow: hidden;
  border: 1px solid var(--ph-dim); border-radius: var(--r-sm); box-shadow: var(--raise);
  background: linear-gradient(180deg, rgba(var(--ph-rgb), .05), rgba(0,0,0,.36) 62%);
  transition: border-color var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out),
              translate var(--t-fast) var(--ease-out); }
/* THE KIND RAIL — a 2px spine down the card's leading edge, lit for the two kinds that are work you
   asked for and dim for the rest. It gives the grid a scannable rhythm at a glance, using the same
   left-rail vocabulary `.q-goalbar` already established for goal and proposal cards. */
.q-grid .q-card::before { content: ''; position: absolute; inset: 0 auto 0 0; width: 2px;
  background: var(--ph-glow2); }
.q-grid .q-card.off:has(.q-kind-ledger)::before,
.q-grid .q-card.off:has(.q-kind-work)::before { background: var(--gold); opacity: .75; }
.q-grid .q-card.on::before { background: var(--gold); }
/* a quest you can still act on lifts under the pointer; a done one is a trophy and stays put */
.q-grid .q-card.off:hover { border-color: var(--ph); translate: 0 -1px;
  box-shadow: var(--raise), inset 0 0 22px rgba(var(--ph-rgb), .07); }
.q-grid .q-card.on { border-color: var(--gold); }
/* the title earns real type: 11px at 130px wide was the single biggest legibility loss */
.q-grid .q-card .nm { font-size: 12.5px; line-height: 1.35; margin-top: 0; letter-spacing: .02em; }
.q-grid .q-card .sub { font-size: 11px; line-height: 1.45; opacity: .78; }
.q-grid .q-card .q-hd { align-items: flex-start; gap: 7px; }
.q-grid .q-card .gl { margin-top: 1px; }
/* THE KIND BADGE — names which of the six real sources a card came from, so a mixed list stops reading
   as one wall. Quiet by default; it labels, never shouts over the title. */
.q-kind { flex: 0 0 auto; margin-left: auto; align-self: flex-start; font-size: 8.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ph-dim); border: 1px solid var(--ph-glow2); border-radius: 2px;
  padding: 1px 5px; opacity: .85; white-space: nowrap; }
.q-kind-ledger, .q-kind-work { color: var(--gold); border-color: rgba(var(--gold-rgb), .5); }
.q-kind + .q-dismiss { margin-left: 4px; }
/* HANGING GLYPHS: the ✓ and ◆ lines lead with a glyph, so their text has to hang off it or the second
   wrapped line slides back under the marker and the column edge goes ragged. */
.q-grid .q-card .q-cw, .q-grid .q-card .q-reward { padding-left: 11px; text-indent: -11px; }
/* the reward, styled as what it is: the real thing this pays out in */
.q-grid .q-card .q-reward { color: var(--gold); opacity: .82; margin-top: 4px; font-size: 10.5px; }
/* the last text line before the action row breathes; the row itself is pushed to the card's floor so
   every primary action in a row shares one baseline (see the stretch note in motion.css) */
.q-grid .q-card .q-actions { margin-top: auto; padding-top: 9px; }
/* THE ACTION ROW — the primary action gets the foot of the card and full width. It used to be a 9px chip
   wedged between the title and the dismiss ✕; a control that STARTS a quest must not sit a few pixels from
   the control that dismisses it forever. */
.q-actions { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
.q-actions .q-go { margin-left: 0; flex: 1 1 auto; text-align: center; font-size: 10px; letter-spacing: .1em;
  padding: 5px 8px; border-color: var(--ph-dim); color: var(--ph); opacity: 1; }
.q-actions .q-go:hover { color: var(--gold); border-color: var(--gold);
  box-shadow: inset 0 0 12px rgba(var(--gold-rgb), .16); }
.q-actions .q-queue { margin-left: 0; flex: 0 0 auto; border-color: var(--ph-glow2); padding: 4px 7px; }
/* the lede sits under the OPEN heading now — quiet, one line, never competing with the cards */
.q-lede { margin: -2px 0 9px; font-size: 10.5px; opacity: .6; }
/* STEADY SHELL, the `.term.console.dossier`/`.dlv-win` reasoning: quest data changes (a fold, a poke, a
   completed step) change content height, and a never-moved window is CSS-centred — content-fit height
   would re-centre the whole window on every such change, sliding the card the Commander is reading out
   from under their eyes. Fixed height; the quest list scrolls inside instead.
   `--con-h` is declared on `.term.console`, which this window is NOT — restate the same formula here
   (an undefined var would make the height silently invalid → back to content-fit, the bug this exists
   to prevent). */
.term.quests-win { --con-h: min(var(--term-band, calc(88vh * var(--sn-unzoom, 1))), 760px); height: var(--con-h); }

/* ---- QUEST LOG rhythm: one vertical scale for the whole panel ----
   The window is now wide enough that inconsistent gutters read as sloppiness rather than density. These
   set ONE spacing scale (18px between movements, 11px inside a card) and give the OPEN count the same
   chip treatment the kind badges use, so the headers stop being bare text with a number floated right. */
.gx-quests .gx-sec { height: auto; padding-bottom: 6px; margin-bottom: 10px; }
.gx-quests .gx-sec .gx-tag { border: 1px solid var(--ph-glow2); border-radius: 2px; padding: 0 6px;
  font-size: 9.5px; line-height: 1.6; color: var(--ph); opacity: .85; font-variant-numeric: tabular-nums; }
/* the north star is the thing every quest below points at — give it the presence to lead the panel */
.gx-quests .q-refresh-card { padding: 11px 13px; }
.gx-quests .q-ns-text { font-size: 14px; line-height: 1.4; margin-top: 5px; }
.gx-quests .q-refresh-row { margin-top: 10px; }
.gx-quests .q-refresh-last { margin-top: 7px; }

/* G1a — quest-log dynamics: the dismiss ✕ on get-to-know-you quests + the completion flourish.
   The flourish is a MOMENT (one ~2.4s gold flash, bold not subtle), never a lingering state. */
/* .q-dismiss composes .x-btn (bright hover) — ghost + opacity delta */
.q-dismiss { margin-left:auto; background:none; border:1px solid transparent; border-radius:2px;
  color:var(--ph-dim); opacity:.55; font-size:11px; padding:1px 4px; }
.q-dismiss:hover { border-color:var(--ph-glow2); opacity:1; }
/* Slice 5: armed (2-step) state — the ✕ becomes an explicit, dangerous "dismiss forever — sure?" until the
   confirming click (or ~4s auto-disarm). Danger skin is the canonical rule above; only the widen + opacity
   (so the longer label fits and reads as a warning, not a passive glyph) is a per-button extra. */
.q-dismiss.armed { opacity:1; white-space:nowrap; padding:1px 6px; }
/* W3 — "build this while I'm away" affordance on an open, buildable quest. Sits before the dismiss ✕; the
   ◈ marks the away-workshop lane. Gold on hover (it opens the sandbox lane, an amber/consent action). */
.q-queue { margin-left:auto; flex:0 0 auto; background:none; border:1px solid transparent; border-radius:2px;
  color:var(--ph-dim); opacity:.7; font-family:inherit; font-size:12px; line-height:1; padding:1px 4px; cursor:pointer; }
.q-queue + .q-dismiss { margin-left:2px; }
.q-queue:hover { color:var(--gold); border-color:var(--gold); opacity:1; }
.q-queue:disabled { opacity:.4; cursor:default; }
/* §C — the GO ▸ chip: routes to the real surface this quest is done on (task board / dossier / REFIT).
   A quiet phosphor chip in the header row, never a raw browser button; nowrap so the label can't
   fold into the title. First of the header's right-aligned trio (GO ▸ · ◈ queue · ✕ dismiss). */
.q-go { margin-left:auto; flex:0 0 auto; background:none; border:1px solid var(--ph-glow2); border-radius:2px;
  color:var(--ph-dim); opacity:.8; font-family:inherit; font-size:9px; letter-spacing:.08em;
  text-transform:uppercase; white-space:nowrap; line-height:1.5; padding:1px 6px; cursor:pointer; }
.q-go:hover { color:var(--gold); border-color:var(--gold); opacity:1; }
.q-go + .q-queue, .q-go + .q-dismiss { margin-left:2px; }
/* shared quest-row vocabulary (F-lane hygiene): the inline styles the rows used to carry, hoisted to classes so the
   markup reads structural and every row speaks one visual language. No behavior change — geometry is identical. */
.q-hd { display:flex; align-items:center; gap:6px; }        /* the glyph + title header row */
.q-gl-gold { color:var(--gold); }                           /* an amber quest glyph (goal / proposal) */
.q-goalbar { border-left:2px solid var(--gold); }           /* the amber left rail on goal + proposal cards */
.q-indent { margin-left:10px; }                             /* a milestone step nested under its goal header */
.q-mt { margin-top:5px; }                                   /* consent-btn cluster / accept button spacing */
.q-bar { height:5px; border-radius:3px; background:var(--well,rgba(0,0,0,.35)); margin-top:5px; overflow:hidden; }
.q-bar-fill { height:100%; background:var(--gold); }        /* width set inline (the live pct) */
.gx-tro .q-cw { opacity:.82; margin-top:3px; }              /* "✓ completes when:" line */
.gx-tro .q-attest-line { margin-top:4px; color:var(--gold); }   /* "⏳ awaiting your confirmation" */
.gx-tro .q-note { opacity:.7; margin-top:3px; }             /* a prior "not yet" decline note */
.gx-tro.q-celebrate { border-color:var(--gold); animation:q-celebrate 2.4s ease-out 1; }
@keyframes q-celebrate {
  0%   { box-shadow:0 0 18px rgba(var(--gold-rgb),.85), inset 0 0 14px rgba(var(--gold-rgb),.45); background:rgba(var(--gold-rgb),.22); }
  55%  { box-shadow:0 0 9px rgba(var(--gold-rgb),.45), inset 0 0 6px rgba(var(--gold-rgb),.22); }
  100% { box-shadow:none; }
}

/* QUEST V3 — the DIRECTION card: north star + standing-refresh status + the REFRESH QUESTS action.
   Reuses the CRT card language (phosphor border, gold accent for the goal); nothing bespoke that would
   drift from the panel's vocabulary. Every value here maps to real engine state (truthful telemetry). */
.q-refresh-card { border:1px solid var(--ph-glow2); border-radius:2px; background:rgba(0,0,0,.18); padding:9px 11px; margin-bottom:12px; }
.q-northstar { border-left:2px solid var(--gold); padding-left:9px; margin-bottom:9px; }
.q-ns-eyebrow { display:block; font-family:inherit; font-size:9px; letter-spacing:.16em; text-transform:uppercase; color:var(--ph-dim); opacity:.8; }
.q-ns-src { color:var(--ph); opacity:.95; }
.q-ns-tag, .q-nstag { display:inline-block; font-size:8px; letter-spacing:.08em; text-transform:uppercase; padding:0 4px; margin-left:5px; border-radius:2px; }
.q-unconf { color:var(--ink); background:var(--gold); opacity:.9; }
.q-ns-text { font-family:inherit; font-size:13px; letter-spacing:.02em; color:var(--gold); text-shadow:0 0 8px rgba(var(--gold-rgb),.35); margin-top:4px; }
.q-ns-text.dim { color:var(--ph-dim); text-shadow:none; }
.q-ns-why { opacity:.7; margin-top:2px; }
.q-refresh-row { display:flex; align-items:center; gap:10px; margin:8px 0 6px; }
.q-refresh-btn { flex:0 0 auto; }
.q-refresh-due { margin:0; }
.q-refresh-last { margin-top:4px; opacity:.85; }
.q-outcome { display:inline-block; font-size:9px; letter-spacing:.06em; text-transform:uppercase; padding:0 5px; border-radius:2px; border:1px solid var(--ph-glow2); color:var(--ph-dim); }
.q-oc-minted { color:var(--gold); border-color:var(--gold); }
.q-oc-none   { color:var(--ph); border-color:var(--ph-glow2); }
.q-oc-skipped, .q-oc-rejected { color:var(--ph-dim); }
.q-oc-error  { color:#e88; border-color:#e88; }

/* COMMANDER JOURNEY — three truthful, non-gating progression tracks. The card only renders the sidecar's
   durable proof snapshot; controls are explicit Commander writes, never inferred engagement telemetry. */
.q-journey-card { border:1px solid var(--ph-glow2); border-radius:2px; background:rgba(0,0,0,.18); padding:10px 11px; margin-bottom:12px; }
.q-evolution { display:flex; align-items:center; justify-content:space-between; gap:12px; padding-bottom:9px; margin-bottom:9px; border-bottom:1px solid var(--ph-glow2); }
.q-evolution-name { margin-top:2px; color:var(--gold); font-family:inherit; font-size:17px; letter-spacing:.16em; text-shadow:0 0 8px rgba(var(--gold-rgb),.35); }
.q-journey-goal { border-left:2px solid var(--gold); padding-left:9px; margin-bottom:11px; }
.q-journey-title { margin:3px 0; color:var(--ph); font-size:12px; }
.q-journey-subhead { margin:12px 0 6px; color:var(--ph-dim); font-size:9px; letter-spacing:.14em; }
.q-metric { padding:7px 0; border-top:1px solid rgba(var(--ph-rgb),.12); }
.q-metric:first-of-type { border-top:0; }
.q-metric .gx-tag { margin-left:auto; }
.q-metric-actions, .q-metric-create { display:grid; grid-template-columns:minmax(70px,.7fr) minmax(120px,1.6fr) auto auto; gap:5px; margin-top:6px; }
.q-metric-create { grid-template-columns:minmax(150px,2fr) minmax(65px,.7fr) minmax(65px,.7fr) minmax(55px,.6fr) auto; padding-top:8px; }
.q-metric-actions input, .q-metric-create input { min-width:0; border:1px solid var(--ph-glow2); border-radius:2px; background:rgba(0,0,0,.35); color:var(--ph); font:inherit; font-size:10px; padding:4px 6px; outline:none; }
.q-metric-actions input:focus, .q-metric-create input:focus { border-color:var(--gold); }
.q-mastery-grid { display:grid; gap:4px; }
.q-mastery-row { display:grid; grid-template-columns:minmax(80px,1fr) minmax(90px,1fr) auto; align-items:center; gap:8px; padding:5px 7px; border:1px solid rgba(var(--ph-rgb),.14); border-radius:2px; font-size:10px; }
.q-receipt { display:flex; align-items:flex-start; gap:8px; padding:7px 0; border-top:1px solid rgba(var(--ph-rgb),.12); }
.q-receipt .sub { flex:1; }
.q-receipt .consent-btn { flex:0 0 auto; }
.q-proof-list { display:grid; gap:4px; margin-top:10px; }
.q-journey-law { margin-top:11px; padding-top:8px; border-top:1px solid var(--ph-glow2); }

/* TROPHY CASE card header — the kind tag (DOSSIER / MILESTONE / n× LIFETIME) pins to the top-right
   corner and never shrinks, so a long title wraps cleanly under it instead of jamming the tag
   mid-text in the narrow grid columns. */
.gx-tro .tro-hd { display:flex; align-items:flex-start; gap:6px; }
.tro-hd .nm { min-width:0; }
.tro-hd .gx-tag { margin-left:auto; flex:0 0 auto; white-space:nowrap; }

.gx-station { border:1px solid var(--gold); border-radius:2px; background:var(--ph-faint); padding:12px 14px; margin-top:6px; }
.gx-station .hd { display:flex; align-items:center; gap:8px; margin-bottom:11px; }
.gx-station .badge { font-family:inherit; font-size:11px; letter-spacing:.1em; color:var(--ink); background:var(--gold); padding:1px 6px; border-radius:2px; }
.gx-station .ttl { font-family:inherit; font-size:12px; letter-spacing:.16em; color:var(--gold); }
.gx-station .agents { margin-left:auto; font-family:inherit; font-size:10px; letter-spacing:.1em; color:var(--ph-dim); }
.gx-station .body { display:flex; align-items:flex-end; gap:16px; }
.gx-station .lv { flex:0 0 auto; text-align:center; border-right:1px solid var(--ph-glow2); padding-right:16px; }
.gx-station .lv .n { font-family:inherit; font-size:28px; line-height:.85; color:var(--gold); text-shadow:0 0 10px rgba(var(--gold-rgb),.4); }

.ag-lv { font-family:inherit; font-size:11px; letter-spacing:.08em; color:var(--gold); border:1px solid var(--gold); border-radius:2px; padding:0 5px; margin-left:7px; }

.tb-stat.station { border:1px solid var(--gold); border-radius:2px; padding:1px 8px; }
.tb-stat.station b { color:var(--gold); }
.tb-stat.station.lvup { animation: gx-stationpulse 1.5s ease-out; }
@keyframes gx-stationpulse { 0%{ box-shadow:0 0 0 0 rgba(var(--gold-rgb),.55); } 100%{ box-shadow:0 0 16px 2px rgba(var(--gold-rgb),0); } }

/* narrow-window fallback: the GROWTH dossier collapses gracefully if the term is squeezed */
@media (max-width: 560px) {
  .gx-2 { grid-template-columns: 1fr; }
  .gx-tros { grid-template-columns: repeat(2, 1fr); }
  .q-metric-actions, .q-metric-create { grid-template-columns:1fr 1fr; }
  .q-mastery-row { grid-template-columns:1fr auto; }
  .q-mastery-row .gx-tag { grid-column:1 / -1; justify-self:start; }
  .q-receipt, .q-evolution { align-items:flex-start; flex-direction:column; }
}

/* responsive: degrade the game layout gracefully as the desktop window narrows.
   Hard breakpoints (no soft transitions) to match the CRT chrome. Stage stays the
   priority all the way down; controls wrap rather than clip. Phone/app not targeted. */

/* 1120px: keep all three columns, just tighten the side columns */
@media (max-width: 1120px) {
  #screen-game.active { grid-template-columns: var(--crew-w, 200px) 1fr var(--chat-w, 300px); }
}

/* 1000px: squeeze gaps/padding + side columns further so the stage keeps room */
@media (max-width: 1000px) {
  #screen-game.active {
    /* --chat-w must survive every breakpoint that still shows a COMMS COLUMN, or the seam drag
       silently dies under 1000px (the grid stops reading the width chatresize.js writes) —
       only the DEFAULT tightens, the dragged width still wins */
    grid-template-columns: 176px 1fr var(--chat-w, 264px);
    padding: 8px; gap: 6px;
  }
}

/* 860px: reflow — crew + stage share the top content row, COMMS drops to its own
   full-width row beneath so the stage stays wide. Stage gets a sensible min-height. */
@media (max-width: 860px) {
  #screen-game.active {
    grid-template-rows: 60px minmax(300px, 1fr) 200px minmax(44px, auto);
    grid-template-columns: 168px 1fr;
    grid-template-areas:
      "top top"
      "left center"
      "chat chat"
      "bot bot";
  }
  /* dragged COMMS width is meaningless once it spans the full row */
  #chat-panel { flex: initial; }
  /* let the bottom-bar docks wrap onto a second line instead of clipping off-screen */
  #bottombar { flex-wrap: wrap; row-gap: 4px; overflow: visible; }
}

/* Short desktop windows need an explicit vertical path to every major region. */
@media (max-width: 860px) and (max-height: 680px) {
  #screen-game.active {
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Landscape phones have very little vertical budget. Keep the fixed controls in-view first:
       a compact stage can scroll its own rails, while COMMS retains enough height for its header,
       identity line and full composer. The grid's overflow remains the fallback if docks wrap. */
    grid-template-rows: 42px minmax(72px, 1fr) minmax(160px, 1fr) minmax(38px, auto);
    padding: 4px;
    gap: 3px;
  }
}

/* ---- CREW rail hidden (leftrail.js): the left column leaves the grid entirely and the stage
   absorbs the space; the ▸ CREW drawer tab (.crew-show) is the only way back. The body-class
   selector out-specifies every breakpoint's #screen-game.active rule, so each override below
   mirrors its breakpoint's remaining columns. ---- */
body.crew-rail-off #left { display: none; }
body.crew-rail-off #screen-game.active {
  grid-template-columns: 1fr var(--chat-w, 360px);
  grid-template-areas: "top top" "center chat" "bot bot";
}
@media (max-width: 1120px) {
  body.crew-rail-off #screen-game.active { grid-template-columns: 1fr var(--chat-w, 300px); }
}
@media (max-width: 1000px) {
  body.crew-rail-off #screen-game.active { grid-template-columns: 1fr var(--chat-w, 264px); }
}
@media (max-width: 860px) {
  body.crew-rail-off #screen-game.active {
    grid-template-columns: 1fr;
    grid-template-areas: "top" "center" "chat" "bot";
  }
}

/* ============================================================
   REFIT MODE — the diegetic full-screen station builder (build.js).
   Sits above the HUD + floating windows but UNDER the global CRT
   scanline/vignette layers (z 940/950) so it stays behind the glass.
   ============================================================ */
.refit-overlay {
  position: fixed; inset: 0; z-index: 80; overflow: hidden;
  background: var(--bg); color: var(--ph); user-select: none;
}
.refit-canvas { position: absolute; inset: 0; width: 100%; height: 100%; image-rendering: pixelated; cursor: crosshair; touch-action: none; }

.refit-top {
  position: absolute; top: 0; left: 0; right: 0; height: 46px; z-index: 2;
  display: flex; align-items: center; gap: 12px; padding: 0 14px;
  background: linear-gradient(180deg, rgba(3,2,1,.92), rgba(3,2,1,.4) 78%, transparent);
}
.refit-title { color: var(--ink); background: var(--ph); padding: 3px 14px; letter-spacing: 3px; font-size: 18px; text-shadow: none; box-shadow: 0 0 14px var(--ph-glow2); }
.refit-sub { color: var(--ph-dim); letter-spacing: 2px; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.refit-spacer { flex: 1; }
/* WELDED CLUSTERS — controls that are ONE instrument look like one: zoom (– % +) and the history
   pair (UNDO/REDO). Six identical loose keys made the deck read as a menu bar; four groups read as
   an instrument panel. Zoom used to be wheel-only, discoverable only from a hint line. */
.refit-zoom, .refit-cluster { display: flex; align-items: stretch; gap: 0; flex: 0 0 auto; }
.refit-top .refit-zoom .bb, .refit-top .refit-cluster .bb { border-radius: 0; margin: 0; }
.refit-top .refit-zoom .bb:first-child, .refit-top .refit-cluster .bb:first-child { border-radius: 4px 0 0 4px; }
.refit-top .refit-zoom .bb:last-child, .refit-top .refit-cluster .bb:last-child { border-radius: 0 4px 4px 0; }
.refit-top .refit-cluster .bb + .bb { border-left-color: rgba(120,200,255,.18); }
/* DONE is the way out and the save — it gets weight the other five don't */
.refit-top .refit-primary { padding-inline: 16px; letter-spacing: 2px; }
.refit-zoomb { min-width: 26px; padding-inline: 0; justify-content: center; font-size: 17px; line-height: 1; }
.refit-zoomlvl { min-width: 54px; justify-content: center; font-size: 12px; letter-spacing: 1px; border-inline: 0 !important; }

/* The build panel — a full-height left sidebar in the station's premium window chrome.
   MODE (tool grid) is fixed at the top, OPTIONS is the ONE scroll region (the palette can
   grow forever — room kinds, the whole prop gallery), the hint is pinned at the foot. */
/* Sized off the OVERLAY (100%), never the viewport (vh/vw): TEXT SIZE is a body zoom and a
   vh length inside the zoomed subtree still computes against the REAL viewport — at AUTO
   (115%) a vh-sized dock rendered ~15% past the glass and hid every palette's tail. The
   overlay is inset:0, so 100% IS the glass in the dock's own (zoomed) coordinate space —
   identical to 100vh at textScale 100. */
.refit-dock {
  position: absolute; left: 14px; top: 54px; z-index: 2;
  display: flex; flex-direction: column; align-items: stretch;
  /* 376px, not 306 (2026-08-07 round 2). The BUILD KIT bands cost the OPTIONS palette ~90px of
     height and pushed SURFACE's COLOUR row below the fold. Width buys that back several times
     over, because both dense palettes are gated on exactly this number: at 356px of content the
     material grid goes 4 → 5 columns (13 recipes in 3 rows, not 4) and the prop gallery 3 → 4
     (a whole category visible without scrolling). 376 also still reads as a SIDEBAR to
     viewInsets (< 60% of the glass), so FIT keeps framing the station in the visible half. */
  width: min(380px, calc(100% - 28px)); max-height: calc(100% - 68px);
  padding: 0; border-radius: 9px; overflow: hidden;
  background: var(--panel);
  box-shadow: 0 0 0 2px var(--ph), 0 0 0 3.5px rgba(0,0,0,.9), 0 0 22px var(--ph-glow2), inset 0 0 40px rgba(0,0,0,.85);
}
/* MOBILE CONTROL REACHABILITY: a touch-only Commander has no hardware Escape key, so
   DONE and every editor action must stay on the glass. The title owns row one and six
   compact station keys share row two. */
@media (max-width: 600px) {
  .refit-top {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    grid-template-rows: auto 30px;
    height: 68px;
    align-content: center;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
  }
  .refit-title {
    grid-column: 1 / -1;
    padding: 2px 8px;
    font-size: 16px;
    letter-spacing: 2px;
  }
  .refit-sub,
  .refit-spacer,
  /* the phone deck is measured at EXACTLY six action columns (see mobile-control-reachability):
     a fourth+fifth+sixth key from the zoom cluster would wrap the row past the 68px it is given.
     Pinch and wheel still zoom; only the on-glass stepper stands down. */
  .refit-zoom { display: none; }
  .refit-top .bb {
    width: 100%;
    min-width: 0;
    height: 30px;
    padding-inline: 3px;
    font-size: 12px;
    letter-spacing: 0;
  }
}
.refit-dock-section {
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  padding: 9px 10px 10px; min-height: 0;
}
.refit-mode-section { flex: 0 0 auto; border-bottom: 1px solid var(--ph-faint); }
.refit-option-section { flex: 1 1 auto; overflow: hidden; }
.refit-option-section.is-empty { display: none; }
.refit-section-label {
  flex: 0 0 auto; color: var(--ph-dim); font-size: 11px; letter-spacing: 2px;
  text-shadow: none; white-space: nowrap; padding-bottom: 2px;
  border-bottom: 1px dashed rgba(120,200,255,.10);
}
.refit-tools {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: stretch; gap: 5px; min-width: 0;
}

/* ---------- THE BUILD KIT (2026-08-07 build-mode overhaul) ----------
   The dock used to open with a tiny grey "MODE" caption over ten identical buttons — station
   hardware with no faceplate. It now leads with an inverse-phosphor header plate (the same
   vocabulary as .refit-title and every window titlebar), and the ten verbs are banded into four
   named groups, each with the QUESTION it answers in lowercase under the band name. */
.refit-dock-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: 7px;
  padding: 7px 10px 6px; border-bottom: 1px solid var(--ph-faint);
  background: linear-gradient(180deg, rgba(120,200,255,.07), transparent);
}
.refit-dock-head-t {
  color: var(--ink); background: var(--ph); padding: 2px 10px;
  font-size: 13px; letter-spacing: 3px; text-shadow: none;
  box-shadow: 0 0 12px var(--ph-glow2);
}
#refit-tools { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.refit-toolgroup { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.refit-group-label {
  display: flex; align-items: baseline; gap: 6px; min-width: 0;
  font-size: 9.5px; letter-spacing: 2px; color: var(--ph); opacity: .6;
  border-bottom: 1px dashed rgba(120,200,255,.13); padding-bottom: 1px; line-height: 1.15;
}
.refit-group-why {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: right; font-size: 9.5px; letter-spacing: .6px; text-transform: lowercase; opacity: .7;
}
/* The bands cost vertical room the OPTIONS palette used to have. Give it back here: the tool rows
   are the one thing in this dock whose height is pure padding, and they still clear the 26px
   touch-row the mobile sheet is measured against. */
.refit-dock .refit-tool { min-height: 25px; padding: 2px 7px; }
.refit-dock-section { padding: 8px 10px 9px; }
.refit-dock-head { padding: 6px 10px 5px; }
/* SELECT owns a full-width row: it is the cursor you fall back to, not a fifth kind of building */
.refit-tools-sel { grid-template-columns: 1fr; }
/* RECLAIM is destructive — its own full-width row, so it is never a mis-click neighbour */
.refit-tool-reclaim { grid-column: 1 / -1; }
/* NOT image-rendering:pixelated — the canvas is an 8× supersample downscaled to this box, and a
   nearest-neighbour downscale is what makes a bitmap icon look lopsided (see build.js toolBtn). */
.refit-toolicon { flex: 0 0 auto; width: 16px; height: 16px; pointer-events: none; opacity: .8; }

/* ---------- THE TOOLS AS HARDWARE (2026-08-07 round 2) ----------
   Ten flat outlines on a flat field read as a list. These read as KEYS on a panel: a matte
   top-lit face (a 1px highlight over a dark base — panel shading, never a gloss ramp), a seated
   shadow under them, and a lit rim + a phosphor edge-bar when armed. Every value is a theme var
   or a tint of one, so a theme switch carries it. */
.refit-dock .refit-tool {
  position: relative;
  background: linear-gradient(180deg, rgba(120,200,255,.055), rgba(0,0,0,.30));
  box-shadow: inset 0 1px 0 rgba(120,200,255,.11), 0 1px 0 rgba(0,0,0,.55);
  border-radius: 4px;
}
.refit-dock .refit-tool:hover {
  background: linear-gradient(180deg, rgba(120,200,255,.13), rgba(0,0,0,.22));
  border-color: var(--ph-dim);
}
/* ARMED: the key is lit and carries a phosphor edge-bar on its leading edge — visible from the
   corner of your eye, which a 2px inset rim alone was not. */
.refit-tool.active::before {
  content: ''; position: absolute; left: 0; top: 3px; bottom: 3px; width: 3px;
  border-radius: 3px 0 0 3px; background: var(--ph); box-shadow: 0 0 8px var(--ph-glow);
}
.refit-dock .refit-tool.active {
  background: linear-gradient(180deg, rgba(120,200,255,.17), rgba(0,0,0,.18));
  padding-left: 12px;
}
.refit-tool-reclaim.active::before { background: var(--bad); box-shadow: 0 0 8px rgba(255,92,77,.5); }

/* ---------- OPTIONS header: `ROOM ▸ TYPE` ---------- */
#refit-palette-label { display: flex; align-items: baseline; gap: 5px; min-width: 0; }
.refit-pl-tool { color: var(--ph-bright); text-shadow: 0 0 8px var(--ph-glow2); }
.refit-pl-sep { opacity: .4; font-size: 9px; }
.refit-pl-what { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- the status strip: the VERB, then the standing keys ---------- */
.refit-hint-verb {
  display: block; color: var(--ph-bright); font-size: 13.5px; letter-spacing: 1.2px;
  line-height: 1.25; text-shadow: 0 0 8px var(--ph-glow2);
}
.refit-hint-keys {
  display: block; margin-top: 3px; color: var(--ph-dim); opacity: .72;
  font-size: 11px; letter-spacing: 1px; line-height: 1.2;
}
.refit-tool.active .refit-toolicon { opacity: 1; }
.refit-toolname { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; text-align: left; }
/* the one destructive verb reads red the whole time, not only once it is armed */
.refit-tool-reclaim { color: rgba(255,150,138,.85); border-color: rgba(255,92,77,.22) !important; }
.refit-tool-reclaim:hover { color: #ffb0a8; border-color: var(--bad) !important; background: rgba(255,92,77,.08) !important; }
.refit-tool-reclaim.active { box-shadow: inset 0 0 0 2px var(--bad), 0 0 10px rgba(255,92,77,.3) !important; color: #ffb0a8; }
/* SELECT is the resting state — phosphor-cool, so an armed build tool is the only warm thing here */
.refit-tools-sel .refit-tool.active { box-shadow: inset 0 0 0 2px var(--ph-dim), 0 0 8px var(--ph-glow2); opacity: .92; }
/* THE scroll fix: the palette owns the panel's remaining height and scrolls as one column —
   nothing in the options region is ever clipped again. */
.refit-palette {
  display: flex; align-content: flex-start; align-items: flex-start; gap: 5px; flex-wrap: wrap;
  justify-content: flex-start; min-width: 0; min-height: 0; width: 100%; flex: 1 1 auto;
  overflow-y: auto; overflow-x: hidden; padding-right: 4px;
  scrollbar-width: thin; scrollbar-color: var(--ph-dim) transparent;
}
.refit-palette::-webkit-scrollbar { width: 8px; }
.refit-palette::-webkit-scrollbar-thumb { background: var(--ph-dim); border-radius: 4px; }
.refit-palette::-webkit-scrollbar-track { background: transparent; }
.refit-palette:empty { display: none; }
.refit-dock .bb {
  border: 1px solid var(--ph-faint); min-height: 28px; line-height: 1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.refit-dock .bb:hover { border-color: var(--ph-dim); background: var(--ph-faint); }
.refit-tool {
  display: flex; align-items: center; justify-content: space-between; gap: 7px;
  font-size: 14px; letter-spacing: 1.5px; padding: 5px 8px;
}
.refit-kind { font-size: 12px; padding-inline: 9px; }
/* prop palette: category tabs WRAP (every tab always visible — no hidden horizontal scroll)
   above a GALLERY of live animated prop previews */
.refit-propcats {
  display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-start; width: 100%;
  min-width: 0;
}
.refit-propcats .bb { flex: 0 0 auto; }
.refit-propcat { font-size: 11px; letter-spacing: 1px; opacity: 0.7; }
.refit-propcat.active { opacity: 1; }

/* prop palette SEARCH — the flat way into a 120-prop catalog when you don't know its drawer.
   The field reuses .refit-input (the picker-card field skin) so it carries the same phosphor grade as
   every other REFIT input; the clear key is a .bb, never a bare button. */
.refit-propsearch { display: flex; align-items: center; gap: 5px; width: 100%; min-width: 0; }
.refit-propsearch .refit-searchfield { flex: 1 1 auto; min-width: 0; margin: 4px 0; font-size: 13.5px; letter-spacing: 1px; }
.refit-searchclear { flex: 0 0 auto; font-size: 12px; padding-inline: 8px; line-height: 1; }
/* the result readout: a real count, or an honest miss — never an empty grid with no explanation */
.refit-searchnote {
  width: 100%; margin: 2px 0 1px; font-size: 11px; letter-spacing: 1.5px;
  font-family: 'VT323', monospace; color: var(--ph-dim); text-shadow: 0 0 5px var(--ph-glow2);
}
.refit-searchnote.none { color: var(--bad); text-shadow: none; }
/* SELECT (the default mode) has no palette — a quiet one-liner explains the click contract */
.refit-selectnote {
  width: 100%; margin: 4px 0 2px; font-size: 12px; letter-spacing: 1px; line-height: 1.35;
  font-family: 'VT323', monospace; color: var(--ph-dim); text-shadow: 0 0 5px var(--ph-glow2);
  white-space: normal;
}
/* The gallery's wrapper MUST claim the full column. .refit-palette is a wrapping FLEX row, so an
   undeclared child shrink-wraps to its content — and .refit-propgrid's own `width:100%` then resolves
   against that shrunken box, collapsing repeat(auto-fill, 84px) from four columns to ONE. Every other
   full-width row here (.refit-tiers, .refit-propcats, .refit-propsearch) declares the same thing for
   the same reason. Tile COUNT looks perfect while this is wrong, which is why it took a real frame. */
#refit-propgrid-host { width: 100%; min-width: 0; }

/* TIER toggle — the hard, obvious split: ⚙ FUNCTIONAL (props that DO something) vs ✦ COSMETIC (looks only) */
.refit-tiers { display: flex; gap: 6px; width: 100%; min-width: 0; margin-bottom: 2px; }
.refit-tier { flex: 1 1 0; justify-content: center; font-size: 12.5px; letter-spacing: 1.5px; opacity: .55; }
.refit-tier.active { opacity: 1; }
.refit-tier-functional.active { border-color: var(--ok) !important; color: #cfffe0; box-shadow: inset 0 0 0 1.5px var(--ok), 0 0 12px rgba(123,200,138,.18); }
.refit-tier-cosmetic.active { border-color: var(--ph) !important; color: var(--ph-bright); box-shadow: inset 0 0 0 1.5px var(--ph), 0 0 12px var(--ph-glow2); }

/* the gallery no longer caps/clips its own height — the .refit-palette column is the scroller */
.refit-propgrid {
  display: grid; grid-template-columns: repeat(auto-fill, 84px); gap: 6px;
  justify-content: space-between; width: 100%;
  margin-top: 4px; padding: 3px 0 2px;
}
.refit-proptile {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 3px;
  width: 84px; padding: 5px 2px 4px; cursor: pointer; border-radius: 5px;
  background: rgba(0,0,0,.34); border: 1.5px solid var(--ph-faint); color: var(--ph-dim);
  font-family: inherit; transition: none;
}
.refit-proptile:hover { border-color: var(--ph); color: var(--ph-bright); background: var(--ph-faint); }
.refit-proptile.active {
  border-color: var(--ph-bright); color: var(--ph-bright);
  box-shadow: 0 0 10px var(--ph-glow), inset 0 0 14px rgba(0,0,0,.55);
}
/* functional tiles read as "wired" — a faint teal cast distinct from cosmetic amber */
.refit-proptile.fn { border-color: rgba(123,200,138,.30); color: #9fd9be; }
.refit-proptile.fn:hover { border-color: var(--ok); color: #cfffe0; background: rgba(123,200,138,.07); }
.refit-proptile.fn.active { border-color: var(--ok); color: #cfffe0; box-shadow: 0 0 10px rgba(123,200,138,.30), inset 0 0 14px rgba(0,0,0,.55); }
.refit-proptile-cv {
  width: 76px; height: 50px; image-rendering: pixelated; pointer-events: none;
  background: radial-gradient(ellipse 78% 78% at 50% 62%, rgba(130,170,150,.08), transparent 75%);
}
.refit-proptile-lbl {
  font-size: 10px; letter-spacing: .5px; line-height: 1; text-align: center;
  max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.refit-proptile-tag { position: absolute; top: 3px; right: 5px; font-size: 10px; color: var(--ph); opacity: .6; pointer-events: none; }
/* capability prop badge — the plain POWER a prop grants (WEB/FILES/TERMINAL…), so the gallery shows at a glance which props matter */
.refit-proptile-grant { position: absolute; top: 3px; left: 4px; font-size: 10.5px; letter-spacing: .04em; color: var(--ph-bright); background: var(--ph-faint); border: 1px solid var(--ph); border-radius: 3px; padding: 1px 3px; pointer-events: none; }
.refit-hint {
  flex: 0 0 auto; border-top: 1px solid var(--ph-faint); padding: 6px 10px 8px;
  font-size: 12px; color: var(--ph-dim); letter-spacing: 1px; min-height: 25px; text-align: left;
  white-space: normal; line-height: 1.25;
}

/* SURFACE palette: MATERIAL (the recipe) above COLOUR (the hue) — two axes, two sections.
   Both speak the SAME card language as the prop gallery above: a dark inset chip with a phosphor
   rim, never a bare <button>. A <button> that declares only a border inherits the UA's grey
   buttonface AND Arial, which is exactly how this section came to read as raw HTML pasted into a
   CRT panel — so every chip here sets background, border-radius and font-family explicitly. */
.refit-palcap {
  display: flex; align-items: baseline; gap: 6px; width: 100%;
  font-size: 11px; letter-spacing: 1px; margin: 9px 0 4px;
}
.refit-palcap:first-child { margin-top: 2px; }
.refit-palcap-k { color: var(--ph); opacity: .6; }
/* the caption carries the live selection as WORDS — 21 hue chips can't each hold a label */
.refit-palcap-v { color: var(--ph-bright); letter-spacing: 1.4px; text-shadow: 0 0 8px var(--ph-glow2); }
.refit-palcap-v::before { content: '· '; opacity: .4; }

/* MATERIAL — a gallery of real baked patches, each labelled (a hue is self-evident from a chip,
   a recipe is not). Sized so six tiles of the bake fit at 1:1 — pixel art is never scaled. */
/* FIXED 66px tracks (the prop gallery's idiom), sized so the 60px patch sits in its cell with no
   dead air — four across keeps ten materials to three rows, which is what stops the dock from
   growing past the bottom of the screen. (The dock's max-height is now %-of-overlay, so it caps
   correctly at every TEXT SIZE — but a short palette still beats a scrollbar.) */
.refit-matgrid { display: grid; grid-template-columns: repeat(auto-fill, 66px); gap: 6px; width: 100%; }
.refit-mattile {
  display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0;
  padding: 4px 1px 3px; cursor: pointer; border-radius: 5px; font-family: inherit;
  background: rgba(0,0,0,.34); border: 1.5px solid var(--ph-faint); color: var(--ph-dim);
}
.refit-mattile:hover { border-color: var(--ph); color: var(--ph-bright); background: var(--ph-faint); }
.refit-mattile.active {
  border-color: var(--ph-bright); color: var(--ph-bright);
  box-shadow: 0 0 10px var(--ph-glow), inset 0 0 14px rgba(0,0,0,.55);
}
.refit-mattile canvas {
  display: block; max-width: 100%; image-rendering: pixelated; pointer-events: none;
  box-shadow: 0 0 0 1px rgba(0,0,0,.55);
}
.refit-matname {
  font-size: 10px; letter-spacing: .5px; line-height: 1; text-align: center;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* COLOUR — the same recipe rendered in every tone, so the row answers "what does PLANK look like
   in COBALT?" rather than showing a flat colour the station never actually renders. */
.refit-huegrid { display: flex; flex-wrap: wrap; gap: 5px; width: 100%; }
.refit-hue {
  flex: 0 0 auto; padding: 0; line-height: 0; cursor: pointer; overflow: hidden;
  border-radius: 4px; font-family: inherit;
  background: rgba(0,0,0,.34); border: 1.5px solid var(--ph-faint);
}
.refit-hue canvas { display: block; image-rendering: pixelated; pointer-events: none; }
.refit-hue:hover { border-color: var(--ph); }
.refit-hue.active { border-color: var(--ph-bright); box-shadow: 0 0 10px var(--ph-glow); }
/* AUTO is not a colour, it's a MODE — "whatever this room's deck is". It has no swatch of its own
   and it needs a WORD, so it takes a full-width row above the hues instead of standing in the grid
   as an odd chip a head taller than its neighbours. */
.refit-hue-auto {
  width: 100%; display: flex; flex-direction: row; align-items: center; gap: 9px;
  padding: 3px 7px 3px 3px; line-height: 1; color: var(--ph-dim); text-align: left;
}
.refit-hue-auto:hover { color: var(--ph-bright); }
.refit-hue-auto.active { color: var(--ph-bright); }
.refit-hue-auto .refit-matname { font-size: 11px; letter-spacing: 1.2px; }

/* STARTER LINES — one-click pre-wired layouts (the beginner onramp) + the belt tool's intro line.
   v2: a single column of UNIFORM cards, each the same anatomy top-to-bottom — schematic viewport
   (a fixed-height dark stage so every card is the same size no matter the blueprint's footprint),
   NAME + a derived footprint/dock chip, one dim purpose line. Hover speaks the gallery's phosphor
   hover language (.refit-proptile), not a private green. The intro stays THE ONE SENTENCE and
   doubles as the belt palette's idle state. */
.refit-lineintro {
  width: 100%; margin: 2px 0 4px; font-size: 12px; line-height: 1.35; letter-spacing: .6px;
  color: var(--ph-dim); text-shadow: 0 0 5px var(--ph-glow2);
}
.refit-linegrid { display: flex; flex-direction: column; gap: 7px; width: 100%; }
/* the LINE LIBRARY's section headers — a rule line + name + one-line blurb, matte phosphor */
.refit-linegroup {
  display: flex; flex-direction: column; gap: 1px; width: 100%; min-width: 0;
  margin: 6px 0 0; padding: 5px 2px 0; border-top: 1px solid var(--ph-faint);
}
.refit-linegrid .refit-linegroup:first-child { margin-top: 0; border-top: 0; padding-top: 0; }
.refit-linegroup-nm {
  font-size: 12px; letter-spacing: 1.6px; color: var(--ph-bright); text-shadow: 0 0 6px var(--ph-glow2);
}
.refit-linegroup-why {
  font-size: 11px; line-height: 1.3; letter-spacing: .5px; color: var(--ph-dim);
  white-space: normal; overflow-wrap: break-word;
}
.refit-linetile {
  display: flex; flex-direction: column; align-items: stretch; gap: 4px; width: 100%; min-width: 0;
  padding: 5px 6px 6px; cursor: pointer; border-radius: 5px; font-family: inherit; text-align: left;
  background: rgba(0,0,0,.34); border: 1.5px solid var(--ph-faint); color: var(--ph);
}
.refit-linetile:hover { border-color: var(--ph); color: var(--ph-bright); background: var(--ph-faint); }
.refit-linetile.active {
  border-color: var(--ph-bright); color: var(--ph-bright);
  box-shadow: 0 0 10px var(--ph-glow), inset 0 0 14px rgba(0,0,0,.55);
}
/* the schematic stage: fixed height = uniform cards; the canvas centres inside at 1:1 */
.refit-linetile-view {
  display: flex; align-items: center; justify-content: center; width: 100%; height: 70px;
  border-radius: 3px; background: #0b0f0e; box-shadow: inset 0 0 0 1px rgba(0,0,0,.55);
}
.refit-linetile-cv { display: block; max-width: 100%; image-rendering: pixelated; pointer-events: none; }
.refit-linetile-hd { display: flex; align-items: center; justify-content: space-between; gap: 6px; width: 100%; }
.refit-linetile .refit-matname { font-size: 12px; letter-spacing: 1.1px; text-align: left; }
/* the stat chip mixes VT323 with fallback-font glyphs ('×' '·') — BOX-centred, never padded by
   font metrics (the symbol-glyph law) */
.refit-linetile-stat {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  padding: 1px 5px; border: 1px solid var(--ph-faint); border-radius: 3px;
  font-size: 10.5px; letter-spacing: 1px; line-height: 1.4;
  color: var(--ph-dim); background: rgba(0,0,0,.3); white-space: nowrap;
}
/* one line of WHAT IT DOES; 2-line floor keeps every card the same height when a purpose wraps */
.refit-linetile-why {
  font-size: 11px; line-height: 1.3; letter-spacing: .4px; min-height: 29px;
  color: var(--ph-dim); opacity: .9;
}
/* DECK-FIT HONESTY: a blueprint the current floor has nowhere to put. The card stays armable
   (never gate the sandbox) — it just stops pretending. Dimmed schematic + the reason in the
   warn amber the rest of REFIT uses for "this won't work yet", matte: no glow, no fill. */
.refit-linetile.nofit { border-style: dashed; opacity: .82; }
.refit-linetile.nofit .refit-linetile-view { opacity: .5; }
.refit-linetile-nofit {
  width: 100%; margin-top: 1px; font-family: 'VT323', monospace;
  font-size: 10.5px; letter-spacing: 1px; line-height: 1.3;
  color: var(--warn); opacity: .9;
}
/* the operational footing sits INSIDE the shelf, directly under the cards */
.refit-linenote {
  width: 100%; margin-top: 2px; padding-top: 5px; border-top: 1px solid var(--ph-faint);
  font-size: 10.5px; letter-spacing: 1.2px; line-height: 1.3;
  font-family: 'VT323', monospace; color: var(--ph-dim); opacity: .85;
}
/* the guide card's lead — THE ONE SENTENCE above the three beats */
.refit-guide-lead { margin: 2px 0 8px; font-size: 13px; line-height: 1.4; color: var(--ph-dim); }

/* DECK / WALLS target toggle — the same lit-frame treatment the prop TIER toggle uses */
.refit-painttarget.active {
  border-color: var(--ph) !important; color: var(--ph-bright);
  box-shadow: inset 0 0 0 1.5px var(--ph), 0 0 12px var(--ph-glow2);
}

.refit-tip {
  position: fixed; z-index: 3; display: none; pointer-events: none;
  font-size: 13px; letter-spacing: 1px; padding: 2px 8px;
  background: rgba(3,2,1,.94); border: 1px solid var(--ph-dim); color: var(--ph-bright);
  text-shadow: 0 0 6px var(--ph-glow2); white-space: nowrap;
  box-shadow: 0 2px 10px rgba(0,0,0,.7);
}
.refit-tip.ok { border-color: var(--ok); color: #aaffc8; }
.refit-tip.bad { border-color: var(--bad); color: #ffb0a8; }

/* Fallout-style prop description card — a persistent hover panel (NOT the transient action tip). Says what a
   prop is + does, its footprint, and (for a placed functional prop) its live assignment. */
.refit-propcard {
  position: fixed; z-index: 5; display: none; pointer-events: none;
  width: 234px; max-width: 72vw; padding: 9px 12px 11px;
  background: var(--panel); border-radius: 7px;
  box-shadow: 0 0 0 1.5px var(--ph), 0 0 0 3px rgba(0,0,0,.9), 0 0 18px var(--ph-glow2), inset 0 0 30px rgba(0,0,0,.85);
  font-family: inherit;
}
.refit-propcard h4 { margin: 0 0 5px; color: var(--ink); background: var(--ph); display: inline-block; padding: 1px 9px; font-size: 14px; letter-spacing: 1.5px; font-weight: normal; }
.refit-propcard .pc-tier { display: block; font-size: 10px; letter-spacing: 1.5px; color: var(--ph-dim); margin-bottom: 5px; }
.refit-propcard .pc-tier.fn { color: var(--ok); }
.refit-propcard p { margin: 0 0 6px; color: var(--text); font-size: 13px; line-height: 1.32; }
.refit-propcard .pc-foot { color: var(--ph-dim); font-size: 11px; letter-spacing: 1px; }
.refit-propcard .pc-assign { margin-top: 7px; padding-top: 6px; border-top: 1px solid var(--ph-faint); font-size: 12px; letter-spacing: .5px; color: var(--warn); }
.refit-propcard .pc-assign.ok { color: #aaffc8; }
/* workstation/bay picker: the agent's model chip beside its name */
.ws-model { opacity: .55; font-size: 10px; letter-spacing: .5px; }

/* primary (safe) action button — inverted phosphor, NOT the danger red */
.refit-primary { background: var(--ph) !important; color: var(--ink) !important; border-color: var(--ph) !important; text-shadow: none !important; }
.refit-primary:hover { background: var(--ph-bright) !important; }

/* per-tool keyboard-shortcut hint chip on each tool button */
.refit-key { flex: 0 0 auto; font-size: 10px; opacity: .5; border: 1px solid currentColor; border-radius: 2px; padding: 0 3px; margin-left: 4px; }
.refit-tool.active { box-shadow: inset 0 0 0 2px var(--ph), 0 0 10px var(--ph-glow2); }
.refit-tool.active .refit-key { opacity: .8; }

/* narrow screens: the panel becomes a bottom sheet — same zones, same single scroll region */
@media (max-width: 760px) {
  .refit-dock { left: 10px; right: 10px; top: auto; bottom: 10px; width: auto; max-height: min(48%, 380px); }  /* % of the overlay, not vh — same zoom law as the desktop dock */
  .refit-dock-section { gap: 5px; padding: 7px 8px; }
  .refit-section-label { font-size: 10px; }
  .refit-tool { min-width: 0; font-size: 13px; }
  .refit-hint { min-height: 32px; }
  /* THE BOTTOM SHEET DISSOLVES THE BANDS. It has only 48% of the glass, and the desktop's four
     named groups cost it two ways: the captions eat rows, and a two-tool group inside a
     three-column grid leaves a dead third column on every one of them. So on a narrow sheet the
     group wrappers go `display: contents` and #refit-tools itself becomes the three-column grid —
     all ten modes flow into FOUR dense rows in the same order, and OPTIONS keeps a real scroll
     viewport (the property test/mobile-control-reachability.test.js exists to guard).
     The BUILD KIT plate goes too: on a phone it sits directly under the ▮ REFIT MODE title and
     says the same thing twice, for 31px the palette needs more. */
  .refit-group-label, .refit-dock-head { display: none; }
  .refit-toolgroup, .refit-tools { display: contents; }
  #refit-tools { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; }
  .refit-toolname { letter-spacing: .5px; }
  /* in one flat grid RECLAIM is no longer a lone widow — let it sit in the flow */
  .refit-tool-reclaim { grid-column: auto; }
}

/* disabled undo/redo (and any .bb) */
.bb:disabled { opacity: .32; cursor: default; pointer-events: none; }

/* a brief attention pulse (undo button after a reclaim; dock button on first run) */
@keyframes refit-pulse { 0%,100% { box-shadow: 0 0 0 0 var(--ph-glow2); } 50% { box-shadow: 0 0 0 3px var(--ph), 0 0 16px var(--ph-glow); } }
.bb.pulse { animation: refit-pulse .45s ease-in-out 0 2; }
#bb-build.refit-nudge { color: var(--ph-bright); animation: refit-pulse 2s ease-in-out infinite; }

/* first-use guide card */
.refit-guide {
  position: absolute; inset: 0; z-index: 4; display: flex; align-items: center; justify-content: center;
  background: rgba(2,1,0,.55); backdrop-filter: blur(1px);
}
.refit-guide-card {
  width: min(420px, 88vw); background: var(--panel); padding: 18px 22px 16px;
  border-radius: 9px; box-shadow: 0 0 0 2px var(--ph), 0 0 0 3.5px rgba(0,0,0,.9), 0 0 26px var(--ph-glow2), inset 0 0 44px rgba(0,0,0,.85);
}
.refit-guide-card h3 { color: var(--ink); background: var(--ph); display: inline-block; padding: 2px 12px; letter-spacing: 2px; margin-bottom: 12px; }
.refit-guide-card ul { list-style: none; margin: 0 0 14px; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.refit-guide-card li { color: var(--text); font-size: 15px; line-height: 1.3; }
.refit-guide-card b { color: var(--ph-bright); font-weight: normal; }
.refit-guide-card .g-ok { color: #aaffc8; } .refit-guide-card .g-bad { color: #ffb0a8; }
.refit-guide-card #refit-guide-go { width: 100%; }

/* ---------- FIRST-RUN GUIDE: the three beats, side by side (2026-08-07) ----------
   Wider than the picker cards that share .refit-guide, because it holds three illustrated steps
   instead of a form. Stacks to one column when the glass can't hold three. */
.refit-guide-wide { width: min(700px, 92vw); }
.refit-steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 4px 0 12px; }
.refit-step {
  position: relative; display: flex; flex-direction: column; gap: 4px; min-width: 0;
  padding: 8px 9px 9px; border-radius: 6px;
  background: rgba(0,0,0,.34); border: 1.5px solid var(--ph-faint);
}
.refit-step-art {
  width: 100%; height: auto; aspect-ratio: 128 / 72; display: block; border-radius: 3px;
  margin-bottom: 2px; background: rgba(0,0,0,.45);
}
.refit-step-n {
  position: absolute; top: 6px; right: 8px; font-size: 15px; line-height: 1;
  color: var(--ph); opacity: .45;
}
.refit-step b { color: var(--ph-bright); font-weight: normal; font-size: 14px; letter-spacing: 1.4px; }
.refit-step > span:not(.refit-step-n) { color: var(--text); font-size: 13px; line-height: 1.3; }
.refit-guide-foot { margin: 0 0 10px; font-size: 12px; line-height: 1.4; color: var(--ph-dim); }
@media (max-width: 640px) {
  .refit-steps { grid-template-columns: 1fr; gap: 7px; }
  .refit-step-art { max-width: 168px; }
}
/* connector-portal bind editor (rides the refit-guide card) */
.refit-conn-rows { display: flex; flex-direction: column; gap: 5px; margin: 4px 0 2px; max-height: 240px; overflow: auto; }
.refit-conn-rows .conn-row { text-align: left; display: flex; align-items: baseline; gap: 6px; }
.refit-conn-rows .conn-row.active { background: var(--ph-faint); border-color: var(--ph); }
.refit-conn-rows .conn-meta { margin-left: auto; color: var(--ph-dim); font-size: 10px; }
.refit-conn-note { color: var(--ph-dim); font-size: 12px; line-height: 1.4; padding: 4px 0; }

/* ---------- CONNECTORS panel (MCP) ---------- */
.mc-list { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 14px; }
.mc-row { display: flex; flex-direction: column; align-items: stretch; gap: 8px; min-width: 0; border: 1px solid var(--ph-dim); border-radius: var(--r-sm); padding: 14px 16px; background: rgba(0,0,0,.35); box-shadow: var(--raise); font-size: 16px; line-height: 1.45; letter-spacing: .2px; overflow-wrap: anywhere; }
.mc-top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.mc-top b { color: var(--ph-bright); font-size: 21px; font-weight: normal; letter-spacing: .5px; min-width: 0; overflow-wrap: anywhere; }
.mc-state { margin-left: auto; font-size: 16px; }
.mc-url { font-size: 16px; word-break: normal; overflow-wrap: anywhere; margin-top: 2px; line-height: 1.45; }
.mc-detail { font-size: 16px; color: var(--bad); margin-top: 4px; line-height: 1.45; overflow-wrap: anywhere; }
.mc-tools { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px; }
.mc-tools code { font-size: 15px; color: var(--ph); border: 1px solid var(--ph-dim); border-radius: 4px; padding: 1px 5px; background: rgba(0,0,0,.4); overflow-wrap: anywhere; min-width: 0; }
.mc-acts { margin-top: 8px; display: flex; gap: 6px; flex-wrap: wrap; }
.mc-form { display: flex; flex-direction: column; gap: 6px; }
/* These are stacked records, unlike the old mission-card .mc-row flex header in style.css.
   Keep title/status, readable context, and actions on separate lines across every consumer. */
.mc-row > * { min-width: 0; }
.mc-row .mc-acts { margin-top: 2px; }
.mc-row .bb.xs { font-size: 14px; min-height: 32px; letter-spacing: .5px; }
.mc-row .mc-tag { font-size: 14px; }
.mc-summary { color: var(--text); font-size: 16px; line-height: 1.45; }
.mc-overview { color: var(--ph-bright); font-size: 18px; margin: 6px 0 12px; }
.mc-notice { padding: 12px 14px; border-left: 2px solid var(--gold); background: rgba(var(--gold-rgb),.055); color: var(--text); font-size: 16px; line-height: 1.5; margin-bottom: 12px; }
.mc-notice b { display: block; color: var(--gold); font-size: 18px; margin-bottom: 4px; }
.mc-inspect { border-top: 1px solid var(--ui-edge); padding-top: 6px; }
.mc-inspect > summary { cursor: pointer; color: var(--ph); font-size: 16px; padding: 4px 0; }
.mc-inspect > summary:focus-visible { outline: 2px solid var(--ph-bright); outline-offset: 2px; }
.mc-inspect[open] > summary { margin-bottom: 8px; }
.mc-inspect .mc-hint { margin: 6px 0; }
.mc-inspect .mc-url { color: var(--text); }
.mc-row[data-service] { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 12px; }
.mc-row[data-service] > :is(.mc-top, .mc-summary) { grid-column: 1 / -1; }
.mc-row[data-service] > .mc-inspect { align-self: start; }
.mc-row[data-service] > .mc-acts { align-self: start; justify-content: flex-end; max-width: 280px; }
@media (max-width: 800px) {
  .mc-row[data-service] { grid-template-columns: minmax(0, 1fr); }
  .mc-row[data-service] > .mc-acts { justify-content: flex-start; max-width: none; }
}
/* EXTENSIONS authoring: two fields side by side on a wide panel, stacked when the console is narrow. No
   colours or chrome of its own — it inherits .key-input / .bb so it can never drift from the control floor. */
.ext-workspace { container-type: inline-size; }
.ext-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.ext-choice { display: flex; align-items: center; gap: 12px; text-align: left; padding: 18px 14px;
  border: 1px solid var(--ui-edge); border-radius: var(--r-sm); background: var(--ui-face);
  box-shadow: inset 0 1px 0 var(--ui-highlight); color: var(--text); cursor: pointer; }
.ext-choice > span:nth-child(2) { flex: 1; min-width: 0; }
.ext-choice b { display: block; font-size: 17px; font-weight: normal; color: var(--ph-bright); }
.ext-choice small { display: block; font-size: 13px; margin-top: 5px; color: var(--text); }
.ext-choice-icon { font-size: 28px; color: var(--ph); }
.ext-choice:hover, .ext-choice[aria-expanded="true"] { border-color: var(--ph); background: var(--ui-face), var(--panel); }
.ext-choice:focus-visible, .ext-details summary:focus-visible { outline: 1px solid var(--ph-bright); outline-offset: 3px; }
.ext-editor { padding: 16px; margin: 12px 0 20px; border: 1px solid var(--ui-edge);
  border-radius: var(--r-sm); background: var(--panel); box-shadow: var(--ui-well); }
.ext-workspace [hidden] { display: none; }
.ext-editor-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 10px; }
.ext-editor-head b { color: var(--ph-bright); font-size: 18px; font-weight: normal; }
.ext-editor label, .ext-details label { display: block; color: var(--text); margin: 7px 0 2px; font-size: 14px; }
.ext-editor .key-input { width: 100%; min-width: 0; box-sizing: border-box; }
.ext-workspace .mc-hint { color: var(--text); line-height: 1.5; }
.ext-details { margin-top: 10px; }
.ext-details summary { color: var(--ph); cursor: pointer; font-size: 13px; padding: 4px 0; }
.ext-details[open] summary { margin-bottom: 8px; }
.ext-list-heading { color: var(--ph); font-size: 12px; letter-spacing: var(--ls-2); margin: 16px 0 8px; }
.ext-empty { padding: 10px 0; font-size: 14px; color: var(--text); }
.ext-row { background: var(--ui-face), var(--panel); border-color: var(--ui-edge); box-shadow: inset 0 1px 0 var(--ui-highlight); padding: 12px; }
.ext-row .mc-top b { overflow-wrap: anywhere; min-width: 0; }
.ext-command { display: block; overflow-wrap: anywhere; white-space: pre-wrap; margin: 8px 0 12px; color: var(--text); }
.ext-workspace #ext-msg:empty { display: none; }
.ext-workspace #ext-msg:not(:empty) { padding: 10px 0; overflow-wrap: anywhere; }
@container (max-width: 540px) { .ext-choices { grid-template-columns: 1fr; } .ext-choice { padding: 12px; } }
/* ROUTINES — RUN NOW result (P0 #11): renders inline in the ACTIVE pane, spliced under the row that fired it,
   framed like a mini terminal so the paid run's output is unmissable (never streamed into the hidden CREATE pane). */
.rt-out { margin: 2px 0 8px; border: 1px solid var(--ph-dim); border-left: 2px solid var(--ph);
  border-radius: var(--r-sm); background: rgba(0,0,0,.4); box-shadow: var(--raise); padding: 7px 10px; }
.rt-out-h { display: flex; align-items: center; gap: 6px; font-size: var(--fs-0);
  color: var(--ph-dim); letter-spacing: var(--ls-2); margin-bottom: 5px; }
.rt-out-h b { color: var(--ph-bright); font-weight: normal; }
.rt-out-x { margin-left: auto; }
.rt-out-b { white-space: pre-wrap; max-height: 220px; overflow: auto; font-size: var(--fs-1); color: var(--text); }
/* per-connector ENABLE pill lives inline in the row header; align it to the title baseline row */
.mc-top { align-items: center; }
.mc-enable { flex: 0 0 auto; margin: 0; }
.mc-enable input[type=checkbox] { margin: 0; }

/* ===== TOOLSETS console: one CRT pill-switch row per capability family ===== */
/* COMPUTE is the always-on core: seated INTO the panel (a well, not a raised tile) so it reads as
   part of the chassis rather than as one more switchable row. Dashed edge keeps "not a control". */
.ts-core { align-items: center; gap: 8px; border: 1px dashed var(--ph-dim); border-radius: var(--r-sm);
  padding: 9px 11px; margin-bottom: 8px; background: rgba(0,0,0,.30); box-shadow: var(--well); opacity: .9; }
.ts-core .ts-glyph { color: var(--ph); text-shadow: 0 0 8px var(--ph-glow); }
.ts-core-tag { font-size: 10px; letter-spacing: 1.5px; color: var(--gold); border: 1px solid var(--gold);
  border-radius: 3px; padding: 0 4px; margin-left: 6px; vertical-align: middle; }
#ts-list { display: flex; flex-direction: column; gap: 8px; }
/* Same depth language as the front door and the catalog cards — one top-left light, matte. Scoped to
   `.ts-row`/`.ts-core` and NOT the shared `.set-row`, which every other settings console also uses. */
.ts-row { align-items: flex-start; gap: 8px; border: 1px solid var(--ph-dim); border-radius: var(--r-sm);
  padding: 8px 10px; box-shadow: var(--raise); position: relative; overflow: hidden;
  background: linear-gradient(180deg, rgba(var(--ph-rgb), .05), rgba(0,0,0,.36) 62%);
  transition: border-color var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out),
              background var(--t-fast) var(--ease-out);
  animation: cardIn var(--t-med) var(--ease-out) both; animation-delay: calc(var(--ci, 0) * 34ms); }
/* the live spine: a lit edge on a granting toolset, dark on one that is off or inert. It states the
   row's REAL state at a glance, which the checkbox alone only says up close. */
.ts-row::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--ph); opacity: .55; transition: background var(--t-fast) var(--ease-out),
  opacity var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out); }
.ts-row:hover { border-color: var(--ph);
  background: linear-gradient(180deg, rgba(var(--ph-rgb), .09), rgba(0,0,0,.32) 62%);
  box-shadow: var(--raise), 0 0 16px var(--ph-glow2), 0 6px 14px rgba(0,0,0,.5); }
.ts-row:hover::before { opacity: 1; box-shadow: 0 0 10px var(--ph-glow); }
/* off or inert = the spine goes dark. Truthful telemetry, in the paint. */
.ts-row.ts-off::before, .ts-row.ts-inert-row::before { background: var(--ph-dim); opacity: .35; box-shadow: none; }
.ts-row > input[type=checkbox] { margin-top: 2px; }
.ts-glyph { font-size: 15px; color: var(--ph); line-height: 1.4; flex: 0 0 auto; }
.ts-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.ts-name { color: var(--ph-bright); letter-spacing: .5px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ts-count { font-size: 10px; letter-spacing: 1px; }
.ts-desc { font-size: 12px; }
.ts-tag { font-size: 10px; letter-spacing: 1px; color: var(--warn); border: 1px solid var(--warn);
  border-radius: 3px; padding: 0 4px; }
/* the inert notice now carries its own cure button, so it is a flex line rather than a bare block */
.ts-inert { display: flex; align-items: center; flex-wrap: wrap; gap: 7px;
  font-size: 11px; color: var(--gold); margin-top: 3px; letter-spacing: .3px; }
.ts-place { flex: 0 0 auto; }
/* "+28 more" — a chip that reads as the continuation of the chip row it ends, not as a command button.
   Dashed so it never competes with the real `.bb` actions elsewhere on the row. */
.ts-more { font-family: inherit; font-size: 11px; letter-spacing: .5px; cursor: pointer;
  color: var(--ph-dim); background: rgba(0,0,0,.4); border: 1px dashed var(--ph-dim);
  border-radius: 4px; padding: 1px 6px; transition: color var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out); }
.ts-more:hover { color: var(--ph-bright); border-color: var(--ph); }
.ts-tools { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px; }
/* each tool name is a key set into the row — recessed, so the chips read as an inventory strip
   rather than as a row of buttons competing with the real actions */
.ts-tools code { font-size: 11px; color: var(--ph); border: 1px solid var(--ph-dim); border-radius: 4px;
  padding: 1px 5px; background: rgba(0,0,0,.42);
  box-shadow: inset 0 1px 3px rgba(0,0,0,.6), inset 0 -1px 0 rgba(var(--ph-bright-rgb), .04); }
.ts-row.ts-off { opacity: .62; }
.ts-row.ts-off .ts-name { color: var(--ph-dim); }
.ts-row.ts-inert-row .ts-glyph { color: var(--ph-dim); }
.ts-spotify { margin-top: 8px; border-top: 1px solid var(--ph-dim); padding-top: 8px; }
/* SKILLS capabilities perk dimmed when its toolset is switched off (honesty parity with the TOOLSETS console) */
.perk.ts-disabled { opacity: .55; }
.perk.ts-disabled .perk-stat { color: var(--ph-dim); }
/* padding-block grew 2px→5px when .bb.xs went line-height:1 (see COMPACT CONSOLE KEYS): the old
   value leaned on the inherited 1.6 line-height for its vertical breathing room, so keeping it
   would have crushed the key to a 13px slab. Net box height is within a pixel of before. */
.bb.xs { font-size: 11px; padding: 5px 9px; }
/* connector add/edit form: one-line help, transport toggle, key:value textareas, transport tag on a row */
.mc-hint { font-size: 16px; color: var(--text); margin: 2px 0 6px; line-height: 1.45; overflow-wrap: anywhere; }
.mc-hint code { color: var(--ph); }
/* shared per-agent MODEL PICKER (native grouped <select> + optional effort select) — used by the dossier
   CONFIG card AND the recruitment bay, so it lives here in the global sheet. Phosphor-terminal styling. */
.mp-wrap { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; flex: 1; min-width: 0; }
.mp-model, .mp-effort {
  font-family: inherit; font-size: 13px; color: var(--ph); background: var(--panel2);
  border: 1px solid var(--ph-dim); border-radius: var(--r-sm, 4px); padding: 4px 8px; cursor: pointer; max-width: 100%;
}
.mp-model { flex: 1 1 200px; min-width: 0; }
.mp-effort { flex: 0 0 auto; color: var(--ph-dim); }
.mp-model:focus, .mp-effort:focus { outline: none; border-color: var(--ph); box-shadow: 0 0 0 1px var(--ph); }
.mp-model optgroup { color: var(--ph-dim); font-style: normal; background: var(--panel2); }
.mp-model option, .mp-effort option { color: var(--ph-bright); background: var(--panel2); }
/* the CONFIG model card: the picker row + the collapsible advanced free-text escape hatch */
.mc-pick-row { display: flex; align-items: center; gap: 10px; margin: 6px 0; }
.mc-pick-row > label { flex: 0 0 60px; color: var(--ph-dim); font-size: 12px; letter-spacing: .5px; }
.mc-pick { flex: 1; min-width: 0; }
.mc-adv { margin: 4px 0 6px; }
.mc-adv > summary { cursor: pointer; color: var(--ph-dim); font-size: 11px; letter-spacing: .5px; list-style: none; padding: 2px 0; }
.mc-adv > summary::-webkit-details-marker { display: none; }
.mc-adv > summary:hover { color: var(--ph); }
.mc-adv[open] > summary { color: var(--ph); margin-bottom: 4px; }
/* BUDGET panel — number-input phosphor styling (the base rule at top only covers text/password) + label→input row.
   Additive: new selectors only, on-theme via the same vars. */
input[type=number].key-input {
  font-family: inherit; font-size: 19px; color: var(--ph-bright);
  background: linear-gradient(180deg, #000, color-mix(in srgb, var(--ph) 3%, #000));
  border: 1px solid var(--ph-dim); border-radius: 4px; padding: 8px 11px; outline: none;
  text-shadow: 0 0 5px var(--ph-glow2); box-shadow: inset 0 2px 5px rgba(0,0,0,.65);
}
input[type=number].key-input:focus { border-color: var(--ph); box-shadow: inset 0 0 18px rgba(0,0,0,.7), 0 0 12px var(--ph-glow2); }
#budget-form .set-row { display: flex; align-items: center; gap: 10px; }
#budget-form .set-row label { flex: 0 0 108px; color: var(--ph-dim); font-size: 12px; letter-spacing: .5px; }
#budget-form .set-row .bg-cap { flex: 1; font-size: 16px; padding: 5px 9px; text-align: right; }
#budget-spend { font-size: 13px; margin: 2px 0 8px; }
#budget-spend b { color: var(--ph-bright); }
/* soft-pool CAP-HIT row + one-click RESUME — rendered ONLY when the governor is actually refusing new runs
   (truthful telemetry: no pool hit, no row). Warn vocabulary, same as the readiness chips. */
#budget-pools .bg-pool-hit { display: flex; align-items: center; gap: 10px; border: 1px solid var(--warn); background: rgba(0,0,0,.32); padding: 6px 9px; margin: 2px 0 8px; font-size: 12.5px; color: var(--warn); }
#budget-pools .bg-pool-hit b { color: var(--ph-bright); }
#budget-pools .bg-pool-hit .bb { flex: 0 0 auto; margin-left: auto; }
/* MODELS panel — the ordered fallback chain (P0-3). Additive: new selectors only, on-theme via the same vars. */
.mc-list-fb { display: flex; flex-direction: column; gap: 4px; margin: 2px 0 4px; }
.fbc-row { display: flex; align-items: center; gap: 6px; border: 1px solid var(--ph-dim); background: rgba(0,0,0,.32); padding: 4px 8px; }
.fbc-ord { flex: 0 0 auto; color: var(--ph-dim); font-size: 12px; }
.fbc-id { flex: 1; color: var(--ph-bright); font-size: 13px; text-shadow: 0 0 5px var(--ph-glow2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fbc-row .bb[disabled] { opacity: .35; cursor: default; }
select.fbc-sel {
  flex: 1; font-family: inherit; font-size: 13px; color: var(--ph);
  background: linear-gradient(180deg, #000, color-mix(in srgb, var(--ph) 3%, #000));
  border: 1px solid var(--ph-dim); border-radius: 4px; padding: 6px 9px; outline: none;
  box-shadow: inset 0 2px 5px rgba(0,0,0,.6);
  transition: border-color .12s, box-shadow .12s;
}
select.fbc-sel:focus { border-color: var(--ph); box-shadow: inset 0 2px 5px rgba(0,0,0,.6), 0 0 12px var(--ph-glow2); }
.mc-seg { display: flex; gap: 0; margin: 2px 0 4px; }
.mc-seg-btn {
  flex: 1; cursor: pointer; font-family: inherit; font-size: 12px; letter-spacing: .5px;
  color: var(--ph-dim); background: rgba(0,0,0,.32); border: 1px solid var(--ph-dim); padding: 4px 8px;
}
.mc-seg-btn + .mc-seg-btn { border-left: none; }
.mc-seg-btn:first-child { border-radius: 4px 0 0 4px; }
.mc-seg-btn:last-child { border-radius: 0 4px 4px 0; }
.mc-seg-btn:hover { color: var(--ph); }
.mc-seg-btn.active { color: var(--ph-bright); background: var(--ph-faint); box-shadow: inset 0 0 0 1px var(--ph); }
.mc-tp-fields { display: flex; flex-direction: column; gap: 6px; }
.mc-kv { resize: vertical; min-height: 40px; font-family: inherit; }
.mc-tag {
  font-size: 10px; letter-spacing: .5px; color: var(--ph); border: 1px solid var(--ph-dim);
  border-radius: 3px; padding: 0 4px; margin-right: 4px; text-transform: uppercase;
}

/* ===== ABILITIES front door: "what are you trying to connect?" =====
   Six routes above the panes. Styled as pressable rows rather than the card grid so it reads as a
   MENU (pick one and move on), not as more content to study — the whole point is to be left behind. */
/* ⛔⛔ LIFT WITH `translate`, NEVER `transform`. Every card here carries `animation: cardIn … both`,
   and a FINISHED fill:both animation retains its final keyframe — `transform: translateY(0)` — which
   outranks even an INLINE style (probed live: setting el.style.transform moved the box 0px). The old
   `.ab-route:active { transform: translateY(1px) }` here was dead CSS for exactly that reason. The
   independent `translate` property is a separate animatable property cardIn never touches, so it
   composes freely (probed: a real 6px move). Same family as the `.cc-card.added` opacity trap. */

/* The front door is seated in a RECESSED WELL — the station's own `--well` recipe — so the six routes
   read as keys set into a console face rather than as six more paragraphs. Matte: an inset floor and
   one top-left light, no specular sheen (`--glass` is the tube in front of the picture, not a panel). */
.ab-router {
  margin: 0 0 16px; padding: 12px 12px 13px; border-radius: var(--r-sm);
  background:
    repeating-linear-gradient(0deg, rgba(0,0,0,.22) 0 1px, transparent 1px calc(3px * var(--cz, 1))),
    linear-gradient(180deg, rgba(var(--ph-rgb), .045), transparent 62%),
    rgba(0,0,0,.30);
  box-shadow: var(--well);
}
.ab-router-q {
  color: var(--ph-bright); letter-spacing: 2px; font-size: 15px; margin: 0 0 10px;
  display: flex; align-items: center; gap: 9px; text-shadow: 0 0 8px var(--ph-glow);
}
/* a phosphor rule that fades out — the console's section grammar, not a new ornament */
.ab-router-q::after {
  content: ''; flex: 1; height: 1px;
  background: linear-gradient(90deg, var(--ph-dim), transparent);
  opacity: .75;
}
.ab-router-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(268px, 1fr)); gap: 7px; }
.ab-route {
  display: flex; align-items: flex-start; gap: 10px; text-align: left; cursor: pointer;
  font-family: inherit; font-size: 13px; letter-spacing: .5px; padding: 9px 11px;
  color: var(--ph); border: 1px solid var(--ph-dim); border-radius: var(--r-sm);
  background: linear-gradient(180deg, rgba(var(--ph-rgb), .05), rgba(0,0,0,.34) 70%);
  box-shadow: var(--raise);
  transition: border-color var(--t-fast) var(--ease-out), background var(--t-fast) var(--ease-out),
              box-shadow var(--t-fast) var(--ease-out), translate var(--t-fast) var(--ease-out);
  animation: cardIn var(--t-med) var(--ease-out) both; animation-delay: calc(var(--ci, 0) * 34ms);
  position: relative; overflow: hidden;
}
/* the spine: a dark phosphor edge that IGNITES on hover. One element, no extra markup. */
.ab-route::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--ph-dim); opacity: .5;
  transition: background var(--t-fast) var(--ease-out), opacity var(--t-fast) var(--ease-out),
              box-shadow var(--t-fast) var(--ease-out);
}
.ab-route:hover {
  border-color: var(--ph); color: var(--ph-bright);
  background: linear-gradient(180deg, rgba(var(--ph-rgb), .13), rgba(0,0,0,.30) 70%);
  box-shadow: var(--raise), 0 0 18px var(--ph-glow2), 0 6px 14px rgba(0,0,0,.55);
  translate: 0 -1px;
}
.ab-route:hover::before { background: var(--ph); opacity: 1; box-shadow: 0 0 10px var(--ph-glow); }
.ab-route:active { translate: 0 1px; box-shadow: var(--well); }
.ab-route:focus-visible { outline: 2px solid var(--ph-bright); outline-offset: 1px; }
/* the glyph sits on its own recessed KEY PLATE — the single strongest "this is hardware" cue here */
.ab-route-glyph {
  flex: 0 0 auto; font-size: 15px; line-height: 1; color: var(--ph);
  width: calc(26px * var(--cz, 1)); height: calc(26px * var(--cz, 1));
  display: grid; place-items: center; border-radius: 3px;
  background: rgba(0,0,0,.42); box-shadow: var(--well);
  text-shadow: 0 0 7px var(--ph-glow);
  transition: color var(--t-fast) var(--ease-out), text-shadow var(--t-fast) var(--ease-out);
}
.ab-route:hover .ab-route-glyph { color: var(--ph-bright); text-shadow: 0 0 12px var(--ph-glow); }
.ab-route-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.ab-route-title { color: var(--ph-bright); letter-spacing: .5px; text-shadow: 0 0 6px var(--ph-glow2); }
.ab-route-blurb { font-size: 11px; line-height: 1.45; letter-spacing: .2px; }
.ab-route-blurb b { color: var(--ph); font-weight: normal; }
/* the affordance carries the DESTINATION: › stays in this console, ↗ opens another window */
.ab-route-go { flex: 0 0 auto; color: var(--ph-dim); font-size: 14px; line-height: 1.3;
  transition: color var(--t-fast) var(--ease-out), translate var(--t-fast) var(--ease-out); }
.ab-route:hover .ab-route-go { color: var(--ph-bright); translate: 2px 0; }
/* Searching means the Commander already named the thing — the results are the answer, so the
   question stops taking up the top of the pane. */
.con-searching .ab-router { display: none; }

/* ===== CONNECTOR CATALOG: one-click, vetted MCP servers (browse-and-add cards) ===== */
/* setup-type legend — color-coded to the per-card auth chips (green no-setup · gold key · dim OAUTH). Single-weight
   font, so no faux-bold; the color carries the emphasis. */
/* tier filter — the legend's own vocabulary turned into controls. Each chip wears the colour of the
   tier it selects, so the row reads as the legend it replaces rather than as a new set of buttons. */
/* Seated in a recessed TRACK so the row reads as one segmented control — a single instrument with a
   lit segment — instead of five loose buttons floating on the pane. */
.cc-filters {
  display: flex; flex-wrap: wrap; gap: 4px; margin: 2px 0 10px;
  padding: 4px; border-radius: var(--r-sm); width: fit-content; max-width: 100%;
  background: rgba(0,0,0,.30); box-shadow: var(--well);
}
.cc-filter {
  font-family: inherit; font-size: 11px; letter-spacing: 1px; cursor: pointer; text-transform: uppercase;
  color: var(--ph-dim); background: transparent; border: 1px solid transparent;
  border-radius: 3px; padding: 3px 10px;
  transition: color var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out),
              background var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out);
}
.cc-filter:hover { color: var(--ph-bright); border-color: var(--ph-dim); background: rgba(var(--ph-rgb), .08); }
.cc-filter:focus-visible { outline: 2px solid var(--ph-bright); outline-offset: 1px; }
/* the selected chip inverts and BLOOMS — the same "this one is live" grammar the console rail uses,
   plus the phosphor halo that says the segment is energised rather than merely filled */
.cc-filter.active {
  color: var(--ink); background: var(--ph); border-color: var(--ph); text-shadow: none;
  box-shadow: 0 0 12px var(--ph-glow2), inset 0 1px 0 rgba(255,255,255,.14);
}
/* each tier chip carries its own tier colour (the `.cc-lg-*` rules below are scoped to `.cc-legend`,
   so the filter row states them again rather than inheriting from prose it is not inside) */
.cc-filter.cc-lg-none { color: var(--ok); border-color: var(--ok); }
.cc-filter.cc-lg-none.active { color: var(--ink); background: var(--ok); border-color: var(--ok); }
.cc-filter.cc-lg-key { color: var(--gold); border-color: var(--gold); }
.cc-filter.cc-lg-key.active { color: var(--ink); background: var(--gold); border-color: var(--gold); }
.cc-filter.cc-f-on { color: var(--ok); border-color: var(--ok); }
.cc-filter.cc-f-on.active { color: var(--ink); background: var(--ok); border-color: var(--ok); }

.cc-legend .cc-lg-none { color: var(--ok); font-weight: normal; }
.cc-legend .cc-lg-key { color: var(--gold); font-weight: normal; }
.cc-legend .cc-lg-oauth { color: var(--ph-dim); font-weight: normal; letter-spacing: .5px; }
.cc-list { display: flex; flex-direction: column; gap: 14px; margin: 4px 0 10px; }
.cc-group { display: flex; flex-direction: column; gap: 8px; }
/* ⛔ A CLASS THAT SETS `display` DEFEATS THE `hidden` ATTRIBUTE. The UA sheet's `[hidden]{display:none}`
   is a bare type-less rule, so the `display:flex` on `.cc-card`/`.cc-group` outranks it: the tier filter
   set `.hidden = true` on 28 of 37 cards and every one of them stayed painted. It read as working only
   because the first category happens to be entirely one tier. Restate the rule at class specificity. */
.cc-group[hidden], .cc-card[hidden] { display: none; }
.cc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(224px, 1fr)); gap: 8px; }
.cc-card { display: flex; flex-direction: column; gap: 6px; border: 1px solid var(--ph-dim); border-radius: var(--r-sm);
  padding: 9px 11px; box-shadow: var(--raise);
  /* one top-left light: a faint phosphor wash at the head falling to the dark card floor */
  background: linear-gradient(180deg, rgba(var(--ph-rgb), .05), rgba(0,0,0,.36) 62%);
  transition: border-color var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out),
              background var(--t-fast) var(--ease-out), translate var(--t-fast) var(--ease-out);
  animation: cardIn var(--t-med) var(--ease-out) both; animation-delay: calc(var(--ci, 0) * 28ms); }
/* lift on hover via `translate` — `transform` is retained by cardIn and cannot win (see the front-door note) */
.cc-card:hover { border-color: var(--ph); translate: 0 -1px;
  background: linear-gradient(180deg, rgba(var(--ph-rgb), .10), rgba(0,0,0,.32) 62%);
  box-shadow: var(--raise), 0 0 16px var(--ph-glow2), 0 6px 14px rgba(0,0,0,.5); }
/* a connected card is LIT, not merely outlined — the green edge plus its own faint halo */
.cc-card.cc-on { border-color: var(--ok); box-shadow: var(--raise), inset 0 0 0 1px var(--ok), 0 0 14px rgba(123,200,138,.16); }
.cc-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cc-head b { color: var(--ph-bright); font-weight: normal; letter-spacing: .5px; }
/* THE CATALOG SEAL — the emblem that makes a card scannable instead of readable (2026-08-14).
   Borrows the bay's `.mkt-coin` / `.mkt-coin-ico` pair verbatim so it inherits the frameless,
   one-phosphor seal treatment; all this rule sets is the size and its refusal to shrink when the
   head wraps. `flex-shrink:0` matters: `.cc-head` wraps, and a flexible seal squashed to an ellipse
   on any card whose name + chip filled the row. */
/* 26px, not 22: a what-it-does GLYPH is a single idea and survives small, but a logo SILHOUETTE carries
   internal structure (the N cut out of Notion's page, Airtable's three facets, GitLab's five) and needs
   the pixels to stay itself instead of becoming a smudge. This is the size the seal set is drawn for. */
.cc-seal { --coin: 26px; flex: 0 0 auto; }
.cc-top .cc-seal { --coin: 24px; }
.cc-badge { font-size: 10px; letter-spacing: .5px; border-radius: 3px; padding: 0 4px; text-transform: uppercase; }
.cc-official { color: var(--ok); border: 1px solid var(--ok); }
.cc-community { color: var(--ph-dim); border: 1px solid var(--ph-dim); }
.cc-chip { margin-left: auto; font-size: 10px; letter-spacing: .5px; white-space: nowrap; }
.cc-blurb { font-size: 12px; line-height: 1.4; flex: 1; }
.cc-acts { display: flex; align-items: center; gap: 8px; margin-top: 2px; }
.cc-home { font-size: 11px; letter-spacing: .5px; text-decoration: none; }
.cc-home:hover { color: var(--ph); }
.cc-key { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cc-steps { margin: 2px 0 4px 16px; padding: 0; font-size: 12px; line-height: 1.4; color: var(--ph-dim); }
.cc-steps li { margin: 2px 0; }
.cc-uri { display: flex; align-items: center; gap: 6px; margin-top: 2px; min-width: 0; }
.cc-uri code { color: var(--ph); flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; word-break: break-all; white-space: normal; }
.cc-copy { flex: 0 0 auto; font: inherit; font-size: 11px; padding: 1px 6px; background: transparent; color: inherit; border: 1px solid currentColor; cursor: pointer; }
.cc-copy:hover { background: rgba(255,255,255,0.08); }

.cc-card .bb[disabled] { opacity: .5; cursor: default; }
/* "VIA <name>" jump landing: the aggregator card flashes gold — a MOMENT (same 2.4s gold-flash
   vocabulary as q-celebrate), never a lingering state. */
.cc-card.cc-jump { border-color: var(--gold); animation: q-celebrate 2.4s ease-out 1; }

/* ---- KEYS "PICK A PLATFORM" cards ----
   These three classes shipped with NO RULE AT ALL. `.cc-top` is the KEYS directory's head row, and with
   no `display:flex` its <b> and its tier chip collapsed together into one run of text: every card in the
   directory rendered its name welded to its badge — "EtsyAPI key", "PrintfulAPI key". The cards also sat
   in a bare `.cc-group` (no `.cc-grid`), so eleven of them stacked full-width down the pane while the
   CATALOG's identical cards tiled three-up. Same card family, two different unstyled fates.
   `.cc-top` mirrors `.cc-head` rather than aliasing it: the KEYS chip is a bordered tier badge that sits
   hard right, where the catalog's `.cc-chip` is plain colour-coded text. */
.cc-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cc-top b { color: var(--ph-bright); font-weight: normal; letter-spacing: .5px; }
.cc-tier { margin-left: auto; font-size: 10px; letter-spacing: .5px; white-space: nowrap;
  border: 1px solid currentColor; border-radius: 3px; padding: 0 4px; }
.cc-tier.cc-lg-none { color: var(--ok); }
.cc-tier.cc-lg-key { color: var(--gold); }
.cc-tier.cc-lg-oauth { color: var(--ph-dim); }
/* An already-keyed platform is context, not an offer.
   ⛔ NOT VIA `opacity`: `.cc-card` carries `animation: cardIn … both`, and cardIn's retained final
   keyframe (`opacity:1`) BEATS any normal opacity declaration in the cascade — the rule computed to
   1 and the state was invisible. Recede through colour + the same green edge `.cc-on` uses, which
   are not animated, rather than adding an `!important` to out-shout the animation. */
.cc-card.added { border-color: var(--ok); box-shadow: var(--raise), inset 0 0 0 1px var(--ok); cursor: default; }
.cc-card.added .cc-top b { color: var(--ph-dim); }
.cc-card.added .cc-blurb, .cc-card.added .mc-url { color: var(--ph-dim); }
/* …and it must not ANSWER the pointer. The depth pass gave every `.cc-card` a hover lift + phosphor
   edge; inherited here it made an already-keyed platform rear up like an offer while `cursor:default`
   said the opposite. A card that cannot be clicked must not animate as though it can. */
.cc-card.added:hover { border-color: var(--ok); translate: none;
  background: linear-gradient(180deg, rgba(var(--ph-rgb), .05), rgba(0,0,0,.36) 62%);
  box-shadow: var(--raise), inset 0 0 0 1px var(--ok); }
/* the directory's own category strip. The CATALOG uses the full `.sec` rule here; KEYS emits a bare
   label, so it gets the same engraved-label grammar without the rule/diamond furniture. */
.cc-cat { color: var(--ph); letter-spacing: 2px; font-size: 12px; text-transform: uppercase;
  margin: 2px 0 1px; opacity: .85; }
/* the KEYS directory card is a PICK, so it must read as pressable — the catalog card is a container
   whose button does the pressing. Hover lifts it the way the shared card language lifts everything else. */
[data-ky-pick] { cursor: pointer; transition: border-color var(--t-fast) var(--ease-out), background var(--t-fast) var(--ease-out); }
[data-ky-pick]:hover:not(.added) { border-color: var(--ph); background: rgba(0,0,0,.5); }

.rt-agent-pick { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 5px; }
.rt-agent-btn {
  min-width: 0; display: grid; grid-template-columns: 9px minmax(0, 1fr); grid-template-rows: auto auto;
  column-gap: 6px; align-items: center; text-align: left; cursor: pointer;
  color: var(--ph); background: rgba(0,0,0,.32); border: 1px solid var(--ph-dim);
  font-family: inherit; font-size: 12px; letter-spacing: .5px; padding: 5px 7px;
}
/* selection reads through the phosphor language, NOT the agent's suit colour — the suit
   colour stays on the identity DOT only (see .rt-agent-dot). Keeps the chip on-palette
   across every theme instead of painting a stray blue/purple border for a blue/purple agent. */
.rt-agent-btn:hover, .rt-agent-btn.active { border-color: var(--ph); background: var(--ph-faint); color: var(--ph-bright); }
.rt-agent-btn.active { box-shadow: inset 0 0 0 1px var(--ph), 0 0 10px var(--ph-glow2); }
.rt-agent-dot { width: 7px; height: 7px; background: var(--rt-agent-color, var(--ph)); box-shadow: 0 0 6px var(--rt-agent-color, var(--ph)); }
.rt-agent-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rt-agent-id { grid-column: 2; min-width: 0; color: var(--ph-dim); font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- SCHEDULE PICKER (.sp) — the routine WHEN builder (frontend/app/schedpicker.js) ----------
   Reads as one instrument panel: a row of cadence keys, then only the controls that cadence needs.
   Every control here is skinned explicitly. `.sp-num` especially: the sheet's field rule is keyed on
   [type=text|password|url|search] + bare inputs, so an [type=number] falls all the way through to the
   CONTROL FLOOR (which sets font-family and nothing else) and would paint OS-white — the exact bug the
   no-white-controls law names. It gets the full field treatment below, not a floor. */
.rt-when { display: flex; flex-direction: column; gap: 6px; }
.rt-when-k { color: var(--ph); letter-spacing: 2px; font-size: 12px; text-transform: uppercase; opacity: .85; }
.sp { display: flex; flex-direction: column; gap: 6px; }
.sp-modes { display: flex; flex-wrap: wrap; gap: 4px; }
.sp-mode {
  cursor: pointer; color: var(--ph); background: rgba(0,0,0,.32); border: 1px solid var(--ph-dim);
  font-family: inherit; font-size: 12px; letter-spacing: .5px; padding: 5px 9px; border-radius: 3px;
  transition: border-color var(--t-fast, .12s) var(--ease-out, ease-out), background var(--t-fast, .12s) var(--ease-out, ease-out), color var(--t-fast, .12s) var(--ease-out, ease-out);
}
.sp-mode:hover { border-color: var(--ph); background: var(--ph-faint); color: var(--ph-bright); }
.sp-mode.active { border-color: var(--ph); background: var(--ph-faint); color: var(--ph-bright); box-shadow: inset 0 0 0 1px var(--ph), 0 0 10px var(--ph-glow2); }
/* a pane is the detail row for ONE cadence; only the active cadence's panes are in the layout */
.sp-pane { display: none; }
.sp-pane.on { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.sp-pane.sp-adv.on { flex-direction: column; align-items: stretch; gap: 4px; }
.sp-lbl { color: var(--ph-dim); font-size: 13px; letter-spacing: 1px; text-transform: uppercase; }
.sp-colon { color: var(--ph-dim); }
.sp-tz { color: var(--ph-dim); font-size: 12px; letter-spacing: .5px; margin-left: 2px; }
.sp-hint { color: var(--ph-dim); font-size: 12px; line-height: 1.35; }
.sp .sp-num {
  width: 66px; font-family: inherit; font-size: 15px; color: var(--ph-bright); text-align: center;
  background: linear-gradient(180deg, #000, color-mix(in srgb, var(--ph) 3%, #000));
  border: 1px solid var(--ph-dim); border-radius: 4px; padding: 6px 8px; outline: none;
  text-shadow: 0 0 5px var(--ph-glow2); box-shadow: inset 0 2px 6px rgba(0,0,0,.7);
}
.sp .sp-num:focus { border-color: var(--ph); box-shadow: inset 0 2px 6px rgba(0,0,0,.7), 0 0 12px var(--ph-glow2); }
.sp-days { display: flex; flex-wrap: wrap; gap: 4px; }
/* the day keys are the one place a beginner reads a WEEK at a glance, so they are square and equal —
   S M T W T F S in calendar order, never a dropdown that hides six of the seven choices. */
.sp-day {
  width: 34px; height: 30px; display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--ph); background: rgba(0,0,0,.32); border: 1px solid var(--ph-dim);
  font-family: inherit; font-size: 14px; border-radius: 3px;
  transition: border-color var(--t-fast, .12s) var(--ease-out, ease-out), background var(--t-fast, .12s) var(--ease-out, ease-out), color var(--t-fast, .12s) var(--ease-out, ease-out);
}
.sp-day:hover { border-color: var(--ph); background: var(--ph-faint); color: var(--ph-bright); }
.sp-day.on { border-color: var(--ph); background: var(--ph-faint); color: var(--ph-bright); box-shadow: inset 0 0 0 1px var(--ph), 0 0 10px var(--ph-glow2); }
/* the timer UNIT keys (minutes · hours · days) — the chosen unit is lit beside the number, same language as the day keys */
.sp-units { display: inline-flex; gap: 4px; }
.sp-unit {
  cursor: pointer; color: var(--ph); background: rgba(0,0,0,.32); border: 1px solid var(--ph-dim);
  font-family: inherit; font-size: 13px; letter-spacing: .5px; padding: 5px 9px; border-radius: 3px;
  transition: border-color var(--t-fast, .12s) var(--ease-out, ease-out), background var(--t-fast, .12s) var(--ease-out, ease-out), color var(--t-fast, .12s) var(--ease-out, ease-out);
}
.sp-unit:hover { border-color: var(--ph); background: var(--ph-faint); color: var(--ph-bright); }
.sp-unit.on { border-color: var(--ph); background: var(--ph-faint); color: var(--ph-bright); box-shadow: inset 0 0 0 1px var(--ph), 0 0 10px var(--ph-glow2); }
/* the honest-consequence line (a day-of-month that some months skip, a once-time already past) */
.sp-note { color: var(--ph-dim); font-size: 12px; line-height: 1.35; }
.sp-note.warn { color: var(--gold); }
.sp-note[hidden] { display: none; }

/* ROUTINE ROWS STACK. `.mc-row` is a space-between FLEX ROW (style.css), so a routine's name, its
   meta line and its action buttons all compete for ONE line: the meta was already breaking mid-word
   ("runs as NOVA · next … las / t never run") and a fourth action tightened it further. The routines
   list is the one console list whose row carries a sentence — "every Tuesday at 9:00 AM" — so give
   ITS rows the column layout this markup was clearly written for (`.mc-acts` already declares its own
   margin-top). Scoped to #rt-list: no other console list moves. */
#rt-list .mc-row { flex-direction: column; align-items: stretch; }
#rt-list .mc-url { word-break: normal; overflow-wrap: anywhere; }

/* RESCHEDULE — the same picker, inline under the routine row it is moving */
.rt-resched {
  display: flex; flex-direction: column; gap: 6px;
  margin: 0 0 8px; padding: 9px 11px;
  border: 1px solid var(--ph-dim); border-left: 2px solid var(--ph); border-radius: var(--r-sm, 4px);
  background: var(--panel2);
}
.rt-resched-pv { min-height: 1em; font-size: .9em; }
.mc-acts button[data-act="resched"].on { border-color: var(--ph); color: var(--ph-bright); background: var(--ph-faint); }

/* ---------- accessibility: a consistent keyboard-focus ring (Finding #3) ----------
   A single, theme-agnostic :focus-visible outline so the focused control is always
   visible when tabbing through a floating .term window or a dock popover. Uses the
   phosphor accent so it reads on every theme; only shows for keyboard focus, never
   on mouse click. The .term panel itself gets focus on open (tabindex=-1) but is a
   container, so its ring is suppressed to avoid a full-window outline. */
.term:focus-visible { outline: none; }
.term:focus-within { box-shadow: inset 0 0 0 1px var(--ph-glow2); }
.term button:focus-visible,
.term input:focus-visible,
.term textarea:focus-visible,
.term select:focus-visible,
.term a:focus-visible,
.term [tabindex]:focus-visible,
.term-x:focus-visible,
.bb-menu .bb:focus-visible,
.bb-grp:focus-visible,
.btn:focus-visible, .btn-xl:focus-visible, .btn-sm:focus-visible, .prov:focus-visible {
  outline: 2px solid var(--ph-bright); outline-offset: 2px;
}
.bb-menu .bb:focus-visible { background: var(--ph-faint); color: var(--ph-bright); }

/* ---------- ui-irritants: NOTIFICATIONS panel severity (whisper, not traffic light) ----------
   The .nf base (style.css) is a bare bottom-ruled text row; add a lead glyph + a left hairline
   that colours to the severity, matching the toast language. Text-colour per severity already
   lives in style.css (.nf.good/.warn/.gold); we add the edge + glyph + the bad/info cases. */
.nf {
  display: flex; align-items: baseline; gap: 6px;
  padding-left: 8px; border-left: 2px solid var(--ph-dim);
}
.nf-sev { flex: 0 0 auto; font-size: 11px; line-height: 1.3; opacity: 0.9; }
.nf-ts { color: var(--ph-dim); font-size: 12px; letter-spacing: 0.5px; flex: 0 0 auto; }
.nf-txt { flex: 1 1 auto; min-width: 0; }
/* per-row dismiss ✕ — quiet until the row is hovered/focused, then a soft phosphor cue */
.nf-x {
  flex: 0 0 auto; align-self: center; margin-left: 4px;
  background: none; border: 0; color: var(--ph-dim); cursor: pointer;
  font-family: inherit; font-size: 12px; line-height: 1; padding: 2px 4px; opacity: 0;
  transition: opacity 120ms ease, color 120ms ease;
}
.nf:hover .nf-x, .nf:focus-within .nf-x { opacity: 0.75; }
.nf-x:hover, .nf-x:focus { opacity: 1; color: var(--bad); outline: none; }
.nf.sev-bad  { border-left-color: var(--bad); color: var(--bad); }
.nf.sev-warn { border-left-color: var(--warn); }
.nf.sev-good { border-left-color: var(--ok); }
.nf.sev-gold { border-left-color: var(--gold); }
.nf.sev-info { border-left-color: var(--ph-dim); }
.nf.sev-bad  .nf-sev { color: var(--bad); }
.nf.sev-warn .nf-sev { color: var(--warn); }
.nf.sev-good .nf-sev { color: var(--ok); }
.nf.sev-gold .nf-sev { color: var(--gold); }
.nf.sev-info .nf-sev { color: var(--ph-dim); }

/* ============================================================
   CONSOLE MODE — the large two-pane window (agent/console-windows)
   A dense panel (SETTINGS, SKILLS) opens as a big centred window: a left
   section rail + a right content pane showing ONE section at a time, with a
   cross-section search. Kills the small-window scroll tunnel. Phosphor vars
   only; motion (the .swap-in crossfade) rides motion.css.
   ============================================================ */
/* STEADY SHELL — ONE HEIGHT PER CONSOLE, FOR EVERY SECTION (2026-08-15).
   Content-fit height (`height:auto`, the 2026-08-03 audit's answer to sparse panels) is wrong for a TAB SET,
   and every console mounted through mountConsole is one. A never-moved window is CSS-centred
   (translate(-50%,-50%)), so a section that is shorter than its neighbour re-centres the whole box and drags
   the rail out from under the cursor: measured live at 1280x720, SETTINGS was 590px on PROVIDERS, 534px on
   BUDGET and 462px on ALERTS — a 128px jump, and the tab you were aiming at moved 64px while you clicked it.
   The DOSSIER (seven tabs) and DELIVERABLES (a details drawer) had each already been pinned to `--con-h` for
   exactly this reason; the remaining consoles — SETTINGS, ABILITIES, CHANNELS, AUTOMATION — were still
   breathing. Pin the shell here instead and let the pane scroll, so it is one rule, not four exceptions.
   - the sparse-panel case the audit fixed is gone by other means: LOOPS/LOGBOOK/ROUTINES are no longer
     windows of their own, they are lanes inside the AUTOMATION console.
   - min-height still yields to a short viewport, so a <=380px-tall screen can't be out-sized by the floor.
   - a user drag writes inline width/height (.term-sized), which still wins over this.
   NO height transition here: the window already animates open via term-power (scaleY 0.012->1), and adding a
   height tween on top left the box measurable at ~4px mid-animation. */
/* WIDE — the second (and last) window size. `.term.wide` is WIDTH ONLY: it gives a non-console window
   (TASK BOARD's three lanes, QUEST LOG's card grid, the COMMANDER DOSSIER's two columns) the same
   shell as the consoles without mounting the console rail markup. --term-w-wide is declared on `.term`
   in style.css so both selectors read ONE token. See the two-sizes note there. */
.term.wide { width: var(--term-w-wide); max-width: var(--term-w-wide); }
.term.console {
  /* The width and height limits are expressed in the counter-zoomed window coordinate space and
     bound to the measured strip between the chrome bars. This retains content-fit consoles while
     preventing large TEXT SIZE from placing their titlebar underneath fixed chrome. */
  --con-w: var(--term-w-wide);
  --con-h: min(var(--term-band, calc(78vh * var(--sn-unzoom, 1))), 720px);
  width: var(--con-w);
  height: var(--con-h);
  /* the floor yields to a short viewport — a flat 320px would out-size the window on a <=380px-tall screen
     (and out-size the 84vh narrow-breakpoint height below), pushing the panel past the frame. */
  min-height: min(320px, var(--con-h));
  max-width: var(--con-w); max-height: var(--con-h);
}
/* (the AGENT DOSSIER's own `.dossier` height override is gone — `.term.console` above now carries the
   steady shell for every console, which is what that rule was asking for one window at a time.) */
.term.console .term-body.term-console-body {
  display: flex; flex-direction: row; gap: 0;
  padding: 0; overflow: hidden;              /* the content pane owns the scroll, not the body */
  flex: 1 1 auto; min-height: 0;
}

/* ---- left: search + section rail ---- */
.con-rail {
  flex: 0 0 190px; display: flex; flex-direction: column; min-height: 0;
  border-right: 1px solid var(--ph-dim);
  background: linear-gradient(180deg, var(--ph-glow2), transparent 40%), rgba(0,0,0,0.28);
}
.con-search {
  position: relative; flex: 0 0 auto;
  padding: 10px 10px 8px; border-bottom: 1px solid var(--ph-faint);
}
.con-search-i {
  position: absolute; left: 18px; top: 50%; transform: translateY(-50%);
  color: var(--ph-dim); font-size: var(--fs-2); pointer-events: none; text-shadow: 0 0 6px var(--ph-glow2);
}
/* .con-search prefix RAISES specificity above the base `input[type=text]` rule (0,1,1) — without it
   that base padding (7px 10px) won the cascade and the ⌕ glyph overlapped the placeholder's first char. */
.con-search .con-search-in {
  width: 100%; font-family: inherit; font-size: var(--fs-1); color: var(--ph-bright);
  background: #000; border: 1px solid var(--ph-dim); border-radius: var(--r-sm);
  /* clear the ⌕ glyph (absolute at left:18px) so it never overlaps the placeholder's first character */
  padding: 6px 8px 6px 32px; outline: none; letter-spacing: var(--ls-1);
  text-shadow: 0 0 5px var(--ph-glow2); box-shadow: inset 0 2px 5px rgba(0,0,0,.6);
}
.con-search-in:focus { border-color: var(--ph); box-shadow: inset 0 0 12px rgba(0,0,0,.7), 0 0 10px var(--ph-glow2); }

/* ---------- window unsaved-close guard banner (P0 draft-loss) ---------- */
/* shown transiently when a ✕/Esc close is refused because a window holds a modified, unsaved editor. */
.term-unsaved-bar {
  position: absolute; left: 10px; right: 10px; bottom: 10px; z-index: 4;
  margin: 0; padding: 5px 10px; text-align: center; pointer-events: none;
  font-size: var(--fs-1); letter-spacing: var(--ls-1);
  color: var(--link-down); background: rgba(0,0,0,0.92);
  border: 1px solid rgba(255,80,60,0.7); border-radius: var(--r-sm);
  box-shadow: 0 0 14px rgba(255,80,60,0.35);
  animation: unsaved-pulse 1.1s ease-in-out infinite;
}
@keyframes unsaved-pulse { 0%,100% { opacity: 0.72; } 50% { opacity: 1; } }

/* ---------- per-agent window switcher (REWIND / LOGBOOK) + logbook header + transcript toggle ---------- */
.rw-switch { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 10px; }
.rw-agent {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: inherit; font-size: var(--fs-1); letter-spacing: var(--ls-1);
  color: var(--ph-dim); background: rgba(0,0,0,0.4);
  border: 1px solid var(--ph-faint); border-radius: var(--r-sm);
  padding: 3px 8px; cursor: pointer; transition: color 120ms ease, border-color 120ms ease;
}
.rw-agent:hover { color: var(--ph); border-color: var(--ph-dim); }
.rw-agent.sel { color: var(--ink); background: var(--ph); border-color: var(--ph); text-shadow: none; }
.rw-agent-dot { font-size: 10px; line-height: 1; }
.rw-agent.sel .rw-agent-dot { color: var(--ink) !important; }
.lb-agent-h { font-size: var(--fs-1); letter-spacing: var(--ls-1); color: var(--ph-dim); margin: 2px 0 8px; padding: 0 2px; }
.lb-agent-h b { color: var(--ph-bright); }
.lb-tx-btn {
  font-family: inherit; font-size: 11px; letter-spacing: var(--ls-1);
  color: var(--ph-dim); background: none; border: 0; cursor: pointer; padding: 0 2px;
}
.lb-tx-btn:hover, .lb-tx-btn:focus { color: var(--ph); outline: none; text-shadow: 0 0 5px var(--ph-glow2); }
.lb-run-open.tx-open { border-color: var(--ph-dim); }
.con-rail-list {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 8px 8px 12px;
  display: flex; flex-direction: column; gap: 3px;
  scrollbar-width: thin; scrollbar-color: var(--ph-dim) transparent;
}
.con-rail-list::-webkit-scrollbar { width: 6px; }
.con-rail-list::-webkit-scrollbar-thumb { background: var(--ph-dim); border-radius: 4px; }
.con-rail-list::-webkit-scrollbar-track { background: transparent; }
.con-rail-item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  font-family: inherit; font-size: var(--fs-1); letter-spacing: var(--ls-2); color: var(--ph-dim);
  background: transparent; border: 1px solid transparent; border-radius: var(--r-sm);
  padding: 7px 9px; cursor: pointer; text-shadow: 0 0 6px var(--ph-glow2);
  transition: color .12s ease, background .12s ease, border-color .12s ease;
}
.con-rail-glyph { flex: 0 0 auto; width: 15px; text-align: center; opacity: .85; }
.con-rail-label { flex: 1 1 auto; }
.con-rail-item:hover { color: var(--ph-bright); border-color: var(--ph-dim); background: var(--ph-faint); }
/* active = the inverted title-chip idiom + a phosphor edge on the left (ties into the window title chip) */
.con-rail-item.active {
  color: var(--ink); background: var(--ph); border-color: var(--ph);
  text-shadow: none; box-shadow: 0 0 12px var(--ph-glow2), inset 3px 0 0 var(--ph-bright);
}
.con-rail-item.active .con-rail-glyph { opacity: 1; }
.con-rail-item:focus-visible { outline: 2px solid var(--ph-bright); outline-offset: 1px; }
/* search dimming of rail items with zero matches */
.con-rail-item.con-rail-dim { opacity: .32; }
.con-rail-item.con-rail-hit:not(.active) { color: var(--ph-bright); }

/* ---- tabsTop: the section tabs as a horizontal strip at the top of the content pane ----
   Opted into by the AGENT DOSSIER so its left rail is purely the agent roster. The strip reuses
   .con-rail-item for base type/colour, then .con-toptab reshapes it horizontal: the active state is a
   hairline phosphor underline + inverted chip (echoes the vertical rail's inset-left edge, turned to the
   bottom). The strip is sticky so the tabs stay put while a long section pane scrolls under them. */
/* tabsTop column: a fixed strip above a scrolling pane. The strip is OUTSIDE the scrollport (see mountConsole),
   so it can no longer be scrolled under, past, or through — it is chrome, and chrome does not move. */
.con-col { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.con-col > .con-pane { flex: 1 1 auto; min-height: 0; }
.con-toptabs {
  flex: 0 0 auto;
  display: flex; flex-wrap: wrap; gap: 4px; align-items: stretch;
  padding: var(--s-3) var(--s-4) 8px;
  border-bottom: 1px solid var(--ph-dim);
  background: linear-gradient(180deg, var(--ph-glow2), transparent 60%);
}
/* the pane no longer owns the gap under the strip (the strip's border is the divider) */
.con-col > .con-pane { padding-top: var(--s-3); }
.con-rail-item.con-toptab {
  width: auto; flex: 0 1 auto; position: relative;
  padding: 8px 14px; border-radius: var(--r-sm) var(--r-sm) 0 0;
}
.con-rail-item.con-toptab .con-rail-glyph { width: auto; }
/* active top tab = inverted chip like the vertical rail, but the phosphor edge rides the BOTTOM as an
   underline (hairline) instead of the left inset — consistent CRT-console active idiom, horizontal. */
.con-rail-item.con-toptab.active {
  color: var(--ink); background: var(--ph); border-color: var(--ph);
  text-shadow: none; box-shadow: 0 0 12px var(--ph-glow2), inset 0 -3px 0 var(--ph-bright);
}

/* ---- right: content pane, one section shown ---- */
.con-pane {
  flex: 1 1 auto; min-width: 0; min-height: 0; overflow-y: auto;
  padding: var(--s-3) var(--s-4) var(--s-5);
  scrollbar-width: thin; scrollbar-color: var(--ph) transparent;
}
.con-pane::-webkit-scrollbar { width: 8px; }
.con-pane::-webkit-scrollbar-thumb { background: var(--ph-dim); border-radius: 4px; }
.con-pane::-webkit-scrollbar-thumb:hover { background: var(--ph); }
.con-pane::-webkit-scrollbar-track { background: transparent; }
.con-sec-hidden { display: none; }
.con-sec-head { margin-top: 2px; }
/* the section's one-line self-explanatory description under its header */
/* 86ch, not 62ch: a section desc sat in a ~50-character column while the `.set-about` paragraphs
   directly beneath it ran the full pane width. Two prose blocks stacked against the same left margin
   disagreeing about their measure is what read as broken layout — not the measure itself. Both are
   capped together below so the column edge is consistent down the pane. */
/* THE MEASURE IS PX, NOT `ch` (2026-08-13). `ch` is the width of "0" IN THE ELEMENT'S OWN FONT SIZE, so
   one shared `86ch` resolved to a DIFFERENT column per block: ~510px on this 14px description and ~452px
   on the 12px `.set-about` paragraphs directly beneath it. That is the exact failure the note above says
   it fixed — two prose blocks against the same left margin disagreeing about where they end — and it also
   left both stranded in roughly half of a ~960px pane. The PERMISSIONS block below already learned this
   (see --perm-measure and its VT323 arithmetic); this hoists the same fix to every console section so
   they finally agree. One token, one column edge down the whole pane. */
:root { --con-measure: 760px; }
.con-sec-desc {
  color: var(--ph-dim); font-size: var(--fs-1); letter-spacing: var(--ls-1);
  margin: -4px 0 12px; line-height: 1.4; max-width: var(--con-measure);
}
/* Keep every console prose block on the same readable measure as its section description. */
.con-sec-body .set-about,
.term.console .sk-note,
.term.console .ch-note,
.term.console .ch-headless { max-width: var(--con-measure); }
/* the section head is a click target only while searching (jump-to-section) */
.con-searching .con-sec-head { cursor: pointer; }
.con-searching .con-sec-head:hover .sec-l { color: var(--ph-bright); }

/* ---------- SETTINGS ▸ the shared CONTROL-GROUP SUB-LABEL ----------
   The line that names a dial and glosses it ("REACH — how far an unattended action may go"). Every
   settings pane wrote this as a `.set-row` holding one `.dim` span, i.e. the exact treatment its own
   body prose gets, so a dial's label sat at the same visual weight as the paragraph explaining it and
   the panes read as undifferentiated text. Splitting name from gloss costs one class and gives every
   section the same hierarchy. Additive: `.set-row` itself is untouched. */
.set-sub {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
  margin: 14px 0 6px; font-size: var(--fs-1);
}
.set-sub-k { color: var(--ph); letter-spacing: var(--ls-2); flex: 0 0 auto; }
.set-sub-d { color: var(--ph-dim); letter-spacing: var(--ls-1); min-width: 0; }

/* ---------- SETTINGS ▸ NIGHT SHIFT — the live status GRID ----------
   Five readouts about ONE machine (state · mode · daily limit · last job · next job). They used to be
   five bare `.set-row` blocks, which is `display:block` + a 5px margin — so a label and its value ran
   together as a sentence, five of them stacked with the explanation paragraphs interleaved, and the
   whole pane read as prose instead of a status board. Same treatment the PERMISSIONS block below got:
   scoped to the .ns-* classes only, so the shared .set-row / .key-list vocabulary is untouched.
   auto-fit keeps it honest at any window width — one column in a dragged-narrow console, three in a
   wide one — and never invents a row that isn't there. */
/* Cells carry their OWN border and the grid has none. A hairline-gap grid painted over a shared
   background looks tidy right up until the row is short: five cells in a three-wide grid leaves a sixth
   track empty, and that empty track renders the grid's own background as a solid block that reads like a
   missing readout. Separate cells simply aren't there when there is nothing to show. */
.ns-grid {
  display: grid; gap: 8px; margin: 10px 0 12px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.ns-cell {
  display: flex; flex-direction: column; gap: 4px; padding: 9px 12px;
  border: 1px solid var(--ph-faint); border-left: 3px solid var(--ph-dim);
  background: rgba(0, 0, 0, 0.28); border-radius: var(--r-sm); box-shadow: var(--raise);
}
.ns-k { font-size: 11px; letter-spacing: var(--ls-2); color: var(--ph-dim); }
/* the VALUE is the thing being read — it carries the phosphor, the label recedes. (`.dim` used to be on
   the value span, which inverted that: the fact was fainter than its own label.) */
.ns-v { font-size: 14px; color: var(--ph-bright); line-height: 1.3; word-break: break-word; }
/* the away rule leads the pane; the two explanation lines trail the grid they explain */
.set-about.ns-away { margin: 0 0 2px; }
.set-about.ns-note { margin: 0 0 6px; }
.set-about.ns-note:empty { display: none; }
/* FOCUS: the readout is a line of its own above the steer field, so the input never sits mid-sentence.
   The row is capped at the pane measure — left to fill a wide console the input stretched the full ~960px
   and stranded its own STEER button alone against the far edge, a metre from the text it acts on. */
.ns-focus-row { margin: 2px 0 8px; cursor: default; }
.ns-steer { display: flex; align-items: center; gap: 8px; cursor: default; max-width: var(--con-measure); }
.ns-steer .key-input { flex: 1 1 auto; min-width: 0; }
.ns-steer .bb { flex: 0 0 auto; }
/* ---------- SETTINGS ▸ PERMISSIONS ----------
   The pane answers three questions in order (who asks first · how far it may go unattended · what is
   already blessed), topped by the FULL BYPASS master switch. Before this block it was one undifferentiated
   column of .set-row prose: block labels rendered at body weight, and because .set-row is display:block
   the flip buttons sat INLINE after variable-length sentences — five ragged left edges, text touching the
   button. These rules are scoped to the permissions ids/classes only; the shared .set-row / .key-list
   vocabulary every other settings section uses is untouched. */
/* The prose measure. NOT expressed in `ch`: VT323's advance is only ~4.8px, so a "92ch" that reads as a
   sane 92-character measure in a proportional font resolves to 442px here — half of an 860px pane, which
   left every paragraph orphaned against a wide empty gutter (live-measured 2026-08-05). Set in px, and
   generous: the paragraphs fill the pane like every sibling settings section, and the cap only bites when
   the console is dragged very wide. */
:root { --perm-measure: 760px; }
.perm-master {
  border: 1px solid var(--ph-faint); border-left: 3px solid var(--ph-dim);
  background: rgba(0, 0, 0, 0.35); border-radius: var(--r-sm); box-shadow: var(--raise);
  padding: 10px 13px 11px; margin: 2px 0 6px;
}
/* ON is the loudest state in the product — the card says so with the alarm accent, not with more words. */
.perm-master.on { border-left-color: var(--bad); box-shadow: var(--raise), 0 0 14px rgba(var(--bad-rgb), 0.16); }
.perm-m-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.perm-m-title { font-size: 15px; color: var(--ph-bright); letter-spacing: 2px; }
.perm-m-chip {
  font-size: 10px; letter-spacing: 1px; padding: 1px 7px; border-radius: var(--r-sm);
  border: 1px solid var(--ph-dim); color: var(--ph-dim);
}
.perm-m-chip.on { border-color: var(--bad); color: var(--bad); box-shadow: 0 0 8px rgba(var(--bad-rgb), 0.28); }
.perm-m-desc { font-size: 12px; color: var(--ph-dim); line-height: 1.5; margin: 6px 0 9px; max-width: var(--perm-measure); }
.perm-m-desc code { color: var(--ph); }
.perm-m-act { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.perm-m-act .bb[disabled] { opacity: 0.5; pointer-events: none; }
/* a refused flip reports AT the switch, in the error palette the rest of the app uses for a real failure */
.perm-m-err {
  font-size: 12px; line-height: 1.4; margin: 0 0 9px; padding: 6px 8px; color: #ffb0a8;
  border: 1px solid rgba(255, 120, 100, 0.45); background: rgba(80, 10, 5, 0.18); border-radius: var(--r-sm);
}
.perm-m-floor {
  font-size: 11px; color: var(--ph-dim); line-height: 1.4; margin: 0; padding-top: 8px;
  border-top: 1px solid var(--ph-faint); max-width: var(--perm-measure); opacity: 0.85;
}
/* the per-agent APPROVAL list: name + state left, the flip button in its own right-aligned column */
.perm-list { display: flex; flex-direction: column; gap: 5px; margin: 9px 0 10px; }
.perm-agent {
  display: flex; align-items: center; gap: 10px;
  border: 1px solid var(--ph-faint); border-left: 3px solid var(--ph-dim);
  background: rgba(0, 0, 0, 0.22); border-radius: var(--r-sm); padding: 6px 11px;
}
.perm-agent.full { border-left-color: var(--gold); }
.perm-agent .pa-name { flex: 0 0 auto; min-width: 88px; color: var(--ph-bright); letter-spacing: 1px; font-size: 14px; }
/* the MODE tag carries the state as a word, in a fixed column, so the list can be read down the
   left edge without parsing five sentences (it replaced a ⚡/✋ pictograph — see paintApproval) */
.perm-agent .pa-mode {
  flex: 0 0 auto; min-width: 92px; font-size: 10px; letter-spacing: 1px;
  color: var(--ph-dim); border: 1px solid var(--ph-faint); border-radius: var(--r-sm);
  padding: 1px 6px; text-align: center;
}
.perm-agent.full .pa-mode { color: var(--gold); border-color: rgba(var(--gold-rgb), 0.45); }
.perm-agent .pa-state { flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--ph-dim); line-height: 1.35; }
.perm-agent .bb { flex: 0 0 auto; margin-left: auto; }
/* the two whole-station switches sit clear of the row list they act on */
.perm-allacts { margin-top: 10px; }
/* the lede under each numbered header: same dim body copy, but measured and spaced as a paragraph
   (.set-about alone carries a 12px top margin tuned for the bottom of a long section) */
.set-about.perm-lede { margin: 0 0 9px; max-width: var(--perm-measure); line-height: 1.5; opacity: 0.62; }
.set-about.perm-lede:empty { display: none; }
/* The STANDING APPROVALS ledger speaks the SAME row language as the APPROVAL list above, so the two
   lists read as one pane instead of "cards up top, loose prose down here". CSS-only: renderGrants keeps
   emitting .set-row + a button, and :has() picks out exactly the rows that HAVE a control — the
   "— pre-approve a capability —" separator and the teaching empty state stay plain text. */
.perm-grants { margin-top: 2px; display: flex; flex-direction: column; gap: 5px; }
.perm-grants .set-row:has(> .bb) {
  display: flex; align-items: center; gap: 10px; margin: 0;
  border: 1px solid var(--ph-faint); border-left: 3px solid var(--ph-dim);
  background: rgba(0, 0, 0, 0.22); border-radius: var(--r-sm); padding: 6px 11px;
}
.perm-grants .set-row:has(> .bb) > span:first-child { flex: 1 1 auto; min-width: 0; font-size: 13px; }
.perm-grants .set-row:has(> .bb) > .bb { flex: 0 0 auto; margin-left: auto; }
.perm-grants .set-about { margin-top: 4px; }
.perm-grants .set-row:not(:has(> .bb)) { margin: 4px 0 0; opacity: 0.75; }

/* ── PERMISSIONS · the plain-language pass (2026-08-07) ─────────────────────────────────────────────
   The pane opened onto four numbered blocks of vocabulary and two separate crew tables. Three shapes
   fix that: an AT A GLANCE card that says the posture in one sentence, ONE crew row carrying both
   per-agent axes, and a labelled question in front of each control. Scoped to the permissions ids/
   classes only — the shared .set-row / .ov-vchip vocabulary is untouched. */
.perm-glance {
  border: 1px solid var(--ph-faint); border-left: 3px solid var(--ph);
  background: rgba(0, 0, 0, 0.3); border-radius: var(--r-sm); box-shadow: var(--raise);
  padding: 10px 13px 11px; margin: 2px 0 14px;
}
/* the override being ON is the loudest fact on the pane — the glance wears the alarm accent too, so
   the summary and the switch in block 2 can never disagree at a glance. */
.perm-glance.loud { border-left-color: var(--bad); box-shadow: var(--raise), 0 0 14px rgba(var(--bad-rgb), 0.16); }
.perm-glance .pg-line { margin: 0; font-size: 15px; line-height: 1.4; color: var(--ph-bright); letter-spacing: 0.5px; max-width: var(--perm-measure); }
.perm-glance.loud .pg-line { color: var(--bad); }
.perm-glance .pg-reach { margin: 7px 0 0; font-size: 12px; line-height: 1.45; color: var(--ph-dim); max-width: var(--perm-measure); }
.perm-glance .pg-reach b { color: var(--ph); }
.perm-glance .pg-floor {
  margin: 9px 0 0; padding-top: 8px; border-top: 1px solid var(--ph-faint);
  font-size: 11px; line-height: 1.45; color: var(--ph-dim); opacity: 0.85; max-width: var(--perm-measure);
}
.perm-glance .pg-floor code { color: var(--ph); }
/* ── THE POSTURE FRONT DOOR ───────────────────────────────────────────────────────────────────────
   Three cards answering ONE question, so a newcomer never has to compose four dials. This is the only
   thing the pane asks of them; everything else lives in the closed fold below. */
.perm-postures { margin: 0 0 12px; }
.pp-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.pp-q { font-size: 13px; letter-spacing: 1px; color: var(--ph-bright); }
/* CUSTOM is the honest resting state of a hand-tuned station — dim, not an error. A matched posture
   is the only thing that lights, because a card may never claim a state it does not hold. */
.pp-state { font-size: 10px; letter-spacing: 1px; color: var(--ph-dim); opacity: 0.8; }
.pp-state.matched { color: var(--ph); opacity: 1; }
/* gap 12, not 8: these are three MUTUALLY EXCLUSIVE choices whose borders are deliberately faint, and at
   an 8px gutter across a ~960px pane they read as one banded strip rather than three things to pick between. */
.pp-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.pp-card {
  display: flex; flex-direction: column; gap: 5px; text-align: left; cursor: pointer;
  font-family: inherit; padding: 10px 12px 11px;
  border: 1px solid var(--ph-faint); border-left: 3px solid var(--ph-dim);
  background: rgba(0, 0, 0, 0.28); border-radius: var(--r-sm); box-shadow: var(--raise);
  transition: border-color .12s ease, background .12s ease;
}
.pp-card:hover { border-color: var(--ph-dim); border-left-color: var(--ph); background: rgba(0, 0, 0, 0.4); }
.pp-card.sel { border-left-color: var(--ph); background: rgba(0, 0, 0, 0.45); }
.pp-card.danger.sel, .pp-card.danger:hover { border-left-color: var(--bad); }
.pp-card.armed { border-color: var(--bad); border-left-color: var(--bad); box-shadow: var(--raise), 0 0 12px rgba(var(--bad-rgb), 0.22); }
.pp-card[disabled] { opacity: 0.45; pointer-events: none; }
.pp-name { font-size: 14px; letter-spacing: 1.5px; color: var(--ph-bright); }
.pp-card.sel .pp-name { color: var(--ph); }
.pp-card.danger .pp-name { color: var(--bad); }
.pp-blurb { font-size: 12px; line-height: 1.4; color: var(--ph-dim); }
.pp-who { font-size: 10px; letter-spacing: 1px; color: var(--ph-dim); opacity: 0.7; }
.pp-foot { margin: 8px 0 0; font-size: 11px; color: var(--ph-dim); opacity: 0.8; }
/* the hairline between tier 1 (station-wide) and tier 2 (per-agent): the two are the same question
   at two scopes, so they need a seam, not a heading, to say "and now the exceptions". */
.perm-tier-rule { border-top: 1px solid var(--ph-faint); margin: 14px 0 2px; }
/* tier 3: real controls, rarely touched. ONE fold — never a fold inside a fold. */
.perm-fold { margin: 14px 0 0; border-top: 1px solid var(--ph-faint); padding-top: 10px; }
.perm-fold > summary {
  cursor: pointer; font-size: 12px; letter-spacing: 1px; color: var(--ph-dim);
  list-style: none; padding: 4px 0;
}
.perm-fold > summary::-webkit-details-marker { display: none; }
.perm-fold > summary::before { content: '▸ '; color: var(--ph); }
.perm-fold[open] > summary::before { content: '▾ '; }
.perm-fold > summary:hover { color: var(--ph-bright); }
/* the reach chip carries the plain meaning big and the house name small underneath */
.pc-reach-chip { align-items: flex-start !important; }
.pc-rl { display: flex; flex-direction: column; gap: 1px; line-height: 1.15; text-align: left; }
.pc-rh { font-size: 9px; letter-spacing: 1px; opacity: 0.55; font-style: normal; }
/* the merged crew row: a header line, then one labelled block per axis. Stacked, not columnar — each
   axis owns a chip strip AND a plain sentence, which cannot share a row honestly at this width. */
/* THE ROW IS A <details> (2026-08-14). Collapsed it is ONE line that still answers the question — who,
   what mode, what it can reach — and the axis controls only exist once you ask for them. Measured: the
   pane was 4454px at ten agents (7.4 screens, 90% of it this block); collapsed rows put it back near one
   screen. Opening is for CHANGING; the list READS without opening anything. */
.perm-crew-row { display: block; padding: 9px 12px 10px; }
.perm-crew-row .pc-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  cursor: pointer; list-style: none; user-select: none;
}
/* both marker syntaxes — WebKit needs the pseudo-element, the modern one needs `display` */
.perm-crew-row .pc-head::-webkit-details-marker { display: none; }
.perm-crew-row .pc-head::marker { content: ''; }
.perm-crew-row .pc-head:hover .pa-name,
.perm-crew-row[open] .pc-head .pa-name { color: var(--ph-bright); }
/* A FIXED NAME COLUMN. Once the rows are one line each they are a LIST, and a list whose second column
   starts at a different x on every row is not one — with `min-width` alone every name longer than the
   floor pushed its own mode chip right, so NOVA / ATLAS / ORACLE / VESPER staggered their chips across
   ~20px. Fixed basis + ellipsis: long names truncate rather than shove the row out of alignment, and the
   full name is still the first thing in the opened row. */
.perm-crew-row .pc-head .pa-name {
  flex: 0 0 96px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* the summary line must not WRAP at a readable roster width, or "compact" is a lie — the state clause
   ellipses instead, and the whole sentence is still there the moment the row opens. */
.perm-crew-row .pc-head .pa-state {
  flex: 1 1 120px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.perm-crew-row .pc-caret {
  flex: 0 0 auto; margin-left: auto; color: var(--ph-dim); font-size: 12px;
  transition: transform .12s ease, color .12s ease;
}
.perm-crew-row[open] .pc-caret { transform: rotate(90deg); color: var(--ph); }
.perm-crew-row .pc-head:hover .pc-caret { color: var(--ph); }
/* a collapsed list is a LIST — tighten the rows so ten of them read as one block, not ten cards */
.perm-crew-row:not([open]) { padding: 7px 12px; }
/* the first axis inside an OPEN row already carries the divider; kill the doubled gap under the summary */
.perm-crew-row[open] .pc-head { margin-bottom: 1px; }
.perm-crew-row .pc-axis { margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--ph-faint); }
/* the question in front of the control — the pane's whole teaching move is that every control is
   preceded by the question it answers, in the label voice the rest of settings uses. */
.perm-crew-row .pc-q {
  display: block; font-size: 10px; letter-spacing: 2px; color: var(--ph); opacity: 0.75; margin-bottom: 5px;
}
/* the block-2 override outranks this axis: the question says so, and the chips read as the setting
   that is currently NOT in force — dimmed, but still legible and still clickable (they select what
   the agent goes back to). The row's own accent is already the effective-posture colour. */
.perm-crew-row .pc-ovr { color: var(--bad); letter-spacing: 1px; }
.perm-crew-row.overridden .pc-ask-axis .pc-chips { opacity: 0.55; }
.perm-crew-row.overridden .pc-ask-axis .pc-chips:hover { opacity: 1; }
.perm-crew-row .pc-chips { margin: 0; gap: 5px; flex-wrap: wrap; }
.perm-crew-row .pc-chips .ov-vchip { display: inline-flex; align-items: center; gap: 6px; }
/* THE REACH LADDER IS A GRID, not a wrap. Five rungs of very different label lengths ("NOTHING OF MINE"
   … "MY WHOLE COMPUTER") flex-wrapped into 4 + 1, so the widest chip set the break and the fifth rung
   sat alone beside a hole. Equal tracks give the ladder one left edge, align every reach meter into a
   readable column, and break 3 + 2 instead of stranding a rung. */
.perm-crew-row .pc-reach-chips {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  gap: 6px; align-items: stretch;
}
.perm-crew-row .pc-reach-chips .ov-vchip { width: 100%; justify-content: flex-start; text-align: left; }
/* the reach meter: filled rungs in phosphor, hollow ones faded, so the ladder reads left-to-right
   without parsing the labels. Fixed advance (VT323 is monospace) keeps the chips' text aligned. */
.pc-dots { font-size: 11px; letter-spacing: 1px; opacity: 0.5; }
.ov-vchip.sel .pc-dots { opacity: 0.9; }
.perm-crew-row .pc-plain { margin: 6px 0 0; font-size: 12px; line-height: 1.45; color: var(--ph-dim); max-width: var(--perm-measure); }
/* the routing truth line stays visible (truthful telemetry) but demoted below the plain sentence —
   it answers "is this actually happening", which is the second question, never the first. */
.perm-crew-row .pc-truth { margin-top: 5px; opacity: 0.6; }
.perm-crew-row .pc-truth b { color: var(--ph); }
/* SANDBOX UNAVAILABLE chip — the router refused (or, under allow, is running on the host) because the requested
   sandbox is not there. Red inset chip in the house chip language; the fix line sits on its own row under it. */
.sandbox-chip { display: inline-block; font-family: inherit; font-size: 11px; letter-spacing: 1px; color: var(--bad); border: 1px solid var(--bad); padding: 1px 6px; margin: 0 8px 3px 0; background: rgba(255,90,90,.08); box-shadow: inset 0 0 6px rgba(255,90,90,.18); opacity: 1; }
.sandbox-chip-fix { display: block; color: var(--bad); opacity: .9; margin: 0 0 4px; }
.perm-crew-row .pc-truth .sandbox-chip, .perm-crew-row .pc-truth .sandbox-chip-fix { opacity: 1; }
.perm-crew-row .pc-more { margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--ph-faint); }
.perm-crew-row .pc-more .mc-acts { margin: 0 0 6px; }
.perm-crew-row .pc-more .mc-acts:last-child { margin-bottom: 0; }
.perm-crew-row .exec-ssh { margin: 0; }
/* the fold closes the pane, so it gets a rule above it — otherwise it floats loose under the ledger */
.perm-adv { margin-top: 18px; padding-top: 12px; border-top: 1px solid var(--ph-faint); }
.perm-adv .perm-policy-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.perm-adv .perm-policy-row .key-input { width: 90px; flex: 0 0 auto; }

/* while searching: every section is shown, non-matching rows fade, empty sections collapse their body */
.con-searching .con-miss { display: none !important; }
.con-searching .cc-group:not(:has(.cc-card.con-hit:not([hidden]))) { display:none; }
.con-searching .cc-service:not(:has(.cc-card.con-hit:not([hidden]))) { display:none; }
.con-searching .cc-alternatives:not(:has(.cc-card.con-hit:not([hidden]))) { display:none; }
.con-searching .cc-group > .sec .sec-tag { display:none; }
.con-searching .sk-state-sec, .con-searching .sk-lib-sum { display:none; }
.cc-alternatives { margin-top:5px; padding:8px; border:1px solid var(--ph-dim); }
.cc-alternatives:not(:has(.cc-card:not([hidden]))) { display:none; }
.cc-alternatives summary { cursor:pointer; }
.con-searching .con-sec-nomatch { display: none; }
.con-searching .con-sec + .con-sec { margin-top: 18px; }
.con-search-empty { margin: 28px 20px; padding: 18px; border: 1px dashed var(--ph-dim); color: var(--ph-bright); text-align: center; letter-spacing: .6px; }

/* responsive: on a narrow viewport the rail sits on top as a horizontal strip */
@media (max-width: 720px) {
  /* both WIDE shells collapse to the same near-full-bleed width here — a `.wide` window left at
     88vw beside a console at 94vw would reintroduce the size drift this pass removed. */
  .term { --term-w-wide: calc(94vw * var(--sn-unzoom, 1)); }
  .term.console { --con-w: calc(94vw * var(--sn-unzoom, 1)); --con-h: var(--term-band, calc(84vh * var(--sn-unzoom, 1))); }
  .term.console .term-body.term-console-body { flex-direction: column; }
  .con-rail { flex: 0 0 auto; border-right: none; border-bottom: 1px solid var(--ph-dim); }
  .con-rail-list { flex-direction: row; flex-wrap: wrap; overflow-x: auto; }
  .con-rail-item.active { box-shadow: 0 0 12px var(--ph-glow2), inset 0 3px 0 var(--ph-bright); }
}

/* ============================================================
   F1 · FORM-CONTROL FOUNDATION (finale polish sprint 2026-07-09)
   One skin for every native <select>; no OS spinners on number
   fields; one global disabled treatment. The chevron is drawn
   with currentColor gradients (never an SVG data-uri) so it
   re-tints with every theme. The OPEN option list stays OS-native
   (unstyleable) — set what browsers honour on option/optgroup.
   `html select` boosts specificity above the legacy per-surface
   skins whose `background:` shorthands would wipe the chevron.
   .comms-agent-select keeps its own themed ▾ (excluded here).
   ============================================================ */
html select:not(.comms-agent-select) {
  -webkit-appearance: none; appearance: none;
  font-family: inherit; cursor: pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 13px top 55%, right 8px top 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 24px;
}
html select option, html select optgroup {
  color: var(--text); background: var(--panel2, #0a0a08); font-style: normal;
}
/* kill the OS number spinners app-wide — every numeric field here is a small
   typed integer (budget caps, timeouts, cooldowns); the spinner is pure chrome */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
input[type=number] { -moz-appearance: textfield; }
/* one global disabled voice — surfaces may deepen it, never brighten it */
button:disabled, select:disabled, input:disabled, textarea:disabled {
  opacity: 0.45; cursor: not-allowed;
}

/* OUTBOX — FINISHED WORK window (accordion, 2026-07-16 round 4). Collapsed row = title + what the
   agent actually did + a dim meta line, nothing else; the whole head is the toggle. Expanded body
   holds the ask, the full output (scrolls inside itself), and the one action row. */
.ob-list { display: flex; flex-direction: column; gap: 8px; }
.ob-row { border: 1px solid var(--ph-faint); border-radius: var(--r-sm); background: var(--panel2); box-shadow: var(--raise); overflow: hidden; transition: border-color 0.15s; }
.ob-row.open { border-color: var(--ph-dim); box-shadow: var(--raise), inset 0 0 14px var(--ph-glow2); }
.ob-head { padding: 9px 12px; cursor: pointer; }
.ob-head:hover, .ob-head:focus-visible { background: rgba(255, 255, 255, 0.03); outline: none; }
.ob-title { color: var(--ph); letter-spacing: 0.5px; display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.ob-title b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
.ob-caret { color: var(--ph-dim); flex: none; }
.ob-desc { margin-top: 3px; font-size: 12px; line-height: 1.45; color: var(--text); opacity: 0.78; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.ob-meta { margin-top: 5px; font-size: 10.5px; color: var(--ph-dim); letter-spacing: 1px; }
.ob-body { border-top: 1px solid var(--ph-faint); padding: 10px 12px 12px; }
.ob-sec { font-size: 10px; letter-spacing: 2px; color: var(--ph-dim); margin: 10px 0 4px; }
.ob-sec:first-child { margin-top: 0; }
.ob-ask, .ob-out { white-space: pre-wrap; word-break: break-word; max-height: 240px; overflow: auto; font-size: 12px; color: var(--text); }
/* dim helper copy inside windows — NOT .mc-detail (that's the CONNECTORS error-detail class, alarm-red) */
.win-note { font-size: 12px; color: var(--ph-dim); line-height: 1.45; }
.ob-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-top: 12px; }


/* ---- LOOPS window (windows/loops.js) — shape picker, live stepper, review cards -------------------------
   The stepper is the "presentation" the loop system was missing: a loop must READ as a cycle you can see
   yourself in, not as a spinner with a status word. Only the stage the server can prove lights up. */
.lp-shapes { display: grid; grid-template-columns: 1fr; gap: 6px; margin: 4px 0 10px; }
.lp-shape {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto auto; gap: 1px 10px;
  align-items: center; text-align: left; padding: 9px 11px; cursor: pointer;
  background: var(--panel-2, rgba(0,0,0,.25)); border: 1px solid var(--ph-faint); border-left: 3px solid transparent;
  color: var(--ph-dim); font-family: inherit; font-size: 12px; transition: border-color .12s, background .12s;
}
.lp-shape:hover { border-color: var(--ph); background: var(--ph-faint); }
.lp-shape.on { border-color: var(--ph); border-left-color: var(--gold); background: var(--ph-faint); color: var(--ph-bright); }
.lp-shape-e { grid-row: 1 / span 3; font-size: 20px; color: var(--gold); opacity: .9; }
.lp-shape-n { font-weight: 700; letter-spacing: .04em; color: var(--ph-bright); }
.lp-shape-t { font-size: 11.5px; color: var(--ph-dim); }
/* RIGOR BADGE — a hard exit condition and a self-reported one must not look alike. */
.lp-shape-r { font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase; opacity: .95; }
.lp-shape-r.hard { color: var(--gold); }
.lp-shape-r.soft { color: var(--ph-dim); }

.lp-cycle { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin: 2px 0 6px; font-size: 11px; }
.lp-step { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin: 5px 0 3px; }
.lp-step-i {
  font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; padding: 2px 7px;
  border: 1px solid var(--ph-faint); color: var(--ph-dim); background: rgba(0,0,0,.2);
}
.lp-step-i.on { border-color: var(--gold); color: var(--gold); background: var(--gold-faint, rgba(255,200,80,.08)); }
.lp-step-sep { color: var(--ph-faint); font-size: 11px; }

/* a candidate awaiting a verdict — the review gate, and the whole point of the panel */
.lp-pends { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 4px; }
.lp-pend { padding: 8px 10px; border: 1px solid var(--ph-faint); border-left: 3px solid var(--gold); background: rgba(0,0,0,.22); }
.lp-pend-h { font-size: 12px; color: var(--ph-bright); }
.lp-pend-b { font-size: 11.5px; color: var(--ph-dim); margin-top: 4px; line-height: 1.45; white-space: pre-wrap; }

.mc-lbl { display: block; font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--ph-dim); margin: 6px 0 2px; }
.mc-lbl .key-input, .mc-lbl textarea { margin-top: 3px; text-transform: none; letter-spacing: normal; }
.lp-dir { display: flex; gap: 6px; align-items: center; }
.lp-dir .key-input { flex: 1 1 auto; }
.lp-agent-pick { display: flex; flex-wrap: wrap; gap: 5px; margin: 8px 0 4px; }
@media (min-width: 560px) { .lp-shapes { grid-template-columns: 1fr 1fr; } }

/* the inline rejection-reason panel (replaced a native window.prompt) */
.lp-why { margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--ph-faint); }
.lp-why-in { width: 100%; }
.lp-why-cost { font-size: 11px; color: var(--bad); }
/* the dock badge — how many loop results are waiting on the Commander */
.bb { position: relative; }
.lp-badge {
  position: absolute; top: 4px; right: 6px; min-width: 15px; height: 15px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; line-height: 1; border-radius: 8px;
  background: var(--gold); color: #0a0a0a; box-shadow: 0 0 6px rgba(255,200,80,.5);
}

/* changed-file list on a review card, and the pre-flight objective preview */
.lp-files { font-size: 11px; color: var(--ph-dim); margin-top: 5px; line-height: 1.7; }
.lp-files code { background: rgba(0,0,0,.3); border: 1px solid var(--ph-faint); padding: 1px 5px; margin-right: 3px; }
.lp-preview { margin: 8px 0 6px; font-size: 11.5px; }
.lp-preview summary { cursor: pointer; color: var(--ph-dim); letter-spacing: .04em; }
.lp-preview summary:hover { color: var(--ph-bright); }
.lp-preview pre {
  margin: 6px 0 0; padding: 8px 10px; max-height: 220px; overflow: auto;
  background: rgba(0,0,0,.3); border: 1px solid var(--ph-faint); border-left: 3px solid var(--ph-faint);
  color: var(--ph-dim); font-size: 11px; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
}

/* the review diff — the change itself, not a description of it */
.lp-diff { margin-top: 6px; }
.lp-diff summary { cursor: pointer; font-size: 11px; letter-spacing: .04em; color: var(--gold); opacity: .9; }
.lp-diff summary:hover { opacity: 1; }
.lp-diff-note { font-size: 10.5px; margin: 4px 0 3px; }
.lp-diff-body {
  margin: 0; padding: 8px 10px; max-height: 320px; overflow: auto;
  background: rgba(0,0,0,.42); border: 1px solid var(--ph-faint); border-left: 3px solid var(--gold);
  font-size: 11px; line-height: 1.45; white-space: pre; tab-size: 2;
}
.lp-diff-body .d-add  { color: #7bc88a; display: block; }
.lp-diff-body .d-del  { color: #e0736d; display: block; }
.lp-diff-body .d-hunk { color: var(--ph); opacity: .75; display: block; }
.lp-diff-body .d-file { color: var(--ph-bright); display: block; }
.lp-diff-body .d-ctx  { color: var(--ph-dim); display: block; }

/* ---- the stepped START A LOOP flow: one decision at a time ---- */
.lp-wiz { display: flex; flex-direction: column; gap: 4px; }
.lp-step-h {
  display: flex; align-items: center; gap: 8px; margin: 12px 0 6px;
  font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--ph-bright);
}
.lp-wiz > .lp-step-h:first-child { margin-top: 2px; }
.lp-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; flex: 0 0 18px;
  background: var(--gold); color: #0a0a0a; font-size: 11px; font-weight: 700; line-height: 1;
}
.lp-stage { border-top: 1px dashed var(--ph-faint); padding-top: 2px; animation: lpReveal .18s ease-out; }
@keyframes lpReveal { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }


/* the check TEST readout — the one field that used to have no feedback at all */
.lp-testout { font-size: 11px; line-height: 1.5; margin-top: 4px; }

/* step 3: what will actually happen, in plain English */
.lp-ready { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 10px; }
.lp-ready-r { display: grid; grid-template-columns: 92px 1fr; gap: 10px; align-items: baseline; font-size: 11.5px; }
.lp-ready-r b { color: var(--ph-dim); font-weight: 600; font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase; }
.lp-ready-r span { color: var(--ph-bright); line-height: 1.5; }
.lp-go { width: 100%; }

.lp-adv { margin: 10px 0 4px; }
.lp-adv > summary { cursor: pointer; font-size: 11px; letter-spacing: .04em; color: var(--ph-dim); }
.lp-adv > summary:hover { color: var(--ph-bright); }

/* the loop row: what you asked for, and what it has done */
.lp-ask { font-size: 11.5px; color: var(--ph-bright); opacity: .9; margin: 3px 0 1px; line-height: 1.45; }
.lp-hist { margin-top: 6px; }
.lp-hist > summary { cursor: pointer; font-size: 11px; color: var(--ph-dim); letter-spacing: .04em; }
.lp-hist > summary:hover { color: var(--ph-bright); }
.lp-hist-r { display: grid; grid-template-columns: 26px 14px 1fr; gap: 6px; align-items: baseline; font-size: 11px; padding: 2px 0; }
.lp-hist-n { color: var(--ph-faint); }
.lp-hist-v { text-align: center; }
.lp-hist-t { color: var(--ph-dim); line-height: 1.4; }
.lp-hist-why { grid-column: 3; margin: 0 0 4px 46px; font-size: 10.5px; color: var(--bad); opacity: .85; line-height: 1.4; }

/* the check line: a stated fact, not a question, when we could detect it */
.lp-checkline { font-size: 11.5px; color: var(--ph-bright); line-height: 1.6; margin: 8px 0 4px; }
.lp-checkline code { background: rgba(0,0,0,.35); border: 1px solid var(--ph-faint); padding: 1px 6px; color: var(--gold); }
.lp-link { font-family: inherit; font-size: 11px; color: var(--ph-dim); background: none; border: 0; border-bottom: 1px dotted var(--ph-faint); cursor: pointer; padding: 0; margin-left: 4px; }
.lp-link:hover { color: var(--ph-bright); border-bottom-color: var(--ph); }
/* (`.exec-policy` retired — the idle-cell policy is no longer a row in the crew list; it is the body
   of the ADVANCED fold at the foot of the pane, styled by .perm-adv above.) */
/* The crew row STACKS (header → axis → axis → advanced): each axis owns a chip strip AND a plain
   sentence, which no 3-column grid can carry honestly. The old grid packed name/mode/state across
   three columns and dropped every control onto a full-bleed row; the flat `display:flex` inherited
   from .perm-agent would otherwise put the axes side by side, so the block wins explicitly here at
   matching specificity. Its own children keep the full-width lane the SSH disclosure relies on. */
.perm-agent[data-profile-agent]{display:block}
.perm-agent[data-profile-agent]>.pc-axis,.perm-agent[data-profile-agent]>.pc-more,.perm-agent[data-profile-agent] .ov-vchips,.perm-agent[data-profile-agent] .mc-acts,.perm-agent[data-profile-agent] .exec-ssh{min-width:0}
.perm-agent[data-profile-agent] .mc-acts .bb,.perm-agent[data-profile-agent] .exec-ssh .bb{margin-left:0}
.exec-ssh{width:100%;box-sizing:border-box;margin-top:8px;padding-top:5px;border-top:1px dashed color-mix(in srgb,var(--ph) 28%,transparent)}
.exec-ssh .set-row{display:grid;grid-template-columns:minmax(100px,130px) minmax(0,1fr);gap:8px;align-items:center}
.exec-ssh .key-input{width:100%;min-width:0}
.exec-ssh .mc-acts{display:flex;flex-wrap:wrap;gap:6px;margin-top:7px}

/* ============== DELIVERABLES — the organized library (2026-08-13) ==============
   Two bands per card, and the whole visual grammar exists to keep them apart:
     .dlv-say   — the AGENT'S prose (title + one line). Reads like writing.
     .dlv-facts — HARNESS truth (status pill, crew chips, counts). Reads like instrumentation.
   A person must be able to tell at a glance which half the station is standing behind, so the say band gets
   normal text colour and the facts band gets chips/rails. Matte throughout: recessed fills and 1px structure,
   no gloss, and no glow standing in for an edge. */
.dlv-split { display: grid; grid-template-columns: minmax(132px, 168px) minmax(0, 1fr); gap: 12px; align-items: start; }
@media (max-width: 640px) { .dlv-split { grid-template-columns: minmax(0, 1fr); } }

/* the PROJECT rail — the axis a person actually thinks in */
.dlv-rail { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.dlv-proj { display: flex; align-items: baseline; gap: 6px; width: 100%; padding: 6px 8px; text-align: left;
  background: transparent; border: 1px solid transparent; border-left: 2px solid var(--ph-faint); border-radius: 0 var(--r-sm) var(--r-sm) 0;
  color: var(--text); font: inherit; font-size: 12px; cursor: pointer; min-width: 0; }
.dlv-proj:hover { background: rgba(255, 255, 255, 0.03); border-left-color: var(--ph-dim); }
.dlv-proj:focus-visible { outline: 1px solid var(--ph); outline-offset: -1px; }
.dlv-proj.on { background: var(--ph-faint); border-left-color: var(--ph); color: var(--ph); }
.dlv-proj-n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dlv-proj-c { flex: none; font-size: 10.5px; color: var(--ph-dim); font-variant-numeric: tabular-nums; }
.dlv-proj.on .dlv-proj-c { color: var(--ph); }
/* a revoked root is SHOWN, marked — it names work that really happened */
.dlv-proj-rev { flex-basis: 100%; font-size: 9.5px; letter-spacing: 0.5px; color: var(--bad); opacity: 0.85; }

/* time buckets */
.dlv-main { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.dlv-bucket { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.dlv-bucket h4 { margin: 0; font-size: 10px; letter-spacing: 2px; color: var(--ph-dim); font-weight: 500;
  display: flex; align-items: center; gap: 8px; }
.dlv-bucket h4::after { content: ''; flex: 1; height: 1px; background: var(--ph-faint); }

/* the card */
.dlv-card { border: 1px solid var(--ph-faint); border-radius: var(--r-sm); background: var(--panel2);
  box-shadow: var(--raise); min-width: 0; overflow: hidden; transition: border-color 0.15s; }
.dlv-card:has(.dlv-head.on) { border-color: var(--ph-dim); box-shadow: var(--raise), inset 0 0 14px var(--ph-glow2); }
/* THE GLANCE — a real button so the whole row is one keyboard-reachable target */
.dlv-head { display: flex; align-items: flex-start; gap: 8px; width: 100%; text-align: left; padding: 10px 12px;
  background: transparent; border: 0; color: inherit; font: inherit; cursor: pointer; min-width: 0; }
.dlv-head:hover, .dlv-head:focus-visible { background: rgba(255, 255, 255, 0.03); outline: none; }
.dlv-head:focus-visible { box-shadow: inset 0 0 0 1px var(--ph); }
.dlv-caret { flex: none; color: var(--ph-dim); font-size: 11px; line-height: 1.6; transition: transform 0.15s; }
.dlv-head.on .dlv-caret { transform: rotate(90deg); color: var(--ph); }
.dlv-headmain { display: flex; flex-direction: column; gap: 5px; min-width: 0; flex: 1; }
.dlv-say { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.dlv-title { color: var(--ph); font-weight: 600; letter-spacing: 0.4px; overflow-wrap: anywhere; }
/* an UNNAMED row's title is a filename, not a description — say it in the file voice, not the prose voice */
.dlv-title.plain { color: var(--text); font-weight: 500; font-size: 12.5px; letter-spacing: 0; opacity: 0.9; }
.dlv-sum { margin: 0; font-size: 12px; line-height: 1.45; color: var(--text); opacity: 0.82; overflow-wrap: anywhere; }
.dlv-sum.none { opacity: 0.45; font-style: italic; }

/* the facts band — instrumentation, never prose */
.dlv-facts { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 8px; min-width: 0; }
.dlv-pill { font-size: 9.5px; letter-spacing: 1.4px; padding: 2px 7px; border-radius: 2px; flex: none;
  border: 1px solid currentColor; }
.dlv-pill.ok { color: var(--ok); }
.dlv-pill.warn { color: var(--gold); background: rgba(var(--gold-rgb), 0.09); }
.dlv-pill.bad { color: var(--bad); background: rgba(var(--bad-rgb), 0.09); }
.dlv-pill.off { color: var(--ph-dim); }
.dlv-crew { display: inline-flex; flex-wrap: wrap; gap: 4px; min-width: 0; }
/* LEAD reads as an inverted phosphor chip (the COMMS speaker vocabulary); workers stay outlined and dim, so
   the hierarchy is legible without a legend and a single-agent card costs nothing. */
.dlv-who { font-size: 10px; letter-spacing: 0.8px; padding: 1px 6px; border-radius: 2px;
  border: 1px solid var(--ph-faint); color: var(--ph-dim); }
.dlv-who.lead { background: var(--ph); color: var(--panel2); border-color: var(--ph); font-weight: 600; }
.dlv-who i { font-style: normal; opacity: 0.7; }
.dlv-meta { font-size: 10.5px; color: var(--ph-dim); letter-spacing: 0.6px; font-variant-numeric: tabular-nums; }

.dlv-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }
/* the ONE file that IS the deliverable, when the agent named one */
.dlv-hero { border-color: var(--gold); color: var(--gold); }

.dlv-empty { border: 1px dashed var(--ph-faint); border-radius: var(--r-sm); padding: 18px 14px; text-align: center;
  font-size: 11px; letter-spacing: 1.6px; color: var(--ph-dim); }
.dlv-empty span { display: block; margin-top: 7px; font-size: 12px; letter-spacing: 0; line-height: 1.5;
  color: var(--text); opacity: 0.6; }

/* ---- DELIVERABLES: the wide shell, the summary strip, the kind chips, and the details drawer ---- */
/* (the steady shell this window needs — a card's drawer must not re-centre the whole box and slide the row
   out from under the pointer — is now `.term.console`'s own height; no per-window override left to keep.) */

/* the one-line answer to "what have I been working on" */
.dlv-head-strip { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 10px; margin-bottom: 9px; border: 1px solid var(--ph-faint); border-radius: var(--r-sm);
  background: var(--ph-faint); }
.dlv-sumline { font-size: 12px; color: var(--text); opacity: 0.8; letter-spacing: 0.3px; }
.dlv-sumline b { color: var(--ph); font-weight: 600; font-variant-numeric: tabular-nums; }
/* the only count that is also an ACTION — it routes to the rows actually waiting on the user */
.dlv-needs { font: inherit; font-size: 11px; letter-spacing: 0.6px; padding: 3px 9px; cursor: pointer;
  background: rgba(var(--gold-rgb), 0.1); color: var(--gold); border: 1px solid var(--gold); border-radius: 2px; }
.dlv-needs:hover { background: rgba(var(--gold-rgb), 0.2); }
.dlv-needs:focus-visible { outline: 1px solid var(--gold); outline-offset: 2px; }

/* KIND chips — the second axis */
.dlv-kinds { display: flex; flex-wrap: wrap; gap: 5px; margin: 8px 0 4px; }
.dlv-chip { display: inline-flex; align-items: baseline; gap: 5px; font: inherit; font-size: 10.5px;
  letter-spacing: 1px; text-transform: uppercase; padding: 3px 8px; cursor: pointer; border-radius: 2px;
  background: transparent; color: var(--ph-dim); border: 1px solid var(--ph-faint); }
.dlv-chip:hover { border-color: var(--ph-dim); color: var(--text); }
.dlv-chip:focus-visible { outline: 1px solid var(--ph); outline-offset: 1px; }
.dlv-chip.on { background: var(--ph-faint); border-color: var(--ph); color: var(--ph); }
.dlv-chip span { font-size: 9.5px; opacity: 0.7; font-variant-numeric: tabular-nums; }

/* THE DRAWER — everything that needs reading or deciding, one click down */
.dlv-more { border-top: 1px solid var(--ph-faint); padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 11px; }
.dlv-more[hidden] { display: none; }
.dlv-sec { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.dlv-sec h5 { margin: 0; font-size: 9.5px; letter-spacing: 2px; color: var(--ph-dim); font-weight: 500; }
/* the Commander's own words — quoted, never paraphrased */
.dlv-quote { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text); opacity: 0.85;
  border-left: 2px solid var(--ph-faint); padding-left: 9px; overflow-wrap: anywhere; }
/* the agent's REAL recorded closing message; scrolls in place rather than pushing the actions off-screen */
.dlv-said { font-size: 12px; line-height: 1.5; color: var(--text); opacity: 0.85; white-space: pre-wrap;
  word-break: break-word; max-height: 190px; overflow: auto; padding-right: 4px;
  scrollbar-width: thin; scrollbar-color: var(--ph-dim) transparent; }

/* the file list — one row per artifact, each openable on its own */
.dlv-files { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.dlv-files li { display: flex; align-items: center; gap: 8px; min-width: 0; }
.dlv-fname { flex: 1; min-width: 0; font-size: 12px; color: var(--text); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.dlv-fname.off { opacity: 0.45; text-decoration: line-through; }
.dlv-fsize { flex: none; font-size: 10.5px; color: var(--ph-dim); font-variant-numeric: tabular-nums; }
.dlv-fmain { flex: none; font-size: 9.5px; letter-spacing: 1px; color: var(--gold); }

.dlv-none { margin: 0; font-size: 12px; color: var(--text); opacity: 0.55; }

/* ===== BOOT GUARD — the fatal-boot banner (frontend/app/bootguard.js) =====
   Rendered ONLY when a critical module global is missing or a <script> failed to load. Same matte CRT card
   vocabulary as the gate screens (.panel bezel, .ov-recovery gold inset, .btn/.btn-xl hardware); the wrapper's
   positioning/backdrop are ALSO set inline by the module so the banner survives app.css itself failing. */
.bgf { color: var(--ph-bright); }
.bgf-card {
  width: min(760px, 94%); max-height: 92%; overflow-y: auto; padding: 18px 22px 16px;
  background: radial-gradient(ellipse 130% 100% at 50% 0%, var(--ph-glow2), transparent 42%), var(--panel);
  border: 1px solid rgba(0,0,0,.85); border-radius: var(--r-lg); box-shadow: var(--bezel);
}
.bgf-head { display: inline-block; color: var(--ink); background: var(--ph); padding: 2px 12px; letter-spacing: 2px; font-size: 22px; }
.bgf-lead { margin-top: 12px; font-size: 17px; letter-spacing: .3px; color: var(--text); text-shadow: 0 0 6px var(--ph-glow2); }
.bgf-list {
  margin: 12px 0 0; padding: 9px 14px 9px 30px; font-size: 17px; color: var(--ph-bright);
  border: 1px solid var(--gold); border-left: 4px solid var(--gold); border-radius: 3px;
  background: rgba(var(--ph-rgb),.07); box-shadow: inset 0 0 22px rgba(0,0,0,.5), 0 0 12px var(--ph-glow2);
}
.bgf-list li { margin: 2px 0; }
.bgf-pre {
  margin: 12px 0 0; padding: 8px 10px; max-height: 180px; overflow: auto; font: 14px/1.3 VT323, monospace;
  color: var(--ph-dim); background: rgba(0,0,0,.45); border: 1px solid var(--ph-faint); border-radius: 3px;
  white-space: pre-wrap; word-break: break-word; user-select: text;
}
.bgf-actions { display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 14px; flex-wrap: wrap; }

/* Provisional recognition may still change; confirmed words retain normal contrast. */
.lv-provisional { opacity: .62; }

/* Intent navigation and grouped history preserve the same station controls at every depth. */
.con-intents { display: grid; gap: 6px; padding: 8px; border-bottom: 1px solid var(--ph-faint); }
.con-intent { text-align: left; padding: 10px 8px; white-space: normal; }
.con-intent[aria-pressed="true"], .ws-kind-filter button[aria-pressed="true"] { color: var(--ph-bright); background: rgba(var(--ph-rgb), .12); border-color: var(--ph); }
.con-rail-item[hidden] { display: none !important; }
.con-intents + .con-rail-list { padding-left: 10px; }
.ws-kind-filter { display: flex; gap: 3px; margin: 5px 0; }
.ws-kind-filter button { flex: 1; min-width: 0; padding: 5px 2px; font-size: 11px; }
.ws-auto-group { list-style: none; margin-top: 8px; }
.ws-auto-group button { display: grid; width: 100%; min-width: 0; text-align: left; padding: 7px 8px; border: 1px solid var(--ph-faint); }
.ws-auto-group span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-auto-group small { color: var(--ph-dim); font-size: 10px; }
.ag-effective { margin: 8px 0; }
.ag-effective .perk-desc { white-space: normal; }
.cf-group-summary { overflow-wrap: anywhere; }

/* Life-goal controls use station paint and remain usable in narrow quest windows. */
.gx-quests .q-life-goal, .gx-quests .q-life-quest { margin-top: 12px; min-width: 0; }
.gx-quests .q-life-goal details, .gx-quests .q-life-quest details, .gx-quests .q-life-report { margin: 8px 0; border-top: 1px solid var(--ph-faint); padding-top: 8px; }
.gx-quests .q-life-goal label, .gx-quests .q-life-quest label, .gx-quests .q-life-report label { display: block; margin: 8px 0; color: var(--ph-dim); }
.gx-quests .q-life-goal textarea, .gx-quests .q-life-goal input, .gx-quests .q-life-quest textarea, .gx-quests .q-life-quest input, .gx-quests .q-life-report textarea { display: block; box-sizing: border-box; width: 100%; margin: 6px 0; padding: 8px; }
.gx-quests .q-life-goal textarea, .gx-quests .q-life-report textarea, .gx-quests .q-life-quest textarea { min-height: 68px; resize: vertical; }
.gx-quests .q-commander-growth { padding-bottom: 12px; margin-bottom: 12px; border-bottom: 1px solid var(--ph-faint); }
.gx-quests .q-achievement, .gx-quests .q-plan-complete { padding: 6px 0; }
.gx-quests .q-deferred { margin: 12px 0; }
.gx-quests .q-life-quest .consent-btns { flex-wrap: wrap; }
