/* Coverage cockpit — terminal theme, v3 (2026-08-19 redesign).
   Dark only, all-mono, by owner decision. Same LAYOUT as v2 — this is a retheme, not a
   restructure: every class name and every token name is unchanged, so the templates and the
   markup-shape tests carry over untouched. What changed is the value behind each token.

   The palette is lifted from grok-cli's src/ui/theme.ts (superagent-ai/grok-cli, MIT) rather
   than eyeballed, because its working colours already describe this app's data model:
     planBorder  #e5c07b  -> staged / needs judgment   (--warn)
     diffAdded   #8adf8a  -> applied / verified / done (--ok)
     diffRemoved #df8a8a  -> rejected / failed gate    (--err)
     subagent    #66d9c2  -> a lane running out of process (--live)
     accent      #5c9cf5  -> provenance: a source you can open (--accent)
   Neutrals are nudged a few degrees cool so the ground sits under the blue, not beside it.
   No light variant by design — see the v2 tag in git history if one is ever needed back. */

/* Vendored latin subsets (SIL Open Font License) — this app is offline-safe and localhost-only,
   so fonts ship with it rather than coming from a CDN. See static/fonts/VENDORED.md. */
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 400 800;
  font-display: swap; src: url("fonts/jetbrains-mono-latin.woff2") format("woff2"); }
@font-face { font-family: "JetBrains Mono"; font-style: italic; font-weight: 400 800;
  font-display: swap; src: url("fonts/jetbrains-mono-latin-italic.woff2") format("woff2"); }
/* The latin subset carries no arrows, and the app renders 100+ of them (-> 50x, NE-arrow 33x,
   multiplication-x 9x). Without this face they fall back mid-line — cosmetic on macOS, tofu on
   the deploy/ Linux box. unicode-range is derived from this file's ACTUAL cmap, not from what
   the subsetter advertises: it advertises gear/star/hook-arrow, which JetBrains Mono does not
   contain, and a face that claims a glyph it lacks paints .notdef instead of deferring to the
   system stack. Those three (one use each) fall back by design. */
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 400 800;
  font-display: swap; src: url("fonts/jetbrains-mono-symbols.woff2") format("woff2");
  unicode-range: U+0020, U+2190-2194, U+2197, U+2248, U+2264-2265, U+25B8, U+25BE, U+2715; }
@font-face { font-family: "Martian Mono"; font-style: normal; font-weight: 100 800;
  font-display: swap; src: url("fonts/martian-mono-latin.woff2") format("woff2"); }

:root {
  --bg: #000000;                        /* true black ground */
  --panel: #0d0e10;                     /* cards / rows */
  --panel2: #16181c;                    /* inset panels, read-only text */
  --line: rgba(188, 204, 226, .13);     /* hairline */
  --line-soft: rgba(188, 204, 226, .06);
  --ink: #e0e0e0;
  --dim: #8a909b;
  --accent: #5c9cf5;                    /* provenance blue */
  --accent-soft: rgba(92, 156, 245, .11);
  --accent-line: rgba(92, 156, 245, .34);
  --live: #66d9c2;                      /* a lane running out of process */
  --live-soft: rgba(102, 217, 194, .11);
  --ok: #8adf8a;   --ok-soft: rgba(138, 223, 138, .10);
  --warn: #e5c07b; --warn-soft: rgba(229, 192, 123, .10);
  --err: #df8a8a;  --err-soft: rgba(223, 138, 138, .10);
  --star: #e8a465;
  --on-accent: #04070d;                 /* text ON a filled accent/state chip */
  /* the pointer layer (docs/in-page-agent-plan.md): its own hue, so an object the analyst
     POINTED at never reads as provenance blue (a source) or as a state colour */
  --ax: #b9a3ff;
  --ax-soft: rgba(185, 163, 255, .14);
  --ax-line: rgba(185, 163, 255, .55);
  --serif: "Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;  /* display */
  --sans: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --sbw: 236px;                         /* sidebar width; 0 on mobile — offsets share it */
}
* { box-sizing: border-box; }
html { color-scheme: dark; accent-color: var(--accent); }
body { margin: 0; padding-left: var(--sbw); background: var(--bg); color: var(--ink);
  font: 13.5px/1.62 var(--sans); -webkit-font-smoothing: antialiased; }
::placeholder { color: var(--dim); opacity: 1; }
::selection { background: var(--accent); color: var(--on-accent); }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }
a:focus-visible, button:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
input:focus-visible, textarea:focus-visible, select:focus-visible { outline: none; }
code { font-family: var(--mono); font-size: .86em; background: var(--accent-soft);
  color: var(--accent); padding: .05rem .3rem; border-radius: .25rem; }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }

/* ── sidebar (claude.ai-style vertical nav) ─────────────────────── */
.sidebar { position: fixed; top: 0; left: 0; bottom: 0; width: var(--sbw); z-index: 10;
  display: flex; flex-direction: column; gap: .1rem; padding: .9rem .7rem 1rem;
  background: var(--bg); border-right: 1px solid var(--line); overflow-y: auto; }
.brand { font-family: var(--serif); font-weight: 700; font-size: .96rem;
  color: var(--accent) !important; letter-spacing: -.05em; padding: .2rem .6rem .5rem; }
.brand:hover { text-decoration: none; }
.sidebar .search { display: block; margin: 0 0 .75rem; }
.sidebar .search input { width: 100%; max-width: none; font-size: .84rem; }
.snav { display: flex; flex-direction: column; gap: .1rem; }
.snav a { display: block; color: var(--ink); font-size: .88rem; padding: .42rem .6rem;
  border-radius: .45rem; }
.snav a:hover { background: var(--accent-soft); color: var(--accent); text-decoration: none; }
.snav a.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.sgroup { display: flex; flex-direction: column; gap: .1rem; margin-top: .6rem; }
.sgroup + a { margin-top: .6rem; }
.slabel { font: 600 .62rem/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim); padding: .3rem .6rem .25rem; }
.sgroup.on .slabel { color: var(--accent); }
.sgroup a.sub { padding-left: 1.05rem; }
.sfoot { margin-top: auto; padding-top: .6rem; border-top: 1px solid var(--line-soft); }
.sfoot a { color: var(--dim); font-size: .82rem; }

/* chat history: a nav row that carries its own flyout trigger (Chat). The menu itself is a
   body-level element, position:fixed and placed by JS — the sidebar scrolls, so a menu nested
   inside it would be clipped at its right edge. */
.snavrow { display: flex; align-items: center; gap: .15rem; }
.snavrow > a { flex: 1; min-width: 0; }
.histbtn { display: flex; align-items: center; justify-content: center; flex: none;
  width: 1.75rem; height: 1.75rem; padding: 0; background: none; cursor: pointer;
  border: 1px solid transparent; border-radius: .45rem; color: var(--dim); }
.histbtn:hover, .histbtn[aria-expanded="true"] { background: var(--accent-soft);
  border-color: var(--accent-line); color: var(--accent); }
.histbtn svg { width: 15px; height: 15px; }
/* Activity's inbox badge — the count of items waiting on the analyst's decision. The
   regbanner red trio (soft ground, translucent border, err ink); tabular digits so 9→10
   doesn't wobble the row. An indicator, not a control. */
.navcount { flex: none; min-width: 1.3rem; padding: .05rem .3rem; text-align: center;
  font: 600 .68rem/1.3 var(--mono); font-variant-numeric: tabular-nums;
  color: var(--err); background: var(--err-soft);
  border: 1px solid rgba(223, 138, 138, .42); border-radius: .4rem; }
.histmenu { display: none; position: fixed; left: 0; top: 0; z-index: 20; width: 17.5rem;
  max-height: 62vh; overflow-y: auto; padding: .3rem; background: var(--panel);
  border: 1px solid var(--line); border-radius: .6rem;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .72), 0 0 0 1px rgba(188, 204, 226, .10); }
.histmenu.open { display: block; }
.histmenu .hhead { font: 600 .62rem/1 var(--mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--dim); padding: .4rem .5rem .45rem; }
.histmenu a { display: block; padding: .35rem .5rem; border-radius: .4rem; color: var(--ink); }
.histmenu a:hover { background: var(--accent-soft); color: var(--accent); text-decoration: none; }
.histmenu a.on { background: var(--accent-soft); color: var(--accent); }
/* the one-line summary of the conversation, clipped rather than wrapped (full text on hover) */
.histmenu .tt { display: block; font-size: .84rem; font-weight: 600; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.histmenu .ro { display: block; margin-top: .1rem; font-size: .7rem; }
.histmenu .hnone { padding: .4rem .5rem .6rem; color: var(--dim); font-size: .8rem; }

/* in-page search forms (e.g. the companies page) keep the inline row layout */
.search { display: flex; gap: .4rem; }
.search input { width: 200px; max-width: 56vw; }

main { max-width: 960px; margin: 1.5rem auto 3.5rem; padding: 0 1.2rem; }
/* A page ASKS for the width, through {% block main_class %} (docs/ui-feedback-2026-09-14.md,
   GE-1). 960px is a reading column and most pages want exactly that; the ones whose job is a
   table, a roster or a ledger want the monitor. The width lives on `main` rather than on a
   breakout inside it, and that is the whole point: `body.ax-open` pads the body right by
   --axw, so an open sidebar chat narrows `main` like any other block and the page simply
   recentres in what is left. The `left: 50%; transform: translateX(-50%)` breakouts this
   replaces could not see that padding, so each one needed its own ax-open correction and,
   without it, slid its left edge under the fixed nav.
   The 1.2rem side gutter above holds at every width - the words never touch the edge. */
main.wide { max-width: none; }               /* Home, Activity: as wide as the window gives */
/* the feeds keep a cap, wider than the reading column: a feed row is read line by line, and
   a 2400px line is not read, it is scanned back to the start of. 1280px is Brian's call
   (GE-1, decided 2026-09-16) over no cap at all. */
main.wide.cap { max-width: 1280px; }

/* ── type scale ─────────────────────────────────────────────────── */
.eyebrow { font: 600 .66rem/1 var(--mono); letter-spacing: .13em; text-transform: uppercase;
  color: var(--accent); display: block; margin-bottom: .35rem; }
h1 { font-family: var(--serif); font-weight: 700; font-size: 1.62rem;
  letter-spacing: -.055em; margin: .1rem 0 .3rem; text-wrap: balance; }
h2 { font-family: var(--serif); font-weight: 600; font-size: 1.06rem; margin: 0 0 .55rem;
  letter-spacing: -.04em; text-wrap: balance; }
h3 { font: 600 .68rem/1.4 var(--mono); color: var(--accent); text-transform: uppercase;
  letter-spacing: .1em; margin: 1.2rem 0 .45rem; }
.pagehead { display: flex; align-items: baseline; justify-content: space-between;
  gap: .6rem 1rem; flex-wrap: wrap; }
.ticker { color: var(--dim); font-family: var(--mono); font-size: .78rem; margin-left: .3rem; }
.hint { color: var(--dim); font-size: .85rem; max-width: 68ch; margin: .35rem 0 .9rem; }
.ro { color: var(--dim); font-size: .76rem; font-family: var(--mono); }
/* the ledger's backend tag beside the model: which harness ran the row
   (docs/standalone-harness-plan.md section 6.1, until the columns land) */
.backend { color: var(--dim); font-size: .68rem; font-family: var(--mono);
  margin-left: .4rem; cursor: help; }
.lanealert { color: var(--err); font-weight: 600; cursor: help; }
.empty { color: var(--dim); padding: 2.2rem 0; text-align: center;
  font-family: var(--sans); font-style: italic; font-size: .95rem; }

/* ── stat tiles (Workflow) ──────────────────────────────────────── */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: .7rem; margin: 1rem 0 .4rem; }
a.tile { display: block; background: var(--panel); border: 1px solid var(--line);
  border-radius: .5rem; padding: .8rem 1rem; color: var(--ink);
  transition: border-color .12s, background .12s; }
a.tile:hover { text-decoration: none; border-color: var(--accent-line); background: #101318; }
.tile .n { font-family: var(--serif); font-size: 1.5rem; font-weight: 700; line-height: 1;
  letter-spacing: -.055em; font-variant-numeric: tabular-nums; }
.tile .l { font: .68rem/1.4 var(--mono); color: var(--dim); margin-top: .35rem;
  text-transform: uppercase; letter-spacing: .07em; }
.tile.alert .n { color: var(--accent); }

/* ── cards & ledger rows ────────────────────────────────────────── */
.card { background: var(--panel); border: 1px solid var(--line); border-radius: .5rem;
  padding: 1.05rem 1.2rem; margin: 1rem 0; }
.flagrow { display: flex; justify-content: space-between; gap: 1rem; padding: .65rem 0;
  border-top: 1px solid var(--line-soft); }
.flagrow:first-of-type { border-top: none; }
.flagmain { min-width: 0; }
.detail { margin: .3rem 0; }
.src, .raised { font-size: .78rem; margin-right: .6rem; }
.raised { color: var(--dim); font-family: var(--mono); font-size: .72rem; }
.flagactions { display: flex; gap: .35rem; align-items: flex-start; flex-shrink: 0; flex-wrap: wrap; }
/* new_catalyst row: the type/title/date the finder couldn't resolve. Title takes the slack —
   it's the catalyst's identity key, so it needs room to be specific. */
.stageform { display: flex; gap: .35rem; margin-top: .5rem; flex-wrap: wrap; align-items: center; }
/* ...enough slack to read the prefilled title without scrubbing it. The date beside it had
   no rule at all and fell back to the UA's ~176px, cutting its own format hint in half.
   The ceilings are what Activity taking the whole window costs (GE-1): flex: 1 grows to
   whatever is left, and on a 2560 monitor that was a 985px box holding a catalyst title and
   a 793px one holding "Q1 2027". A field stops growing once it can show its own value. */
.stageform input[name="title"] { flex: 1 1 26rem; min-width: 16rem; max-width: 40rem; }
.stageform input[name="date"] { flex: 1 1 14rem; min-width: 12rem; max-width: 32rem; }
.edgerow { padding: .15rem 0; font-size: .9rem; }

/* ── the theme's notebook (docs/ui-feedback-2026-09-14.md, TH-1) ─────────────── */
/* A .card of entries under the synthesis, so it lives with the card rules above rather than
   in a page section of its own. The composer sits on one line with its Save; entries below
   are hairline-separated, like every other list in the cockpit. */
.notecomp { display: flex; align-items: flex-start; gap: .45rem; margin: .15rem 0 .35rem; }
.notecomp textarea { flex: 1; min-width: 0; }
.notecomp .btn { flex: none; }
/* the floor the script's grow() cannot shrink past: an empty composer is a box you can see
   you may type into, not a single line pretending to be an input */
.notebook textarea { min-height: 3.1rem; }
.notedoc { margin: 0 0 .15rem; }
.noterow { border-top: 1px solid var(--line-soft); padding: .55rem 0 .45rem; }
/* the analyst's own line breaks ARE the note's shape - a notebook that reflowed them into
   one paragraph would be rewriting the entry it promises never to rewrite */
.notebody { white-space: pre-wrap; font-size: .93rem; line-height: 1.6; }
.notefoot { display: flex; align-items: baseline; gap: .6rem; margin-top: .25rem; }
/* hidden until the row is aimed at - the feed rows' rule (.feedrows .cardfoot .actions),
   applied to a card's own rows: never display, so the box each note sits in is the one the
   page had before the notebook arrived.
   OPACITY here, where the feeds use visibility, and the difference is load-bearing: a
   visibility:hidden control is out of the tab order entirely. A feed row survives that
   because its title is a focusable child, so tabbing into the row reveals the actions and
   they become reachable; a note row is prose with no focusable child at all, so the same
   rule would put Edit and Delete beyond a keyboard for good. At opacity 0 they stay
   tabbable, and :focus-within paints them the moment one is reached. */
.notefoot .actions { margin-left: auto; flex: none; display: inline-flex; gap: .3rem;
  opacity: 0; transition: opacity .12s; }
.noterow:hover .notefoot .actions,
.noterow:focus-within .notefoot .actions { opacity: 1; }
@media (hover: none) { .notefoot .actions { opacity: 1; } }
/* edit in place: the entry becomes its own textarea and the read view steps aside, rather
   than sitting above a second copy of the same words */
.noterow.editing .notebody, .noterow.editing .notefoot { display: none; }
.noterow.editing .noteedit { display: block; }
.noteedit .erow { display: flex; gap: .35rem; margin-top: .35rem; }
/* the detection dialog over a saved note: the universe picker's own rows (.pickrow), with
   the phrase that matched in the reason column. The uncovered names below them are a
   readout with one link, not a pick - so they lose the row's pointer. */
.modal.detect .pickwhy { color: var(--dim); }
/* an uncovered name has no checkbox, so its row starts 28px (measured: the box, its own
   margin and the row's gap) to the left of every name above it - indented back, the group
   reads as more rows of the same list under its kicker instead of a ragged left edge */
.modal.detect .pickrow.out { cursor: default; padding-left: 1.75rem; }
.modal.detect .pickrow.out:hover { background: none; }
.modal.detect .outside { margin: .85rem 0 0; font: 600 .66rem/1.5 var(--mono);
  color: var(--dim); text-transform: uppercase; letter-spacing: .08em; }

/* ── the mini-feed deck ─────────────────────────────────────────── */
/* Independently scrollable mini-feeds. Panels are glanceable surfaces: every row's full
   affordances live on the section's own page. Home's deck is six of them and sets its own
   body cap (.homedeck, band 3); this base is what every other deck on the site gets. */
/* Panels stretch to their grid row's tallest (the default `stretch`, not `start`) so the two
   in a row end at the same rule — a short Social next to a full News used to sit on a ragged
   edge. Rows still size independently, and each panel's body is capped, so the tallest panel
   a row can reach is one full feed: stretching never opens more void than that. */
.deck { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: .8rem; margin: 1rem 0; }
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: .5rem;
  min-width: 0; display: flex; flex-direction: column; }
.phead { display: flex; align-items: baseline; gap: .55rem;
  padding: .7rem .95rem .55rem; border-bottom: 1px solid var(--line); }
.phead h2 { font-size: .95rem; margin: 0; }
/* The heading is the section's link. Ink at rest so it still reads as a heading, accent on
   hover/focus as the affordance, and never an underline - (0,2,2) beats the global a:hover on
   specificity. Same pattern as a.kpill.via and .audioep-head h2 .eplink. */
.phead h2 a { color: inherit; }
.phead h2 a:hover, .phead h2 a:focus-visible { color: var(--accent); text-decoration: none; }
.pcount { font: .68rem/1.4 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .07em; }
/* the "more" link at a head's right edge. The deck panels no longer carry one (their headings
   are the links); the Inbox's decision band still does ("ledger & bulk"). */
.pmore { margin-left: auto; font-size: .78rem; white-space: nowrap; }
/* flex:1 hands the stretched panel's spare height to the scroll region, so a short feed's
   rows keep their own hairlines instead of a dead strip under a floating border. */
.pbody { flex: 1 1 auto; min-height: 0; max-height: 56vh; overflow-y: auto;
  overscroll-behavior: contain; padding: .15rem .95rem .5rem; }
.prow { display: block; padding: .55rem 0; border-top: 1px solid var(--line-soft); }
.prow:first-child { border-top: none; }
.pmeta { display: flex; align-items: baseline; gap: .45rem; flex-wrap: wrap;
  font-size: .8rem; min-width: 0; }
.ptext { margin-top: .18rem; font-size: .88rem; line-height: 1.38; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
a.prow.plink { color: var(--ink); }
a.prow.plink:hover { text-decoration: none; background: var(--accent-soft); }
/* A row's headline and company name are CONTENT, not calls to action — ink, like the
   whole-row Audio/Agents links above. Accent is left to mean something on this page: the
   alert tiles, the run buttons, each panel's "open →", and the empty-state CTAs. Underline
   on hover (inherited) still marks them clickable. */
.prow a { color: var(--ink); }
.pempty { color: var(--dim); padding: 1.2rem 0; text-align: center;
  font-family: var(--sans); font-style: italic; font-size: .89rem; }

/* ── Home, widescreen (docs/home-widescreen.md) ─────────────────── */
/* Home is the one page whose job is the whole cockpit at once, and it is UNCAPPED by owner
   decision: a wider monitor buys more columns rather than more margin. It asks for that with
   `main.wide` (GE-1, 2026-09-16); this is just the band stack inside it, taking the width it
   is given. It used to break out of main with left: 50% / translateX(-50%), which needed a
   body.ax-open correction of its own because a transform cannot see the padding an open
   sidebar puts on the body - and slid Home under the nav whenever that correction was wrong.
   `main` now narrows like any block, so there is nothing left to correct and nothing to reset
   at 1100px either: below ~1196px the window minus the nav is narrower than the reading
   column, so wide and capped are the same page. */
.homewide { width: 100%; }
.hband { margin: 1rem 0; }
.hband > .phead { border-bottom: none; padding: 0 0 .45rem; }

/* Band 1: the lane roster. Every seat, always - the lanes that have NOT run are the point,
   and a roster showing only live runs would be empty on the mornings that matters most. */
.seats { display: grid; grid-template-columns: repeat(auto-fit, minmax(15.5rem, 1fr));
  gap: .55rem; }
.seat { display: flex; flex-direction: column; min-width: 0; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line); border-radius: .45rem;
  padding: .55rem .7rem .5rem; }
a.seat:hover { text-decoration: none; border-color: var(--accent-line);
  background: var(--accent-soft); }
/* a lane that has never run is stated by its border too, so the roster's shape reads before
   any of it is read; a live one takes the running colour the rest of the app uses */
.seat.cold { border-color: var(--line-soft); }
.seat.live { border-color: var(--live); background: var(--live-soft); }
.seathead { display: flex; align-items: center; justify-content: space-between;
  gap: .4rem; margin-bottom: .1rem; }
.seathead .nm { font: 600 .84rem/1.35 var(--serif); letter-spacing: -.035em; }
.seat .does { font-size: .76rem; color: var(--dim); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; margin-bottom: .35rem; }
.seat .seatfoot { margin-top: auto; display: flex; align-items: baseline; gap: .5rem;
  flex-wrap: wrap; font: .68rem/1.4 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .06em; }
.seat .seatfoot b { color: var(--ink); font-variant-numeric: tabular-nums; }
.seat .seatfoot .scope { margin-left: auto; color: var(--ax); }
/* a lane with no run at all. Outline-only warn: it must read as "nothing here yet", never
   compete with the filled chip a genuinely running lane carries two cards over. */
.status.never { color: var(--warn); background: transparent;
  border-color: rgba(229, 192, 123, .38); }
/* work waiting with no runner on it yet - the scheduled road writes the row before the skill
   picks it up, so this is an ordinary morning state. Accent rather than the warn a running
   lane wears: on a band whose headline is idleness, pending work has to be legible as neither
   of the two states it sits between. */
.seat .status.queued { color: var(--accent); background: var(--accent-soft);
  border-color: var(--accent-line); }

/* Band 2: spend. Narrow number card beside the chart, which is the element that needs the
   width - a sparkline cannot show that one day was $15 and six were nothing. The card carries
   the whole-app totals and nothing else; nothing here draws a cap, because no single ceiling
   governs every lane. */
/* `align-items: start` so each card is as tall as its own content: stretched to match, the
   chart card letterboxed its 6:1 SVG inside the taller spend card and opened a void under
   it. The floor on the first track is what keeps the key/value list on one line each - at
   26% of a 1280px window the labels wrapped to three lines and dragged the whole band
   taller, which is what made that void big enough to notice. */
.spendband { display: grid; grid-template-columns: minmax(19rem, 26fr) minmax(0, 74fr);
  gap: .55rem; align-items: start; }
.spendband .pbody { max-height: none; overflow: visible; padding-bottom: .8rem; }
.bignum { font-family: var(--serif); font-size: 1.75rem; font-weight: 700; line-height: 1;
  letter-spacing: -.05em; margin: .35rem 0 .1rem; font-variant-numeric: tabular-nums; }
/* capped: when the band stacks, this card is the full page width and an uncapped key/value
   grid strands each value a screen away from its own label */
.kvlist { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0 .7rem;
  max-width: 26rem; margin: .7rem 0 0; font-size: .76rem; font-family: var(--mono); }
.kvlist dt { color: var(--dim); padding: .2rem 0; border-top: 1px solid var(--line-soft); }
.kvlist dd { margin: 0; padding: .2rem 0; text-align: right;
  font-variant-numeric: tabular-nums; border-top: 1px solid var(--line-soft); }
.kvlist dt:first-of-type, .kvlist dd:first-of-type { border-top: none; }
.kvlist dd.bad { color: var(--err); }
/* the chart. Geometry comes from app._spend_chart; this file only colours it. There is no
   cap line to colour: the y-axis is the busiest day in the window, and no single ceiling
   governs the all-lane total this band reports. */
.spendchart { display: block; width: 100%; height: auto; overflow: visible; }
.spendchart .axis { stroke: var(--line); stroke-width: 1; }
.spendchart .ytick, .spendchart .xtick { fill: var(--dim); font: 9px var(--mono); }
.spendchart .xtick { text-anchor: middle; }
.spendchart .barval { fill: var(--ink); font: 9px var(--mono); text-anchor: middle; }
.spendchart .bar { fill: var(--accent); }
/* a day that cost nothing still draws its stub: an empty day is a fact about the machine,
   and drawing nothing there reads as missing data rather than as no work */
.spendchart .bar.zero { fill: var(--line); }

/* Band 3: six panels in one auto-fitting grid - two-up at 1280, three at ~1900, four at
   2560. `.deck`'s own auto-fit already does this; the wider floor keeps a feed row from
   being squeezed to a width its two-line headline cannot use. */
.homedeck { grid-template-columns: repeat(auto-fit, minmax(min(100%, 25rem), 1fr)); }
.homedeck .pbody { max-height: 30rem; }
/* how far out a dated catalyst is - the column the diary exists for. A date on Thursday and
   one three months out must not read the same, so the near ones take the warn colour. */
.diaryrow { display: grid; grid-template-columns: 2.6rem minmax(0, 1fr); gap: .55rem; }
.diaryrow .dwhen { padding-top: .1rem; }
.diaryrow .dbody { min-width: 0; }
.countdown { display: block; text-align: right; font: 600 .74rem/1.35 var(--mono);
  color: var(--ink); letter-spacing: .02em; font-variant-numeric: tabular-nums; }
.countdown.soon { color: var(--warn); }
/* a window that has OPENED and not closed is happening, not overdue - the running colour the
   rest of the app uses for work in flight, never the urgent one a near date takes */
.countdown.now { color: var(--live); }
.countdown.tbc { color: var(--dim); font-size: .62rem; letter-spacing: .08em; }
/* The chart stacks well before the page does, and on its own breakpoint. Squeezed to 74% of
   a narrow column it is still a chart, but a 6:1 viewBox scaled to 450px puts its bar labels
   at four pixels - illegible, and the reason to have a chart rather than a number is gone.
   Full width one row down is the better trade, so the band stacks while the page has not.
   The second query is the same threshold shifted by the open sidebar's 380px, which no
   viewport media query can see. It is the one ax-open correction GE-1 did NOT delete: the
   others were arithmetic about the page's WIDTH, which `main` now owns, while this one is a
   breakpoint - a media query reads the viewport and cannot know the dock took 380px of it. */
@media (max-width: 1400px) { .spendband { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 1780px) { body.ax-open .spendband { grid-template-columns: minmax(0, 1fr); } }

/* ── Dashboard run cards ────────────────────────────────────────── */
/* Three per row, each field LABELLED — with one skill per card there is no header row left to
   say which value is which. Cards stretch to their row's tallest so the Run buttons line up
   across the grid the way they used to line up down a column; rows size independently (the
   grid default) rather than grid-auto-rows:1fr, because one lane with a long error note would
   otherwise drag every other row to its height and open a ~150px void inside every short card. */
.rundeck { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: .7rem; margin: .7rem 0 1.8rem; }
.runcard { display: flex; flex-direction: column; min-height: 15rem; min-width: 0;
  background: var(--panel); border: 1px solid var(--line); border-radius: .5rem;
  padding: .95rem 1rem; }
.runcard.live { border-color: var(--live); background: var(--live-soft); }
.rc-name { font: 600 .95rem/1.3 var(--serif); color: var(--ink);
  text-transform: none; letter-spacing: -.04em; margin: 0 0 .35rem; }
/* the blurb is prose, so it reads as prose — the ledger mono this used to inherit from a
   table cell is for values, not sentences */
.rc-blurb { font-size: .82rem; line-height: 1.5; color: var(--dim); margin: 0 0 .5rem; }
.rc-lands { margin: 0 0 .7rem; font-size: .8rem; }
.rc-lands .k, .rc-stats .k { display: block; font: 600 .62rem/1.5 var(--mono);
  color: var(--dim); text-transform: uppercase; letter-spacing: .09em; }
/* the whole footer sits at the bottom of the card, so every Run control lands on the same
   baseline however long its blurb runs */
.rc-foot { margin-top: auto; padding-top: .6rem; border-top: 1px solid var(--line-soft); }
.rc-stats { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: .5rem .7rem; }
.rc-stats .v { display: block; margin-top: .1rem; font-size: .8rem; min-width: 0; }
.rc-stats .lastrun { font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--ink); }
.rc-stats .lastrun.never { color: var(--dim); font-style: italic; }
.rc-stats .ro { display: inline-block; }
.rc-note { margin: .45rem 0 0; font-size: .76rem; line-height: 1.45; color: var(--err);
  overflow-wrap: anywhere; }
.rc-run { margin-top: .7rem; }
.rc-run form { margin: 0; }
.rc-run .btn { width: 100%; }
@media (max-width: 876px) {
  .rundeck { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .rundeck { grid-template-columns: 1fr; }
  .runcard { min-height: 0; }
}

/* ── agent universe: chip roster + picker modal ─────────────────── */
/* the roster used to live in <p class="hint"> (68ch, normal text wrapping), which broke
   multi-word company names across lines mid-name. Chips are flex items that never shrink,
   so each name stays whole and the row wraps between chips instead of inside them. */
.unichips { gap: .3rem .35rem; margin: .3rem 0 0; }
.unichips .chip { flex: none; }
/* first modal in the app — native <dialog>, so the backdrop/Esc/focus-trap are the
   browser's. Only the box needs styling. */
.modal { width: min(38rem, calc(100vw - 2rem)); max-height: min(80vh, 44rem); padding: 0;
  border: 1px solid var(--line); border-radius: .6rem; background: var(--panel);
  color: var(--ink); box-shadow: 0 12px 40px rgba(0, 0, 0, .78), 0 0 0 1px rgba(188, 204, 226, .12); }
.modal::backdrop { background: rgba(0, 0, 0, .74); }
/* the dialog is the flex column so the body scrolls while head and foot stay put.
   DIRECT child only: a dialog whose rows post independently holds many small forms, and an
   unscoped `.modal form` turned every one of them into a full-width column. */
.modal > form { display: flex; flex-direction: column; max-height: inherit; margin: 0; }
/* …and when the dialog holds no single wrapping form (its rows post independently), the
   dialog itself is that column. Scoped to [open]: an unqualified `display:flex` would beat
   the UA's `dialog:not([open]) { display: none }` and leave every closed dialog on screen. */
.modal[open] { display: flex; flex-direction: column; }
.modalhead { display: flex; align-items: baseline; gap: .6rem;
  padding: .9rem 1.1rem .1rem; }
.modalhead h2 { margin: 0; }
.modalhead .btn { margin-left: auto; font-size: 1rem; line-height: 1; }
.modal .hint { margin: 0; padding: 0 1.1rem .7rem; }
.modalbody { overflow-y: auto; overscroll-behavior: contain; padding: 0 1.1rem;
  border-top: 1px solid var(--line-soft); }
.modalfoot { display: flex; align-items: center; gap: .5rem; padding: .7rem 1.1rem .9rem;
  border-top: 1px solid var(--line); }
.modalfoot .btn:first-of-type { margin-left: auto; }
/* the footer note must never wrap the buttons off the row */
.modalfoot .pathnote { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; cursor: help; }
/* the confirm dialog (base.html; docs/ui-feedback-2026-09-14.md, ME-1): one question and
   two buttons, no head - so the body loses its top seam, which was the line under a heading.
   Narrower than the pickers: a one-line question in a 38rem box reads as an empty room.
   The action button is primary, or the destructive one when the form says so
   (data-confirm-danger): same word, same place, and the colour says what it costs. */
.modal.confirm { width: min(30rem, calc(100vw - 2rem)); }
.modal.confirm .modalbody { border-top: none; padding: 1.1rem 1.2rem .95rem;
  font-size: .92rem; line-height: 1.55; }
.modal.confirm .modalbody p { margin: 0; max-width: none; }
.modal.confirm .btn[data-confirm-yes] { background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); }
.modal.confirm .btn[data-confirm-yes]:hover { box-shadow: 0 4px 12px rgba(92, 156, 245, .3); }
.modal.confirm .btn[data-confirm-yes].danger { background: var(--err); border-color: var(--err); }
.modal.confirm .btn[data-confirm-yes].danger:hover { box-shadow: 0 4px 12px rgba(223, 138, 138, .3); }
.pickrow { display: flex; align-items: baseline; gap: .5rem; padding: .42rem 0;
  border-top: 1px solid var(--line-soft); cursor: pointer; }
.pickrow:first-child { border-top: none; }
.pickrow:hover { background: var(--accent-soft); }
.pickrow input { flex: none; align-self: center; }
.pickname { font-size: .88rem; min-width: 0; }
/* the reason column is right-aligned so the ticks and names stay a clean left edge */
.pickwhy { margin-left: auto; flex: none; padding-left: .6rem; }
.pickwhy.pinned { color: var(--accent); }
/* the input a section needs, sitting just after its name (not right-aligned like the
   kind), so it reads as a caveat on the pick rather than a second column */
.pickneeds { flex: none; font-style: italic; font-size: .78rem; }

/* ── the run picker: which companies a run is for (GE-3) ──────────────────────
   The same rows as every other picker above, plus a sector band around them and a filter
   bar that stays put while the list under it scrolls. */
.modal.picker { width: min(40rem, calc(100vw - 2rem)); }
/* .pickrow and .pickband both set `display`, and an AUTHOR display rule beats the UA's
   `[hidden] { display: none }` at any specificity - so the filter's r.hidden would paint
   every row it just hid. These two lines are what make hiding work at all. */
.pickrow[hidden], .pickband[hidden] { display: none; }
/* sticky so the filter and All/None stay reachable 40 names down the list; the modalbody is
   the scroller, and the negative margins let the bar span its padding rather than sitting in
   a 1.1rem trench of its own */
.pickfind { position: sticky; top: 0; z-index: 1; display: flex; align-items: center;
  gap: .6rem; margin: 0 -1.1rem; padding: .6rem 1.1rem;
  background: var(--panel); border-bottom: 1px solid var(--line-soft); }
.pickfind input { flex: 1 1 auto; min-width: 0; font-size: .82rem; padding: .24rem .45rem; }
.pickfind .ro { flex: none; }
.pickband { padding-bottom: .3rem; }
/* the band heading is a pick row that happens to tick a whole sector: same geometry, but
   the sector's own name is set in the list-heading mono so a band never reads as a company */
.pickrow.bandhead { border-top: 1px solid var(--line); margin-top: .5rem; }
.pickband:first-of-type .pickrow.bandhead { border-top: none; margin-top: 0; }
.pickrow.bandhead .pickname { font: 600 .66rem/1.5 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .09em; }
.pickrow.bandhead + .pickrow { border-top: none; }
/* the count is a readback, not a control - it must not shift the row when it ticks over
   from "8 of 8" to "10 of 10" */
.pickband .pickwhy { font-variant-numeric: tabular-nums; }

/* ── page info: the ⓘ that now carries each page's description ──── */
/* chromeless icon button; align-self centers it against the h1 block inside the
   baseline-aligned .pagehead. The .icon size is pinned here even though it matches the
   base rule — a later container rule must not shrink it silently. */
.infobtn { flex: none; align-self: center; padding: .15rem; margin: 0; background: none;
  border: none; border-radius: 50%; color: var(--dim); cursor: pointer; line-height: 0;
  transition: color .12s, background .12s; }
.infobtn .icon { width: 16px; height: 16px; }
.infobtn:hover { color: var(--accent); background: var(--accent-soft); }
/* the Social gear reuses .infobtn so the pagehead icons read as one family. .alert is the
   lane-alert tint: the always-on-screen summary line is gone, so the error signal rides the
   button itself. The :hover companion is spelled out — bare .infobtn.alert (0,2,0) would
   only tie .infobtn:hover and win on file order, which is the tie trap this file keeps
   hitting; (0,3,0) settles it. */
.infobtn.alert { color: var(--err); }
.infobtn.alert:hover { color: var(--err); background: var(--accent-soft); }
/* the description dialog: a reading surface — narrower than the pickers, and the body
   carries its own bottom padding because there is no .modalfoot to provide one.
   text-align is set, not inherited: a centred ancestor (chat's landing hero) must not
   centre the dialog's copy. The p rules are dialog-qualified past class-pair depth on
   purpose — `.chathead.solo p` (0,2,1) sits LATER in this file, and an equal-specificity
   tie there hands the dialog's paragraphs a stray `margin: .45rem auto 0`. */
.modal.pinfo { width: min(34rem, calc(100vw - 2rem)); text-align: left; }
.pinfo .modalbody { padding: .75rem 1.1rem 1rem; font-size: .88rem; line-height: 1.6; }
dialog.pinfo .modalbody p { margin: 0 0 .65rem; max-width: none; }
dialog.pinfo .modalbody p:last-child { margin-bottom: 0; }
/* chat's landing hero is a centred block, not a flex row — its ⓘ (a sibling of the hero,
   outside it, so the hero's own p/text-align rules never reach the dialog) pins to the
   landing column's corner instead */
.nothread { position: relative; }
/* DIRECT child: this pins the ⓘ that sits as a sibling of the hero, and the descendant
   form silently claimed the preferences gear down in .chatfoot too — two absolutes at
   top:.1rem/right:0, stacked on each other in the same corner. */
.nothread > .infobtn { position: absolute; top: .1rem; right: 0; }

/* ── earnings profile editor (the same modal, more menus) ───────── */
/* wider than the universe picker: this one carries three stacked menus (sectors, 47 KPIs
   grouped by sector, 11 sections) rather than one flat list */
.modal.wide { width: min(46rem, calc(100vw - 2rem)); }
.modalbody h3 { margin: 1rem 0 .3rem; }
.modalbody h3:first-child { margin-top: .7rem; }
/* sectors are short labels — two per row keeps the 9 of them from eating the dialog */
.pickgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1rem; }
.pickgrid .pickrow:nth-child(-n+2) { border-top: none; }
/* the always-pulled headline set: shown so the analyst knows not to re-pick it, styled
   as inert chips so it never reads as a control that failed to respond */
.lockedrow { display: flex; flex-wrap: wrap; gap: .3rem; align-items: baseline;
  padding: .35rem 0 .1rem; }
.chip.locked { background: var(--panel2); border-style: dashed; color: var(--dim);
  cursor: default; }
.pickrow.locked { cursor: default; opacity: .72; }
.pickrow.locked:hover { background: none; }
.kpigroup > summary { cursor: pointer; padding: .4rem 0; font-size: .86rem;
  border-top: 1px solid var(--line-soft); }
.kpigroup > summary:hover { color: var(--accent); }
.kpigroup[open] > summary { font-weight: 600; }
.kpigroup .pickrow { padding-left: 1rem; }
/* a sector that isn't ticked supplies no reachable KPIs — dim it so the disabled boxes
   read as "turn the sector on first", not as controls that broke */
.kpigroup.off > summary { color: var(--dim); }
.kpigroup.off .pickrow { opacity: .5; cursor: default; }
.kpigroup.off .pickrow:hover { background: none; }
/* a metric the analyst authored, vs one inherited from the shared catalog */
.pickwhy.yours { color: var(--accent); }
.rmkpi { margin-left: .4rem; padding: 0 .35rem; line-height: 1.2; flex: none; }
.pickrow .rmkpi { visibility: hidden; }
.pickrow:hover .rmkpi, .rmkpi:focus-visible { visibility: visible; }
/* the sibling forms behind the add/remove controls carry only hidden inputs */
.hidden-form { display: none; }
/* add-a-metric composer, docked under the KPI groups */
.addkpi { border: 1px dashed var(--line); border-radius: .45rem; padding: .6rem .7rem;
  margin: .7rem 0 .2rem; background: var(--panel2); }
.addkpi .k { display: block; font: 600 .62rem/1.5 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .09em; margin-bottom: .35rem; }
.addkpi-row { display: flex; gap: .35rem; flex-wrap: wrap; }
.addkpi-row select, .addkpi-row input { font-size: .82rem; padding: .24rem .45rem; min-width: 0; }
.addkpi-row select { padding-right: 1.45rem; }   /* caret room (see the base select rule) */
.addkpi-row input[name="add_label"], .addkpi-row input[name="add_key"] { flex: 1 1 8rem; }
.addkpi-row .unitin { flex: 0 0 4rem; }
.addkpi .hintin { width: 100%; margin-top: .35rem; font-size: .82rem; padding: .24rem .45rem; }
.addkpi input[name="add_aliases"] { width: 100%; margin-top: .35rem; font-size: .82rem;
  padding: .24rem .45rem; }
.addkpi-foot { display: flex; align-items: center; gap: .6rem; margin-top: .45rem; }
/* flex: none - the note beside it is two lines of prose, and a shrinkable button next to that
   wrapped to "Add / metric" on a full-width page. */
.addkpi-foot .btn { margin-left: auto; flex: none; }

/* the natural-language box: one field, full width, comfortable to type a sentence in */
.addkpi .kpinl { width: 100%; font-size: .86rem; padding: .4rem .5rem; resize: vertical;
  line-height: 1.45; }
/* the staged read: same shell, an accent edge so it reads as "here is what we found" */
.kpiprop { border-style: solid; border-color: var(--accent); background: var(--accent-soft); }
.kpiprop .kpipp { display: flex; gap: .4rem; align-items: baseline; margin-top: .4rem;
  font-size: .82rem; cursor: pointer; }
.kpiprop .kpipp input { flex: none; align-self: center; }
.kpiprop .kpinote { display: block; margin: .4rem 0 0; font-size: .8rem; font-style: italic; }
/* both actions group at the right; the base rule's per-button margin would scatter them */
.addkpi-foot .kpiprop-acts { margin-left: auto; display: flex; gap: .4rem; }
.addkpi-foot .kpiprop-acts .btn { margin-left: 0; }
/* The one-line readback. Every field the model decided is behind `edit details`, so this
   line IS the readback: what it prints as, its unit, who it is asked of, which way is good. */
.kpiprop .kpireceipt { display: block; margin: .1rem 0 0; font-size: .86rem; line-height: 1.5; }
.kpiprop .kpireceipt b { font-weight: 600; }
.kpiprop .kpidetails { margin-top: .5rem; }
.kpiprop .kpidetails > summary { font: 600 .62rem/1.5 var(--mono); color: var(--dim);
  letter-spacing: .04em; text-transform: uppercase; cursor: pointer; padding: .15rem 0; }
.kpiprop .kpidetails[open] > summary { margin-bottom: .35rem; }
.kpiprop .kpidetails select[name="add_scope"] { width: 100%; margin-top: .35rem;
  font-size: .82rem; padding: .24rem 1.45rem .24rem .45rem; }

/* The memo start page's one-sentence box: the primary road, so it leads the page and the two
   older roads sit under a disclosure below it. */
.saybox { display: block; margin: 0 0 1rem; }
.saybox .k { display: block; font: 600 .62rem/1.5 var(--mono); color: var(--dim);
  letter-spacing: .04em; text-transform: uppercase; margin-bottom: .3rem; }
.saybox textarea { width: 100%; font-size: .95rem; padding: .5rem .6rem; resize: vertical;
  line-height: 1.5; }
.saybox-foot { display: flex; align-items: center; gap: .6rem; margin-top: .45rem; }
.saybox-foot .ro { flex: 1 1 auto; }
.saybox-foot .btn { margin-left: auto; flex: none; }
.forkmore { margin-top: .4rem; }
.forkmore > summary { font: 600 .62rem/1.5 var(--mono); color: var(--dim);
  letter-spacing: .04em; text-transform: uppercase; cursor: pointer; padding: .2rem 0; }
.forkmore[open] > summary { margin-bottom: .6rem; }

/* the email FORMAT sample drop zone (Format step) - a dashed target you drop your own email on */
.samplebox { border: 1px dashed var(--line); border-radius: .45rem; padding: .6rem .7rem;
  margin: 1rem 0 .2rem; background: var(--panel2); }
.samplebox .k { display: block; font: 600 .62rem/1.5 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .09em; margin-bottom: .4rem; }
.samplezone { display: block; border: 1.5px dashed var(--line); border-radius: .5rem;
  padding: 1.1rem .8rem; text-align: center; cursor: pointer; background: var(--panel);
  transition: border-color .12s, background .12s; }
.samplezone:hover, .samplezone.drag { border-color: var(--accent); background: var(--accent-soft); }
.samplezone.slim { padding: .55rem .8rem; margin-top: .5rem; }
.samplezone input[type="file"] { display: none; }
.samplecue { font-size: .84rem; color: var(--dim); }
.samplefoot { display: flex; align-items: center; gap: .6rem; margin-top: .5rem; }
.samplefoot .btn { margin-left: auto; flex: none; }
.samplehas { display: flex; align-items: baseline; gap: .5rem; }
/* the filename truncates instead of wrapping mid-name; min-width:0 lets a flex child shrink */
.samplename { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: .88rem; }
.samplekind { flex: none; }
.samplehas .btn { margin-left: auto; flex: none; align-self: center; }

/* ── Social sources dialog: platform rail + one pane per platform ── */
.modal.srcs { width: min(46rem, calc(100vw - 2rem)); }
/* the rail/pane pair owns the height, so switching platforms never resizes the dialog;
   each pane scrolls alone. overflow:hidden overrides the base .modalbody scroll — without
   it the body and the pane would each grow a scrollbar. */
.srcs .modalbody { display: flex; padding: 0; height: min(24rem, 60vh); overflow: hidden; }
.srcrail { flex: none; width: 10.5rem; display: flex; flex-direction: column; gap: .1rem;
  padding: .55rem .45rem; border-right: 1px solid var(--line-soft); overflow-y: auto; }
.srcrow { display: flex; align-items: center; gap: .55rem; padding: .5rem .6rem;
  background: none; border: none; border-radius: .45rem; color: var(--ink); font: inherit;
  font-size: .88rem; cursor: pointer; text-align: left; }
.srcrow:hover { background: var(--accent-soft); }
.srcrow[aria-selected="true"] { background: var(--accent-soft); color: var(--accent);
  font-weight: 600; }
.srcrow .icon { width: 15px; height: 15px; }
.srcrow .cnt { margin-left: auto; font: .68rem/1.4 var(--mono); color: var(--dim); }
.srcpane { flex: 1; min-width: 0; padding: .8rem 1.1rem 1rem; overflow-y: auto;
  overscroll-behavior: contain; }
/* the [hidden] companion — an author display rule on .srcpane would beat the UA's
   `[hidden] { display: none }` (the .estcell lesson below) */
.srcpane[hidden] { display: none; }
section.srcpane h3:first-child { margin-top: 0; }
/* the matched-only tick rides inside a .search form, whose bare `input` width rule
   (200px) would inflate the checkbox and scatter the label across the row */
.search label.ro { display: inline-flex; align-items: center; gap: .3rem;
  white-space: nowrap; }
.search label.ro input[type="checkbox"] { width: auto; max-width: none; }
/* a narrow window keeps the rail as a logo strip */
@media (max-width: 640px) {
  .srcrail { width: 3.1rem; }
  .srcrow { justify-content: center; padding: .5rem .3rem; }
  .srcrow .lbl, .srcrow .cnt { display: none; }
}

/* ── earnings run wizard ────────────────────────────────────────── */
.runrow2 { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; }
.runrow2 label { display: flex; flex-direction: column; gap: .25rem; font-size: .85rem;
  color: var(--dim); }
.runind { margin: .45rem 0 0; }

/* Step 1 of the Dashboard door: which print. The prints the coverage DB already knows have
   HAPPENED lead the list, then the whole book behind a filter. Rows are radios, not links —
   picking one re-resolves the two steps behind it, so it has to be a form control. */
.coband { border: 1px solid var(--line); border-radius: .45rem; background: var(--panel2);
  padding: .5rem .65rem; margin: .2rem 0 .3rem; }
.coband .bandhd { display: flex; align-items: baseline; gap: .5rem; margin-bottom: .1rem; }
/* right-aligned, like .corow .when below: at a .5rem gap the count read as part of the
   label ("REPORTED, NO MEMO YET 4 on record") rather than as a count of the rows under it */
.coband .bandhd .ro { margin-left: auto; }
.coband .bandhd .k { font: 600 .62rem/1.5 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .09em; }
/* flex-wrap so the trailing date/industry column drops to a second line before the name has
   to give up room. The name itself DOES ellipsise (.corow .co below) and carries the full
   string in title=, because a wrapped 60-character legal name made every row a different
   height and the list stopped scanning as a list. */
.corow { display: flex; align-items: baseline; gap: .55rem; flex-wrap: wrap;
  padding: .42rem .1rem; border-top: 1px solid var(--line-soft); cursor: pointer; }
.corow:first-of-type { border-top: none; }
/* an author `display` beats the UA's `[hidden] { display: none }`, so the filter's
   `row.hidden = true` would leave every row on screen — the same trap already patched for
   .estcell, .btn, .maphits and the wizard's Back+Continue */
.corow[hidden] { display: none; }
.corow:hover { background: var(--accent-soft); }
.corow input { flex: none; align-self: center; }
.corow .tk { flex: 0 0 4.4rem; font-family: var(--mono); font-size: .78rem; color: var(--ink); }
.corow .co { flex: 1 1 12rem; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: .88rem; }
.corow .when { margin-left: auto; flex: none; font: .74rem/1.5 var(--mono); color: var(--dim); }
/* a name with no industry pin opens step 2 with nothing ticked — say so on the row that
   causes it, not only once the analyst is looking at an empty picker */
.corow .when.noind { color: var(--warn); }
.pickfilter { width: 100%; margin: .1rem 0 .45rem; }
/* 24rem, not the 15rem this had inside the modal: on a page of its own there is room, and
   35 covered names through a 15rem window is most of a book read through a letterbox. */
.colist { max-height: 24rem; overflow-y: auto; overscroll-behavior: contain;
  border: 1px solid var(--line); border-radius: .45rem; padding: .1rem .6rem; }
.pickerr { margin: .5rem 0 0; color: var(--err); font-size: .85rem; }
/* the confirm step. The Dashboard cannot tell you which company you meant, so this reads the
   whole submit back — including the two writes that happen on "Save settings only" too. */
.recap { background: var(--panel2); border: 1px solid var(--line); border-radius: .45rem;
  padding: .7rem .85rem; margin: .3rem 0 .6rem; }
.recap dl { display: grid; grid-template-columns: 8.5rem minmax(0, 1fr); gap: .32rem .8rem;
  margin: 0; font-size: .87rem; }
.recap dt { font: 600 .62rem/1.7 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .09em; }
.recap dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 560px) { .recap dl { grid-template-columns: 1fr; } }
/* one estimate cell per metric; headline metrics lead, so they read as the spine they are */
.estgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: .45rem .6rem; margin-top: .3rem; }
.estcell { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
/* an author `display` beats the UA's `[hidden] { display: none }`, so a cell the wizard hid
   stayed on screen while the DOM insisted it was hidden — only a screenshot showed it */
.estcell[hidden] { display: none; }
.estcell .k { font: 600 .64rem/1.5 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .07em; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.estcell.hl .k { color: var(--ink); }
.estcell input { font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: .84rem; padding: .24rem .45rem; }
.modalbody textarea { font-size: .85rem; margin-top: .3rem; }
@media (max-width: 640px) { .runrow2 { grid-template-columns: 1fr; } }

/* ── flashes ────────────────────────────────────────────────────── */
/* floating toasts: a flash must be visible wherever the user is scrolled — a refusal
   rendered off-screen reads as "the button did nothing" */
.flashes { position: fixed; top: 1rem; left: calc(50% + var(--sbw) / 2); transform: translateX(-50%);
  z-index: 1000; width: min(720px, calc(100vw - var(--sbw) - 2rem)); padding: 0; }
.flash { padding: .5rem .8rem; border-radius: .4rem; margin-bottom: .4rem; font-size: .88rem;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .72), 0 0 0 1px rgba(188, 204, 226, .10); cursor: pointer;
  border: 1px solid var(--line); background: var(--panel); }
/* tint layered OVER the opaque panel, not instead of it — the *-soft tokens are ~9% alpha,
   so setting them as the sole background lets the page text behind bleed through the toast */
.flash.ok { background: linear-gradient(var(--ok-soft), var(--ok-soft)), var(--panel);
  border-color: rgba(138, 223, 138, .32); color: var(--ok); }
.flash.warn { background: linear-gradient(var(--warn-soft), var(--warn-soft)), var(--panel);
  border-color: rgba(229, 192, 123, .32); color: var(--warn); }
.flash.err { background: linear-gradient(var(--err-soft), var(--err-soft)), var(--panel);
  border-color: rgba(223, 138, 138, .32); color: var(--err); }
/* a one-time secret (a personal access token, a set of 2FA recovery codes): the same family,
   but a BLOCK rather than a toast. It never fades, the value is selectable, and the cursor
   says so, because the click that dismisses every other flash would delete this one
   mid-drag. Warn-toned: this is the one flash that is asking for something back. */
.flash.secret { background: linear-gradient(var(--warn-soft), var(--warn-soft)), var(--panel);
  border-color: rgba(229, 192, 123, .32); color: var(--ink); cursor: default;
  padding: .65rem .8rem .7rem; text-align: left; }
.flash.secret .secretwhy { margin: 0 0 .5rem; color: var(--warn); font-size: .82rem;
  line-height: 1.5; }
/* pre-wrap, so what the block SHOWS is what Copy puts on the clipboard: a value carrying
   its own newlines would otherwise render space-joined and copy differently */
.flash.secret .secretval { display: block; padding: .45rem .55rem; color: var(--ink);
  background: var(--panel2); border: 1px solid var(--line); border-radius: .3rem;
  font-size: .8rem; overflow-wrap: anywhere; white-space: pre-wrap; user-select: all;
  cursor: text; }
.flash.secret .secretacts { display: flex; gap: .4rem; margin-top: .55rem; }

/* ── buttons: two sizes, one primary per view ───────────────────── */
.btn { background: var(--panel); color: var(--ink); border: 1px solid var(--line);
  border-radius: .375rem; padding: .34rem .75rem; cursor: pointer; font-size: .84rem;
  font-family: inherit; width: auto; justify-self: start;
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  transition: border-color .12s, background .12s, box-shadow .12s;
  /* a <button> gets line-height: normal from the UA stylesheet, but an <a class="btn">
     inherits the body's taller one: measured 30.9px vs 34.6px, so every Cancel link sat
     3.7px taller than the Save button beside it. One line-height for both. */
  line-height: normal; }
/* the author `display` above beats the UA's `[hidden] { display: none }` (same trap patched
   for .estcell / .maphits / the wizard's Back+Continue). Without this, ANY button a script
   hides with .hidden = true stays on screen — which is why the agent wizard showed its
   "Create agent" submit next to "Continue" on step 1 of 5. */
.btn[hidden] { display: none; }
.btn:hover { border-color: var(--accent-line); background: var(--accent-soft);
  /* resets the global a:hover underline, so an <a class="btn"> hovers like a <button> */
  text-decoration: none; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn.primary:hover { box-shadow: 0 4px 12px rgba(92, 156, 245, .3); }
.btn.ghost { color: var(--dim); background: transparent; }
/* a row whose action is BLOCKED: this control navigates away to clear a precondition rather
   than doing the row's work. Styled apart from both the real action and Reject so the queue
   reads act / go-unblock / reject instead of three identical buttons. */
.btn.blocked { color: var(--warn); background: transparent;
  border-style: dashed; border-color: rgba(229, 192, 123, .45); }
.btn.blocked:hover { background: var(--warn-soft); border-color: var(--warn); }
.btn.small { padding: .17rem .5rem; font-size: .75rem; }
.btn.busy, button.busy { opacity: .6; cursor: progress; pointer-events: none; }
/* the loading circle — every busy/working button spins, click feedback included */
.btn.busy::after, button.busy::after { content: ""; display: inline-block; width: .7em;
  height: .7em; margin-left: .45em; vertical-align: -.05em; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent;
  animation: btnspin .7s linear infinite; }
@keyframes btnspin { to { transform: rotate(360deg); } }
.btn:disabled { opacity: .5; cursor: not-allowed; }

/* ── forms ──────────────────────────────────────────────────────── */
input, textarea, select { background: var(--panel); color: var(--ink);
  border: 1px solid var(--line); border-radius: .375rem; padding: .38rem .55rem; font: inherit; }
input:focus, textarea:focus, select:focus { border-color: var(--accent-line);
  box-shadow: 0 0 0 3px var(--accent-soft); }
/* A file input's button is a shadow part: color-scheme darkens the field but leaves the OS
   button light, which read as a bright chip on the Documents drop. Style it like a .btn. */
::file-selector-button { font: inherit; cursor: pointer; margin-right: .5rem;
  background: var(--panel2); color: var(--ink); border: 1px solid var(--line);
  border-radius: .3rem; padding: .18rem .55rem; }
::file-selector-button:hover { border-color: var(--accent-line); color: var(--accent); }
/* A NATIVE select silently drops the background / border / radius set above (WebKit paints OS
   chrome instead), which is why every dropdown in the cockpit read as a raw system control
   next to its styled inputs. Reset appearance so a select obeys the same rule as its
   neighbours, and draw back the caret the OS chrome took with it. The caret is inlined as a
   data URI and repeats --dim's value literally, because a data URI cannot read a custom
   property — if --dim moves, the stroke below has to move with it. Every rule below that re-pads a select keeps the
   right-hand room this reserves; text under the caret is the failure mode to watch for. */
select { appearance: none; -webkit-appearance: none; cursor: pointer; padding-right: 1.55rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.75 4.75 6 8l3.25-3.25' fill='none' stroke='%238a909b' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .55rem center; background-size: 10px; }
select:hover { border-color: var(--accent-line); }
/* a multi-select is a list box, not a dropdown — a caret on it would promise a menu that never
   opens. No instance in the app today; this keeps the rule above correct for a shape it
   necessarily matches, rather than anticipating a feature. */
select[multiple] { background-image: none; padding-right: .55rem; cursor: default; }
textarea { width: 100%; resize: vertical; }
form.inline { display: inline-flex; gap: .35rem; margin: 0; flex-wrap: wrap; }
/* a label-above-control form. A bare `<label>Name <input></label>` lays the caption inline,
   so a following label reads as a value sitting beside the previous field rather than as the
   heading for its own control, and every input keeps the UA's ~176px default width. */
.stackform { display: flex; flex-direction: column; gap: .7rem; align-items: flex-start; }
.stackform label { display: flex; flex-direction: column; gap: .28rem; width: 100%;
  font-size: .8rem; color: var(--dim); }
.stackform input[type=text], .stackform input[type=url], .stackform input:not([type]),
.stackform textarea { width: 100%; }
.prov { display: flex; gap: .4rem; margin-top: .4rem; flex-wrap: wrap; }
.prov input { flex: 1 1 180px; min-width: 0; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; align-items: end; }
.grid2 label { display: flex; flex-direction: column; gap: .25rem; font-size: .85rem; color: var(--dim); }
.grid2 label.wide { grid-column: 1 / -1; }
.grid2 label.chk, label.chk { flex-direction: row; align-items: center; gap: .4rem; }
.grid2 .btn, .grid2 button { width: auto; justify-self: start; }

/* edit affordances — read first, forms behind a summary */
details.edit, details.csvimport { margin-top: .6rem; }
details.edit > summary, details.csvimport > summary { cursor: pointer; color: var(--accent);
  font-size: .82rem; list-style: none; display: inline-block; user-select: none; }
details.edit > summary:hover, details.csvimport > summary:hover {
  text-decoration: underline; text-underline-offset: 2px; }
details.edit[open] > summary, details.csvimport[open] > summary { margin-bottom: .6rem; }
details.csvimport { margin-bottom: 1rem; }
details summary { cursor: pointer; color: var(--dim); }
details.card > summary { margin: -.2rem 0; }

/* new-company import: search-driven add prompt + staged review card */
.addnew { border-color: var(--accent-line); }
.addalt { margin: .2rem 0 1.2rem; gap: .5rem; align-items: baseline; }
.reviewcard .grid2 { margin: .55rem 0; }

/* read views */
.readgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .55rem 1.4rem; }
.readgrid > div { min-width: 0; }
.readgrid .k { font: 600 .66rem/1.5 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .08em; }
.persp-read { border-top: 1px solid var(--line-soft); padding: .6rem 0; }
.persp-read:first-of-type { border-top: none; }
.persp-read .k { font: 600 .66rem/1.7 var(--mono); color: var(--accent);
  text-transform: uppercase; letter-spacing: .08em; }
.persp-read .body { font-family: var(--sans); font-size: .93rem; line-height: 1.7; }
.persp-read .body.none { color: var(--dim); font-style: italic; font-size: .9rem; font-family: var(--sans); }
.persp-edit { border-top: 1px solid var(--line-soft); padding: .7rem 0; }
.persp-edit label { font-weight: 600; display: block; margin-bottom: .3rem; font-size: .85rem; }

/* ── filters: mono kickers + pill chips ─────────────────────────── */
.filters { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin: .8rem 0; }
/* the micro-caps treatment is for the row's LABELS ("status:", "source:") only. A bare
   `.filters > span` also caught the helper sentences (.ro) and an active term chip, which
   rendered whole sentences as 10px uppercase — unreadable, and inconsistent with the same
   .ro class one card above. */
.filters > span:not(.ro):not(.chip) { font: 600 .64rem/1 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .09em; }
.filters .sep { margin-left: .8rem; }
.filters select { font-size: .82rem; padding: .24rem 1.45rem .24rem .45rem; max-width: 16rem; }
/* News search: same row as the Theme dropdown, pinned to the far right. It wraps to its own
   line on a narrow window rather than squeezing the dropdowns. */
.newssearch { margin-left: auto; display: inline-flex; align-items: center; gap: .35rem; }
.newssearch input { font-size: .82rem; padding: .24rem .5rem; width: 15rem; max-width: 42vw; }
.chip { padding: .24rem .68rem; border: 1px solid var(--line); border-radius: 9999px;
  color: var(--ink); font-size: .79rem; background: var(--panel); }
.chip:hover { background: var(--accent-soft); border-color: var(--accent-line); text-decoration: none; }
.chip.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.chip.on:hover { background: var(--accent); }
.chips { display: flex; flex-wrap: wrap; gap: .35rem; margin: .3rem 0; }
.chipform { display: inline-flex; align-items: center; gap: .3rem; background: var(--panel);
  border: 1px solid var(--line); border-radius: 9999px; padding: .1rem .35rem .1rem .65rem;
  font-size: .79rem; }
.chipform a { color: var(--ink); }
.chipform a.on { color: var(--accent); font-weight: 600; }
.chipform button { background: none; border: none; color: var(--dim); cursor: pointer; padding: 0 .1rem; }
.chipform button:hover { color: var(--err); }

/* ── badges & state pills ───────────────────────────────────────── */
.badge { font-family: var(--mono); font-size: .62rem; text-transform: uppercase;
  letter-spacing: .07em; padding: .16rem .5rem; border-radius: .3rem; margin-right: .4rem;
  background: var(--panel2); border: 1px solid var(--line); color: var(--dim); }
.badge.catalyst_occurred { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }
.badge.stale_commentary { background: var(--warn-soft); border-color: rgba(229, 192, 123, .32); color: var(--warn); }
.badge.new_catalyst { background: var(--ok-soft); border-color: rgba(138, 223, 138, .32); color: var(--ok); }
.badge.peer_reported { background: rgba(201, 138, 201, .12); border-color: rgba(201, 138, 201, .42); color: #c98ac9; }
.badge.news { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }
.badge.x { background: var(--ink); border-color: var(--ink); color: var(--on-accent); }
.badge.substack { background: rgba(232, 164, 101, .12); border-color: rgba(232, 164, 101, .45); color: var(--star); }
.badge.academic { background: rgba(102, 217, 194, .10); border-color: rgba(102, 217, 194, .40); color: var(--live); }
.badge.reddit { background: rgba(242, 133, 106, .11); border-color: rgba(242, 133, 106, .45); color: #f2856a; }
.status { font: 600 .62rem/1 var(--mono); text-transform: uppercase; letter-spacing: .07em;
  padding: .18rem .45rem; border-radius: .25rem; color: var(--dim); background: var(--low, var(--panel2));
  background: var(--panel2); border: 1px solid var(--line-soft); }
.status.upcoming, .status.confirmed, .status.occurred { color: var(--accent);
  background: var(--accent-soft); border-color: var(--accent-line); }
.status.pending, .status.running, .status.queued { color: var(--warn); background: var(--warn-soft); border-color: rgba(229, 192, 123, .32); }
.status.done { color: var(--ok); background: var(--ok-soft); border-color: rgba(138, 223, 138, .32); }
/* 'stopped' = the runner died mid-run. It means the same thing to the analyst as 'error'
   (that run did not finish), so it must not render dimmer than 'queued'. */
.status.error, .status.stopped { color: var(--err); background: var(--err-soft); border-color: rgba(223, 138, 138, .32); }
/* 'cancelled' = the analyst pressed Stop. Neutral on purpose — it sits with 'dismissed',
   never with 'error': a red pill would report their own control back to them as a failure. */
.status.dismissed, .status.read, .status.cancelled { color: var(--dim); }
.status.linked { color: var(--ok); background: var(--ok-soft); border-color: rgba(138, 223, 138, .32);
  text-decoration: none; }
a.status.linked:hover { border-color: var(--ok); }
.origintag { font: 600 .64rem/1 var(--mono); text-transform: uppercase; letter-spacing: .07em;
  padding: .22rem .55rem; border-radius: .3rem; border: 1px solid var(--line);
  color: var(--dim); background: var(--panel); }
.origintag.final { border-color: rgba(138, 223, 138, .38); color: var(--ok); background: var(--ok-soft); }
.origintag.edited { border-color: var(--accent-line); color: var(--accent); background: var(--accent-soft); }

/* ── tables ─────────────────────────────────────────────────────── */
/* The house scroll box for a table that cannot be made to fit: the TABLE scrolls sideways
   inside it, the document never does. Declared once, here, with the tables - it used to be
   spelled twice (here bare, and again beside .usagetbl with the margin), and a selector that
   exists twice with different values is a rule whose behaviour depends on which copy the
   reader found. A wrapper is also why this is a div in the template and not `display: block`
   on the table itself: changing a table's display drops its table semantics for a screen
   reader, and a fix that costs the roster its row/column announcements is not a fix. */
.tablewrap { overflow-x: auto; margin: .6rem 0 2rem; }
/* ...except when it ENDS a card, where the card's own padding is already the gap below and
   the 2rem lands on top of it as an empty strip. Last-child only: a wrapper with a sibling
   after it (company.html's three, each followed by its editor) still needs the separation. */
.card > .tablewrap:last-child { margin-bottom: 0; }
table.cats, table.progs { width: 100%; border-collapse: collapse; font-size: .85rem; }
.cats th, .progs th { text-align: left; font: 600 .64rem/1.4 var(--mono); color: var(--accent);
  text-transform: uppercase; letter-spacing: .08em; border-bottom: 1px solid var(--line);
  padding: .35rem .45rem .35rem 0; }
.cats td, .progs td { padding: .4rem .45rem .4rem 0; border-bottom: 1px solid var(--line-soft);
  vertical-align: top; }
.rowact { display: flex; gap: .3rem; flex-wrap: wrap; }
/* the catalyst table's row action (Mark occurred) surfaces on hover or keyboard focus, the
   feed rows' rule (.feedrows .cardfoot .actions below): painted on every upcoming row it said
   the same thing eight times. The FORMS hide, not the cell, so the row's bottom border holds.
   Touch devices get it outright, in the (hover: none) block beside the feed rows'. No
   white-space rule on the button: a hidden control keeps exactly the box the painted one had,
   so the title column is as wide as it was when the button was painted - a nowrap label
   made the column reserve 20px more for a control nobody could see, and titles wrapped a
   line earlier at rest. Where the table is tight the revealed label wraps, as the painted
   one did. */
table.cats .rowact > form { visibility: hidden; }
table.cats tr:hover .rowact > form, table.cats tr:focus-within .rowact > form { visibility: visible; }
/* visibility: hidden takes the button OUT of the tab order, so :focus-within can only
   fire from some OTHER focusable thing in the row - the constraint the .audioep rules
   below state and satisfy by keeping a discovered card's first form painted. A catalyst
   row has only two candidates, its title (a link when the source URL is http(s)) and its
   review pill (a link only while pending), and a row with an unusable source URL - which
   the template's own comment says arrives routinely from web-searching agents - has
   neither. Such a row keeps its control painted rather than hiding it from the keyboard. */
table.cats tr:not(:has(a)) .rowact > form { visibility: visible; }
/* a PENDING review pill is the door to its Review row (the one queue): a link that keeps the
   pill's colours, no underline, a firmer border on hover */
a.status.pending:hover, a.status.pending:focus-visible { border-color: var(--warn); text-decoration: none; }
/* the same door in the accent colours: Activity's "draft ready" pill links to the draft review */
a.status.upcoming:hover, a.status.upcoming:focus-visible { border-color: var(--accent); text-decoration: none; }
table.companies { width: 100%; border-collapse: collapse; margin: .6rem 0; }
table.companies td { padding: .34rem .4rem; border-bottom: 1px solid var(--line-soft); }

/* the two auth-event logs (/account, /admin): bare tables with no column rules of their own,
   so an event kind long enough to say WHICH credential it was ("mcp token revoked all
   (password change, 3)") ran straight into the timestamp beside it, and the headers sat
   centred over left-aligned data. One class, because both render the same rows. */
.evtlog th { text-align: left; }
.evtlog th, .evtlog td { padding: .16rem .6rem .16rem 0; vertical-align: top; }
.evtlog th:last-child, .evtlog td:last-child { padding-right: 0; }
/* the stamp and the address are single values, not prose: they keep their line and the
   event kind takes whatever width is left, because a timestamp broken across two lines is
   unreadable in a way a wrapped sentence is not */
.evtlog td.ro { white-space: nowrap; }
/* Which is exactly why both logs live in a .tablewrap in their templates. Two unwrappable
   columns plus the event kind do not fit a phone however the width is shared out: a
   "2026-09-18 17:11:15" stamp is 148px and an IPv6 address is 204px, so at 390px the
   /account log measured 461px and /admin's 756px and the whole DOCUMENT scrolled sideways -
   every page, because the scroll is on the root. The honest trade at that width is the one
   .mdtable already states for a six-column chat table: the table scrolls, the page does not,
   and no value is hidden or truncated to buy it. */
/* ...and the floor .mdtable states with it, for the same reason and the same failure. Inside
   a 321px scroll box the table sits at its MIN-content, which takes the one prose column down
   to its longest word: "mcp token issued #4" came out as four stacked lines beside a
   one-line timestamp, eight rows of it. A floor hands that column back its line and the
   scroll pays for it - the layout a phone gets is then the one a laptop gets, scrolled.
   Two floors, keyed on the column count like .mdtable's, because /admin's log carries a
   fourth column (who) whose emails do not wrap either. Unconditional, also like .mdtable's:
   what makes the table tight is the width of the CARD it is in - which the sidebar chat and
   every window between the phone breakpoint and the reading column all move - and a floor
   hung off a viewport query is right on one side of that query and absent on the other. Both
   floors sit under the widths these cards reach on a laptop, so a log that fits is never the
   one made to scroll. */
.evtlog { min-width: 33rem; }
.evtlog:has(th:nth-child(4)) { min-width: 50rem; }
/* With that floor in hand the who column may wrap, and only with it: `anywhere` takes a
   column's min-content down to one character, and a table sitting AT its min-content then
   renders an address one letter per line - the trap .mdtable's column-width rule describes.
   The floor is what keeps this table off its min-content, so the break is spent on an
   address too long for the column instead of on the column itself. Without it /admin's log
   gave its whole 321px of visible width to one unbreakable email and pushed every event kind
   out of sight. td.ro is untouched by this: nowrap already forbids the break. */
.evtlog td { overflow-wrap: anywhere; }

/* /admin's roster: id · email · workspace · a budget form · reset and delete. Each cell holds
   a form that is ONE control - a field and the button that submits it - so it may not wrap.
   Let it (form.inline is flex-wrap: wrap everywhere else, which is right for a row of
   independent buttons) and the column's min-content collapses to the width of the widest
   single INPUT: at 390px that laid every roster row out as five stacked lines, four of them
   blank beside a one-line email. Nowrap trades a wider table for one-line rows, and the table
   is scrolled inside .tablewrap either way. */
.usertbl form.inline { flex-wrap: nowrap; }
/* ...and the same two corrections .evtlog needed, because this is the same kind of table with
   the same absence of rules: headers left over left-aligned data, and a gutter, so a slug
   does not run into the budget field beside it. */
.usertbl th { text-align: left; }
.usertbl th, .usertbl td { padding: .2rem .6rem .2rem 0; }
.usertbl th:last-child, .usertbl td:last-child { padding-right: 0; }
/* "1 ★" is one value - the id and the badge that says this account is an admin. The column
   sits at its min-content, which is the digit, so the star was wrapping onto a line of its
   own under every admin's id at EVERY width. */
.usertbl td:first-child { white-space: nowrap; }

/* M6 — the coverage list carries its own workload columns (staged / news / seen). Figures are
   mono + tabular so the digits line up as a column you can scan down, not a ragged edge. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
table.companies.cov th { padding: .2rem .4rem .32rem; border-bottom: 1px solid var(--line);
  font: 600 .62rem/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim); text-align: left; white-space: nowrap; }
table.companies.cov th.num { text-align: right; }
table.companies.cov th a { color: inherit; }
table.companies.cov th a:hover { color: var(--accent); text-decoration: none; }
table.companies.cov th.on a { color: var(--accent); }
/* the active column names itself with a caret rather than a colour alone — and the caret
   points the way that column actually sorts: name is the only ascending one */
table.companies.cov th.on a::after { content: " \25BE"; }
table.companies.cov th.on.asc a::after { content: " \25B4"; }
table.companies.cov th.tick { width: 1.6rem; }
/* Sector grouping: the map's sector rail, as bands down the coverage list. Each band is its
   own tbody headed by a `th scope="rowgroup"` — that scope reads "every remaining cell in
   this row group", so one tbody per band is what makes it name its own rows rather than the
   rest of the table. The rule out-specifies the plain `cov th` above (uppercase micro-type
   for the column strip): a band is a heading in the list, not another column label. */
table.companies.cov tr.secrow > th { padding: .8rem .4rem .3rem; border-bottom: 1px solid var(--line);
  font: 600 .86rem/1.4 var(--sans); letter-spacing: 0; text-transform: none; color: var(--ink);
  text-align: left; }
table.companies.cov tbody tr.secrow:first-child > th { padding-top: .2rem; }
/* the count rides on the band's own line, dimmed — it labels the band, it is not a figure
   in the Staged/News/Seen columns */
table.companies.cov tr.secrow > th .ro { margin-left: .45rem; font-size: .7rem; }
/* The band heading IS the fold control once the script upgrades it (companies.html): a
   button that inherits the heading's type instead of the browser's chrome, so the line looks
   the same folded, unfolded, and with no JS at all.
   Both the explicit width and the negative margin are load-bearing for the hover tint: a
   button shrink-to-fits even as a flex container, so without them the tint stopped after the
   count and read as a highlighted label rather than "this whole line is the target". */
table.companies.cov tr.secrow > th .secx { display: flex; align-items: center;
  box-sizing: border-box; width: calc(100% + .6rem); margin: 0 -.3rem; padding: .06rem .3rem;
  border: 0; border-radius: .3rem; background: none; color: inherit; font: inherit;
  text-align: left; cursor: pointer; }
table.companies.cov tr.secrow > th .secx:hover { color: var(--accent); background: var(--accent-soft); }
/* Drawn with borders rather than typed: \25BE (what the sort headers use) renders as a ~4px
   dot in this font stack, which reads as a bullet, not a control. Down when the band is open,
   quarter-turned when it is folded - the only cue a folded band's row can carry once its
   names are gone. */
table.companies.cov tr.secrow .caret { width: 0; height: 0; margin-right: .45rem;
  border-left: .3rem solid transparent; border-right: .3rem solid transparent;
  border-top: .34rem solid currentColor; opacity: .5; transition: transform .12s ease; }
table.companies.cov tr.secrow [aria-expanded="false"] .caret { transform: rotate(-90deg); }
/* folded: the names are HIDDEN, never removed - their checkboxes stay in the form, so the
   band's own heading (with its count) is all that is left to see */
table.companies.cov tbody.folded > tr:not(.secrow) { display: none; }
table.companies.cov td.num { width: 4.5rem; text-align: right; font-family: var(--mono);
  font-variant-numeric: tabular-nums; font-size: .82rem; white-space: nowrap; }
table.companies.cov td.num.zero { color: var(--dim); }
/* unticked-but-unsaved: the row sinks to the bottom and reads as leaving, without vanishing —
   nothing is written until Save coverage, and re-ticking puts it back where it was sorted */
table.companies.cov tr.dropped td, table.companies.cov tr.dropped td a { color: var(--dim); }
/* on a phone the three figure columns are what push the table past the viewport — tighten them
   rather than drop one: a column you cannot see is worse than a narrow one */
@media (max-width: 640px) {
  table.companies.cov th, table.companies.cov td { padding-left: .2rem; padding-right: .2rem; }
  table.companies.cov td.num { width: 3.1rem; font-size: .76rem; }
  table.companies.cov th { font-size: .56rem; letter-spacing: .06em; }
}

/* ── banners, verify, quotes ────────────────────────────────────── */
.banner { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  flex-wrap: wrap; padding: .6rem .9rem; border-radius: .4rem; margin: .8rem 0; font-size: .9rem; }
.banner.draft { background: var(--accent-soft); border: 1px solid var(--accent-line); color: var(--accent); }
.banner.stale { background: var(--warn-soft); border: 1px solid rgba(229, 192, 123, .32); color: var(--warn); }
.verify { font-size: .82rem; margin: .3rem 0; }
.verify.match { color: var(--ok); }
/* a pass, but on the dateline rather than the quote — same family as match, quieter */
.verify.dateline { color: var(--ok); }
.verify.mismatch { color: var(--err); }
.verify.review { color: var(--warn); }

/* Thesis analyzer: verdict chips + input form */
.verdict { font-family: var(--mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .06em;
           padding: .18rem .5rem; border-radius: .3rem; border: 1px solid var(--line); margin-left: .35rem; }
.verdict.supports  { color: var(--ok);   background: var(--ok-soft);   border-color: rgba(138, 223, 138, .38); }
.verdict.conflicts { color: var(--err);  background: rgba(223, 138, 138, .10); border-color: rgba(223, 138, 138, .38); }
.verdict.mixed, .verdict.neutral, .verdict.unclear { color: var(--warn); background: var(--warn-soft); border-color: rgba(229, 192, 123, .32); }


/* R12 supply-chain landscape graph */
/* the flow diagram breaks out of the 960px column to the full content area (viewport minus
   the sidebar) — a supply chain reads left-to-right and needs the room; the surrounding
   text/forms stay in the column. */
.graphwrap { position: relative; left: 50%; transform: translateX(-50%);
  width: calc(94vw - var(--sbw)); max-width: 1640px; }
#cy { height: 72vh; min-height: 440px; background: var(--panel); border: 1px solid var(--line); border-radius: .5rem; }
.cytip { position: fixed; display: none; z-index: 60; max-width: 260px; pointer-events: none;
         background: var(--panel); border: 1px solid var(--line); border-radius: .4rem;
         padding: .5rem .6rem; font-size: .8rem; box-shadow: 0 6px 18px rgba(0, 0, 0, .62), 0 0 0 1px rgba(188, 204, 226, .10); }
.cytip .t { font-family: var(--mono); color: var(--dim); font-size: .72rem; }
.cytip .c { color: var(--dim); font-size: .74rem; margin-top: .15rem; }
.cytip ul { margin: .3rem 0 0; padding-left: 1rem; }
.cytip li { margin: .1rem 0; }
.cytip .note { color: var(--ink); font-size: .78rem; line-height: 1.35; margin-top: .3rem; }
.legend .grp { display: inline-block; width: 14px; height: 10px; border-radius: 2px;
  background: var(--accent-soft); border: 1px solid var(--accent-line); vertical-align: middle; margin-right: .25rem; }
.legend { display: flex; flex-wrap: wrap; gap: .9rem; align-items: center; margin: .6rem 0 1rem;
          font-size: .78rem; color: var(--dim); }
.legend .dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; vertical-align: middle; margin-right: .25rem; }
.legend .dot.component { background: var(--accent); }
.legend .dot.company { background: var(--live); border-radius: 2px; }

/* K3 Tier 3 — the knowledge map's interaction layer (docs/knowledge-base-plan.md).
   The chrome floats OVER the stage instead of sitting under it: the map is already 74vh of
   black, and a control strip below the fold is a control strip nobody uses. TWO rails, both
   anchored to .graphwrap (position: relative, above) — the right one stacks search over the
   inspect panel (one column, one right edge, the box you type in always above the thing it
   selects), the left one holds the sector browser. */
.maprail { position: absolute; top: .6rem; right: .6rem; width: 19rem; z-index: 3;
  display: flex; flex-direction: column; gap: .4rem; max-height: calc(100% - 1.2rem); }
/* relative, not absolute: the RAIL owns the width and the right edge now, and .maphits
   hangs off this box */
.mapsearch { position: relative; }
.mapsearch .mapq { width: 100%; background: rgba(13, 14, 16, .92); font-size: .82rem; }
/* the hits OVERLAY the panel beneath instead of pushing it down the rail — typing with a
   node inspected must not walk its panel off the bottom of the stage */
.maphits { position: absolute; top: calc(100% + .3rem); left: 0; right: 0; z-index: 4;
  padding: .25rem; max-height: 15rem; overflow-y: auto;
  background: rgba(13, 14, 16, .96); border: 1px solid var(--line); border-radius: .4rem;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .7); }
.maphits p.ro { margin: .25rem .4rem; }
/* 19rem (was 17): the company quick-look renders news headlines now, and 17 wrapped nearly
   every one to three lines. The width lives on .maprail now; min-height:0 is what lets a
   long panel shrink inside that flex column instead of overflowing the stage. */
.mappanel { position: relative; min-height: 0; overflow-y: auto; padding: .7rem .8rem;
  background: rgba(13, 14, 16, .96); border: 1px solid var(--line); border-radius: .5rem;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .7); }
/* the sector browser, in the corner search used to hold. Narrower than the inspect rail:
   it carries names and counts, never news headlines. */
.mapsectors { position: absolute; top: .6rem; left: .6rem; width: 15rem; z-index: 3;
  padding: .55rem .6rem; max-height: calc(100% - 1.2rem); overflow-y: auto;
  background: rgba(13, 14, 16, .96); border: 1px solid var(--line); border-radius: .5rem;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .7); }
.mapsectors h2 { margin: 0; font: 600 .68rem/1.4 var(--mono); text-transform: uppercase;
  letter-spacing: .07em; color: var(--dim); }
.mapsectors p.ro { margin: .2rem 0 .45rem; font-size: .66rem; line-height: 1.45; }
.secgroup > summary { cursor: pointer; padding: .28rem .1rem; font-size: .8rem;
  border-radius: .3rem; color: var(--ink); }
.secgroup > summary:hover { color: var(--accent); background: var(--accent-soft); }
.secgroup[open] > summary { font-weight: 600; }
/* beats `.mapsectors p.ro` above — the count rides INSIDE the summary line, not under it */
.secgroup > summary .ro { margin-left: .35rem; font-size: .68rem; }
.secgroup .mapnbrs { padding-left: .45rem; }
.mappanel h2 { font-size: .92rem; line-height: 1.35; margin: 0 1.4rem .15rem 0;
  overflow-wrap: anywhere; }
.mappanel p { margin: .1rem 0 .45rem; }
.mappanel p.maphead { margin: .6rem 0 .3rem; padding-top: .5rem;
  border-top: 1px solid var(--line-soft); }
.mapx { position: absolute; top: .3rem; right: .35rem; padding: .2rem .3rem; border: 0;
  background: none; color: var(--dim); font: inherit; line-height: 1; cursor: pointer; }
.mapx:hover { color: var(--ink); }
.maplink { font-size: .8rem; }
/* onboarding a peer straight off the map: the button leads, the cost note follows it so
   the analyst reads "what this does" before "what it spends" */
.mapadd { display: flex; flex-direction: column; align-items: flex-start; gap: .3rem;
  margin: .1rem 0 .45rem; }
/* the author `display` above beats the UA's `[hidden] { display: none }` — the same trap
   patched for .estcell, .btn, .maphits and .mappanel. Without this the panel's
   addwrap.hidden = true is dead code and the billed "+ Add to coverage" button renders on
   every node, including companies already in coverage. */
.mapadd[hidden] { display: none; }
.mapadd .ro { font-size: .72rem; line-height: 1.45; }
.mapnbrs { display: flex; flex-direction: column; gap: .1rem; }
.maphit, .mapnbr { display: flex; width: 100%; gap: .5rem; align-items: baseline;
  justify-content: space-between; padding: .22rem .4rem; border: 0; border-radius: .3rem;
  background: none; color: var(--ink); font: inherit; font-size: .8rem; text-align: left;
  cursor: pointer; }
.maphit:hover, .mapnbr:hover { background: var(--accent-soft); color: var(--accent); }
.maphit .nm, .mapnbr .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.maphit .ro, .mapnbr .ro { flex-shrink: 0; font-size: .68rem; }
/* the company quick-look (Milestones + News) inside the inspect panel: date gutter,
   wrapping body, quiet status tag. Links inherit ink and only tint on hover — a panel
   of accent-blue headlines would drown the "Open company page" affordance above it. */
.peekrow { display: flex; gap: .45rem; align-items: baseline; padding: .16rem 0;
  font-size: .78rem; line-height: 1.4; }
/* a FIXED date gutter, not a content-sized one: `when` is either an ISO date
   (2026-03-15, 10 chars) or free text off the catalyst ("Q1 2027"), and sizing the
   column to each row's own string started every short date's body further left than
   its neighbours'. 10.5ch of this span's own mono face holds the ISO date with a
   hair to spare; anything longer wraps INSIDE the gutter so the bodies stay aligned. */
.peekrow .pk-when { flex: 0 0 10.5ch; max-width: 10.5ch; color: var(--dim);
  font-family: var(--mono); font-size: .67rem; overflow-wrap: anywhere; }
.peekrow .pk-text { min-width: 0; overflow-wrap: anywhere; }
.peekrow .pk-text a { color: var(--ink); }
.peekrow .pk-text a:hover { color: var(--accent); }
.peekrow .pk-tag { flex: none; margin-left: auto; font: 600 .6rem/1.7 var(--mono);
  color: var(--dim); text-transform: uppercase; letter-spacing: .05em; }
/* an author `display` beats the UA's `[hidden] { display: none }` (see .estcell above) */
.maphits[hidden], .mappanel[hidden] { display: none; }
.mapfilter .fbox { display: inline-flex; align-items: center; gap: .3rem; cursor: pointer;
  -webkit-user-select: none; user-select: none; }
.mapfilter input[type=checkbox] { margin: 0; cursor: pointer; }
@media (max-width: 720px) {
  .maprail { width: 13rem; }
  .mapsectors { width: 11rem; }
}
.eln { display: inline-block; width: 16px; border-top: 2px solid var(--dim); vertical-align: middle; margin-right: .3rem; }
.eln.competes { border-top-style: dashed; border-top-color: var(--err); }
.eln.part_of { border-top-color: #4a505a; }
.nodecard summary { cursor: pointer; }
/* review list nested like the graph: each component box heads its companies */
.cathead { margin: 1.1rem 0 .35rem; padding-bottom: .28rem; border-bottom: 1px solid var(--line);
  font: 600 .72rem/1.4 var(--mono); text-transform: uppercase; letter-spacing: .07em;
  color: var(--accent); border-left: 3px solid var(--accent); padding-left: .5rem; }
.cathead .n, .groupbox .n { color: var(--dim); font-weight: 400; letter-spacing: 0; }
.groupbox { border-left: 3px solid var(--accent-line); margin-bottom: .3rem; }
.groupbox > summary { font-weight: 600; }
.groupchildren { margin: 0 0 1rem 1.15rem; padding-left: .6rem; border-left: 1px solid var(--line); }
.groupchildren .nodecard { margin: .3rem 0; }
.metriclist, .edgelist { margin: .3rem 0; padding-left: 1.1rem; }
.metriclist li, .edgelist li { margin: .18rem 0; }
.metriclist.model { list-style: none; padding-left: 0; }
.mtag { font: 600 .58rem/1 var(--mono); text-transform: uppercase; letter-spacing: .06em;
  color: var(--accent); background: var(--accent-soft); border: 1px solid var(--accent-line);
  padding: .1rem .32rem; border-radius: .22rem; margin-right: .3rem; }
/* edges read as a sentence; the proposed tag + remove sit right-aligned, out of the way */
.edgelist { list-style: none; padding-left: 0; }
.edgelist li { display: flex; justify-content: space-between; align-items: center; gap: .8rem;
  margin: 0; padding: .32rem 0; border-top: 1px solid var(--line-soft); }
.edgelist li:first-child { border-top: none; }
.edgemain { min-width: 0; }
.edgeside { display: flex; align-items: center; gap: .45rem; flex-shrink: 0; }
.arow { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin-top: .5rem; }
.arow input, .arow select { flex: 1 1 9rem; }
/* R10: a staged driver-update proposal nested under its driver row */
.proposal-row td { background: var(--accent-soft); border-top: 1px dashed var(--line); font-size: .86rem; }
.quote { font-family: var(--sans); font-style: italic; font-size: .95em; color: var(--ink);
  margin: .35rem 0; padding-left: .7rem; border-left: 2px solid var(--accent-line); }
.meta { font-size: .78rem; color: var(--dim); }

/* ── programs editor rows ───────────────────────────────────────── */
.prog { display: grid; grid-template-columns: repeat(6, 1fr) auto; gap: .35rem;
  padding: .4rem 0; border-top: 1px solid var(--line-soft); align-items: center; }
.prog input { min-width: 0; }
/* one-Save editors (company.html drivers + programs): a row is six inputs owned by the editor's
   form (form="…") plus its own tiny remove form, so Enter in any input saves everything and
   the remove is a separate submit. The remove surfaces on hover / keyboard focus, like every
   other row action; Save sits once under the rows (.rowsfoot). */
.prog > form { margin: 0; display: flex; }
.prog .rm { visibility: hidden; }
.prog:hover .rm, .prog:focus-within .rm { visibility: visible; }
.rowsfoot { display: flex; align-items: center; gap: .6rem; margin-top: .5rem; }
.assetblock { padding: .6rem 0; border-top: 1px solid var(--line-soft); }
.assetgrid { display: grid; grid-template-columns: repeat(4, 1fr) auto; gap: .35rem; align-items: end; }
.assetgrid label { display: flex; flex-direction: column; gap: .2rem; font-size: .74rem; color: var(--dim); }
.assetgrid input { min-width: 0; }

/* ── side-by-side draft review ──────────────────────────────────── */
.sxs { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; }
.sxs .col label { font: 600 .66rem/1.6 var(--mono); color: var(--dim); display: block;
  margin-bottom: .3rem; text-transform: uppercase; letter-spacing: .07em; }
.sxs .curtext { background: var(--panel2); border: 1px solid var(--line); border-radius: .375rem;
  padding: .5rem .6rem; color: var(--dim); min-height: 3.2rem; font-family: var(--sans); }

/* ── news / social feed rows ────────────────────────────────────── */
.newsrow { background: var(--panel); border: 1px solid var(--line); border-radius: .5rem;
  padding: .85rem 1.05rem; margin: .55rem 0; }
.newshead { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
/* social cards date-stamp to the right edge of the head row (news cards keep theirs inline
   next to the company, so this is its own class rather than a rule on .raised) */
.newshead .postdate { margin-left: auto; margin-right: 0; }
.summary { margin: .3rem 0; color: var(--ink); overflow-wrap: anywhere; }
/* a title-less post's body IS its link to the source — it must still read as body text,
   so it keeps the ink colour and only underlines on hover. Same on the News tab, where the
   quote (or the headline, when there's no quote) carries the link instead of a source button. */
/* "the text is the link": ink at rest, underline on hover (the global a:hover) — a table
   of accent-blue titles would shout, and the catalyst title is the row's identity first
   and a source link second. .cats joins the News/quote surfaces on the same rule. */
.summary a.bodylink, .quote a.bodylink, .detail a.bodylink,
.cats a.bodylink { color: inherit; }
.detail, .quote, .askpost { overflow-wrap: anywhere; }

/* ── one post, read at full length (social_post.html) ───────────────────────
   The feed card shows 280 characters of a body that averages ~4,000, so this page exists to
   print the rest. Everything here serves the read: a measure narrow enough to track line to
   line, air between the lines, and the chrome (byline, out-link, chips) pushed to the edges
   of the text rather than through it. */
.postpage { max-width: 78ch; margin: .3rem 0 2.5rem; }
.posttitle { text-wrap: balance; overflow-wrap: anywhere; }
.postmeta { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  padding-bottom: .7rem; border-bottom: 1px solid var(--line-soft); }
.postmeta .postdate { margin-left: auto; margin-right: 0; }
/* Taller line-height than the app default (1.62) and a real gap between paragraphs: at this
   measure the two together are the difference between scanning and reading. The template
   emits one <p> per source line (see its comment on why not pre-wrap). */
.postbody { overflow-wrap: anywhere; line-height: 1.85;
  margin: 1.1rem 0 1.6rem; color: var(--ink); }
.postbody p { margin: 0 0 1.05em; }
.postbody p:last-child { margin-bottom: 0; }
/* Said where the text runs out, so a cut never reads as the author trailing off. */
.posttrunc { margin: -.6rem 0 1.4rem; padding-left: .7rem;
  border-left: 2px solid var(--line); font-style: italic; }
/* The way out sits UNDER the text: you press it having read enough to want the original. */
.postout { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding-top: 1rem; border-top: 1px solid var(--line-soft); }
.postout .actions { margin-left: auto; display: inline-flex; gap: .35rem; }
.postout .actions form { display: inline; }
.postmatch { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin-top: 1rem; }
/* .tk is scoped to .audioep in _audio.html, so the chips are defined here rather than
   inherited - same shape and the same two tones, kept deliberately identical to that set. */
.postmatch .tk { font: 600 .74rem/1 var(--mono); padding: .24rem .5rem; border-radius: 4px; }
.postmatch a.tk:hover { text-decoration: none; border-color: var(--accent); }
.postmatch .tk.primary { color: var(--accent); background: var(--accent-soft);
  border: 1px solid var(--accent-line); }
.postmatch .tk.muted { color: var(--dim); border: 1px solid var(--line); font-weight: 500; }

/* ── the same post, read without leaving the feed (social.html's dialog, SO-1) ──────
   It is built out of the reading page's OWN parts - .postmeta, .postbody, .posttrunc,
   .postout above - so the two surfaces cannot drift apart, and everything here is only what
   a box needs that a page does not: its own measure inside 46rem of dialog, and the air the
   page got from the space around it. */
.postdlg .modalbody { padding: .9rem 1.2rem 1.15rem; }
.postdlg .postmeta { padding-bottom: .65rem; }
/* The SAME measure the reading page uses (.postpage, 78ch), not the width of the box: 46rem
   of unbroken line is wider than the eye tracks back across, and matching the page is what
   makes this read as the post rather than as a panel that happens to hold text. */
.postdlg .postbody { max-width: 78ch; margin: 1rem 0 1.3rem; }
.postdlg .posttrunc { max-width: 78ch; margin: -.75rem 0 1.3rem; }
/* The out-link row needs nothing but its own air: the votes are cloned off the card, so
   they land here as a plain `.actions` cluster that .postout above already lays out the way
   the full page does, and the feed's hide-until-hover rule is scoped to
   `.feedrows .cardfoot`, which the dialog is outside of. */
.postdlg .postout { padding-top: .85rem; }

.newsrow .meta { display: flex; align-items: center; gap: .8rem; margin-top: .4rem; flex-wrap: wrap; }
/* News scroll-drain: unread is a dot, not a filter. The row never moves — scrolling past it
   clears the mark (and Home's "N new"), so nothing disappears from under the analyst. */
.unreaddot { display: none; width: .45rem; height: .45rem; border-radius: 50%;
  background: var(--accent); flex: 0 0 auto; }
.newsrow.unread .unreaddot { display: inline-block; }
.newsrow.unread .detail { font-weight: 600; }
/* The match line is prose — links joined by "," and "·". As a flex row every bare separator
   became its own flex item with a .8rem gap on EACH side, so a comma floated away from the
   word it belongs to and could even wrap onto a line by itself. Keep .meta's type, drop the
   flex; the sibling .meta (the open link + right-aligned actions) still needs it. */
.newsrow .meta.matchline { display: block; }
.newsrow .actions { display: inline-flex; gap: .35rem; margin-left: auto; flex-wrap: wrap; }
.newsrow .actions form { display: inline; }
/* feed vote pair: the cast vote lights up (and clicking it again clears it) */
/* one footer row per card: match line left, votes pinned right (they were stacked divs, so
   the votes sat on an empty line of their own) */
/* nowrap + min-width:0 is what keeps the votes ON this row: a long match list wraps INSIDE
   its own cell instead of shoving the buttons onto a line of their own */
.cardfoot { display: flex; align-items: center; gap: .8rem; flex-wrap: nowrap; margin-top: .45rem; }
.cardfoot .meta { margin-top: 0; min-width: 0; }
.cardfoot .actions { margin-left: auto; display: inline-flex; gap: .3rem; flex: none; }
.cardfoot .actions form { display: inline; margin: 0; }
/* vote thumbs: outline + grey until cast, then filled and coloured (green up / red down).
   Hover previews the colour, so the affordance reads before you commit. */
.btn.vote { padding: .26rem .42rem; line-height: 0; color: var(--dim); }
.btn.vote .bowl { fill: none; }
.btn.vote.on .bowl { fill: currentColor; }
/* background-color (longhand) so hovering an already-cast vote can't have `.btn:hover`'s
   `background` shorthand reset the other background longhands out from under it */
.btn.vote.up:hover, .btn.vote.up.on { color: var(--ok);
  border-color: rgba(138, 223, 138, .48); background-color: var(--ok-soft); }
.btn.vote.down:hover, .btn.vote.down.on { color: var(--err);
  border-color: rgba(223, 138, 138, .48); background-color: var(--err-soft); }
.feedfoot { display: flex; align-items: center; gap: .8rem; margin: 1rem 0; }

/* ── stepped wizard (one form, one panel at a time) ─────────────────── */
/* agent_new's in-page wizard. The step machinery is JS-OPT-IN (.wiz.on): with JS off none of
   the rules below apply, so every panel and the submit button stay visible and the page is
   just a plain long form. Page-specific pieces (agent_new's mode radios, chip rows, recap
   panel) stay in that template.
   The earnings-memo dialog used to share this block; it is a nine-page walkthrough now
   (memo_new.html, "── the earnings-memo walkthrough" below), which needs no step machinery
   at all - one page IS one step, so there is nothing for JS to switch. */
.wiz .wizbar, .wiz [data-wizback], .wiz [data-wiznext] { display: none; }
.wiz.on .wizbar { display: flex; }
/* :not([hidden]) — else this reveal rule outranks the hidden attribute's UA display:none
   and the step's Continue button stays on screen next to the final submit. */
.wiz.on [data-wizback]:not([hidden]), .wiz.on [data-wiznext]:not([hidden]) {
  display: inline-flex;
}
.wizbar { gap: .3rem; margin: .1rem 0 1rem; }
.wizbar i { flex: 1; height: 3px; border-radius: 2px; background: var(--line); }
.wizbar i.on { background: var(--accent); }
.wiz.on .wizstep { display: none; }
.wiz.on .wizstep.on { display: block; }
.wizstep + .wizstep { margin-top: 1.1rem; }
.wiz.on .wizstep + .wizstep { margin-top: 0; }
.wizstep h2 { margin: 0 0 .2rem; font-size: 1.15rem; }
.wizstep .hint { margin: 0 0 .9rem; }
.wizstep input[type=text], .wizstep textarea { width: 100%; }
.wizfoot { display: flex; align-items: center; gap: .5rem; margin-top: 1.1rem; }
.wizfoot .ro { margin-right: auto; }

/* resolve_kpis' own warnings, on a page before a billed run - an `add:` key that beats an
   inherited one, an unknown sector, an override that matched nothing. */
.estwarn { margin: .1rem 0 .8rem; padding: .5rem .7rem; border-radius: 6px;
  border: 1px solid rgba(229, 192, 123, .45); background: var(--warn-soft, rgba(229, 192, 123, .10));
  color: var(--ink); font-size: .88rem; }

/* ── the earnings-memo walkthrough (/memos/new) ─────────────────────── */
/* Nine full-size pages, one field each, with a rail on the left that doubles as the recap.
   NO step machinery: a page is a step, so nothing here is JS-opt-in and nothing is hidden
   from the analyst by a class. The rail is the only chrome the pages share. */
.wizcols { display: grid; grid-template-columns: 13.5rem minmax(0, 1fr); gap: 1.4rem;
  align-items: start; margin-top: 1rem; }
/* Under ~760px the rail stops being a rail: it goes above the page as a compact progress
   strip rather than squeezing the field being asked into half a column. */
@media (max-width: 760px) {
  .wizcols { grid-template-columns: minmax(0, 1fr); gap: .8rem; }
  .steprail { position: static; max-height: none; }
  .steprail ol { display: flex; flex-wrap: wrap; gap: .1rem .3rem; }
  .steprail .sv { display: none; }        /* the readbacks need the vertical room */
}
.steprail { position: sticky; top: 1rem; max-height: calc(100vh - 2rem); overflow-y: auto;
  overscroll-behavior: contain; }
.steprail ol { list-style: none; margin: 0; padding: 0; }
.steprail li { margin: 0; }
.steprail a, .steprail .rowspan { display: grid; grid-template-columns: 1.35rem minmax(0, 1fr);
  gap: 0 .45rem; padding: .34rem .5rem; border-radius: .35rem; color: var(--dim);
  font-size: .85rem; align-items: baseline; }
.steprail a:hover { background: var(--accent-soft); color: var(--accent);
  text-decoration: none; }
.steprail .sn { font: 600 .64rem/1.7 var(--mono); color: var(--dim);
  text-align: right; font-variant-numeric: tabular-nums; }
.steprail .sl { min-width: 0; overflow-wrap: anywhere; }
/* The answer, read back under its own step. This is what makes the rail a recap instead of a
   position indicator - without it a nine-page flow costs five page-opens to remember what you
   picked. Truncated to one line: it is a reminder, and the page itself is the full answer. */
.steprail .sv { grid-column: 2; font: .72rem/1.4 var(--mono); color: var(--dim);
  opacity: .8; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.steprail li.done .sn { color: var(--accent); }
.steprail li.done .sl { color: var(--ink); }
.steprail li.on .rowspan { background: var(--accent-soft); }
.steprail li.on .sl { color: var(--accent); font-weight: 600; }
.steprail li.on .sn { color: var(--accent); }
/* A step past the frontier is TEXT, not a dead link: its page is built from the answers before
   it and cannot be drawn yet, so it must not look pressable. */
.steprail li.off .rowspan { opacity: .45; }
.railfoot { margin: .7rem 0 0; padding: .5rem .5rem 0; border-top: 1px solid var(--line); }
.railfoot .linklike { font-size: .78rem; color: var(--dim); }
.railfoot .linklike:hover { color: var(--err); }

.wizpage { margin: 0; }
.wizpage > .hint:first-child { margin-top: 0; }
.wizform textarea { width: 100%; }
.wizform input[type=text], .wizform input[type=search] { width: 100%; }
/* One field per page means the field can have room: labels stack over their input rather than
   sharing a line with it. */
.wizfield { display: flex; flex-direction: column; gap: .25rem; font-size: .85rem;
  color: var(--dim); max-width: 22rem; }
.wizpagefoot { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-top: 1.3rem; padding-top: .9rem; border-top: 1px solid var(--line); }
.wizpagefoot .ro { margin-right: auto; max-width: 44ch; }
/* Back is a LINK (it navigates and saves nothing) and sits left of the submits. */
.wizpagefoot a.btn { order: 1; }
.wizpagefoot button { order: 2; }
.wizpagefoot .btn.primary { order: 3; }
/* A footer control that asks before it commits is its OWN form, because the confirm is read
   off the form and the press beside it must not inherit it. The wrapper takes the order the
   bare button would have had, so the row still reads Back, destructive, primary. */
.wizpagefoot form { display: flex; order: 2; }

/* Quick-jump dots at the foot of the page - a compact, horizontal echo of the rail. */
.stepdots { display: flex; justify-content: center; align-items: center; gap: .5rem;
  flex-wrap: wrap; margin: 1.5rem auto 0; padding-top: 1.1rem;
  border-top: 1px solid var(--line-soft); }
.stepdot { width: 1.6rem; height: 1.6rem; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font: 600 .68rem/1 var(--mono); text-decoration: none;
  border: 1px solid var(--line); background: var(--panel); color: var(--dim);
  transition: border-color .12s, background .12s, color .12s; }
a.stepdot:hover, a.stepdot:focus-visible { border-color: var(--accent); color: var(--accent);
  background: var(--accent-soft); }
.stepdot.done { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.stepdot.on { border-color: var(--accent); background: var(--accent); color: var(--on-accent);
  box-shadow: 0 0 0 3px var(--accent-soft); }
/* a page past the frontier can't be drawn yet, so its dot is dimmed and inert, matching the
   rail's `off` rows */
.stepdot.off { opacity: .4; cursor: default; }
/* Which cell came from the profile rather than from this draft, so a pre-filled number is
   never a number with no account of where it came from. */
.estcell .estfrom { font: .64rem/1.4 var(--mono); color: var(--dim); opacity: .75; }
.recap dd.warn { color: var(--warn); }

/* ── the memo fork: two roads, two targets ──────────────────────────── */
/* This page collects no field of the run, so it has no rail and no Continue: whichever card
   is pressed IS the answer. They are <button>s inside their own forms rather than links,
   because each one commits a choice to the draft. Equal width and equal height on purpose -
   a smaller card would read as the lesser option, and neither is. */
.forkgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem;
  margin-top: 1.2rem; align-items: stretch; }
.forkgrid form { display: flex; }
@media (max-width: 700px) { .forkgrid { grid-template-columns: minmax(0, 1fr); } }
.forkcard { display: flex; flex-direction: column; gap: .3rem; width: 100%; text-align: left;
  padding: 1.15rem 1.25rem 1.05rem; border: 1px solid var(--line); border-radius: .55rem;
  background: var(--panel); color: var(--ink); cursor: pointer; font: inherit;
  transition: border-color .12s, background .12s; }
.forkcard:hover, .forkcard:focus-visible { border-color: var(--accent);
  background: var(--accent-soft); }
.forkcard .fk-eyebrow { font: 600 .62rem/1.5 var(--mono); letter-spacing: .11em;
  text-transform: uppercase; color: var(--dim); }
.forkcard:hover .fk-eyebrow, .forkcard:focus-visible .fk-eyebrow { color: var(--accent); }
.forkcard .fk-title { font-family: var(--serif); font-weight: 700; font-size: 1.28rem;
  letter-spacing: -.04em; margin-bottom: .25rem; }
.forkcard .fk-body { font-size: .88rem; color: var(--dim); line-height: 1.5; }
/* margin-top:auto pins the footnote to the bottom of BOTH cards, so two blurbs of different
   length still line their last line up */
.forkcard .fk-foot { margin-top: auto; padding-top: .7rem; font: .74rem/1.5 var(--mono);
  color: var(--dim); opacity: .8; }

/* the drop-in page: no rail, so no gutter beside it */
.importpage { max-width: 46rem; margin-top: 1rem; }
/* the derived template, shown before it can matter - scrolls rather than stretching the page */
.tplpreview { margin: .5rem 0 0; padding: .7rem .8rem; border-radius: .4rem;
  background: var(--panel2); border: 1px solid var(--line); color: var(--dim);
  font: .78rem/1.6 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere;
  max-height: 22rem; overflow-y: auto; }
.importpage input[type=file] { font-size: .85rem; color: var(--dim); }
/* One confirmable section per row. NOT .corow: that one is a pointer-cursor pick row whose
   inputs are checkboxes pinned at their natural width, and here the input IS the row's
   content and has to take the space left over.
   Named for this feature and SCOPED to its page, both. An unscoped `.secrow` was already
   the Coverage list's sector band <tr> (companies.html), so a bare `display:flex` here took
   those table rows out of the table's row model on the default view of a page this feature
   never touched. Reusing a neighbour's shape is fine; reusing its selector is not. */
.importpage .memosecrow { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding: .35rem .1rem; border-top: 1px solid var(--line-soft); }
.importpage .memosecrow:first-of-type { border-top: none; }
.importpage .memosecrow .tk { flex: 0 0 1.6rem; font-family: var(--mono);
  font-size: .78rem; color: var(--dim); }
/* min-width:0 so a long heading shrinks the box instead of pushing the row controls off the
   card, and the ops keep one column whatever the name above them is doing */
.importpage .memosecrow input[type=text] { flex: 1 1 12rem; min-width: 0; width: auto;
  font-size: .88rem; }
.importpage .memosecops { margin-left: auto; display: flex; gap: .3rem; flex: none; }
.importpage .memosecops .btn[disabled] { opacity: .35; cursor: default; }
.importpage .memosecadd { display: flex; align-items: center; gap: .5rem;
  margin: .5rem 0 .2rem; }
.importpage .memosecadd input[type=text] { flex: 1 1 12rem; min-width: 0; width: auto;
  font-size: .88rem; }
/* The band header carries the form's DEFAULT submit (see memo_import.html), so it needs the
   room the count alone did not. */
.importpage .coband .bandhd .btn { margin-left: .5rem; }

/* ── the Memos shelf: the drafts in progress ────────────────────────── */
/* One row per draft, because opening a second company's write-up leaves the first standing.
   The readback wraps and the controls do not: an analyst scanning three half-answered drafts
   is looking for the one that is theirs, and a Resume button that shifted column with the
   length of the answer above it is a target they have to hunt for on every row. */
/* The rule goes BETWEEN rows, never on the first one. `:first-of-type` cannot say that here:
   it is scoped to the tag, and the card's first div is the .sechead, so a `.draftrow` is
   never a first-of-type div - the header ended up with a second line drawn under it. */
.draftrow { display: flex; align-items: flex-start; gap: 1rem; flex-wrap: wrap;
  padding: .3rem 0 .8rem; }
.draftrow + .draftrow { padding-top: .8rem; border-top: 1px solid var(--line); }
.draftwhat { flex: 1 1 22rem; min-width: 0; }
.drafthd { display: flex; align-items: baseline; gap: .55rem; flex-wrap: wrap;
  margin-bottom: .15rem; }
/* the one the session is walking, marked rather than reordered: the list stays in
   last-touched order so a row does not move under the pointer when it is resumed */
.draftrow.on .drafthd b { box-shadow: inset 0 -.35em 0 var(--accent-soft); }
.draftacts { display: flex; align-items: center; gap: .5rem; flex: none; margin-left: auto; }

/* ── agent detail: work on the left, configuration on the right ─────── */
/* The rail is a fixed 22rem rather than a fraction: the config fields (skills, sectors, the
   persona box) read at a fixed comfortable width, while the work column absorbs the slack. */
.agentcols { display: grid; grid-template-columns: minmax(0, 1fr) 22rem; gap: 1.4rem;
  align-items: start; }
.agentmain { min-width: 0; }
/* max-height + scroll, not just sticky: a rail taller than the viewport sticks with its lower
   half off-screen, so the Save button under Scope was unreachable through the whole middle of
   the page — you could edit the persona and have nowhere to commit it. */
.agentside { display: flex; flex-direction: column; gap: .8rem; position: sticky; top: 1rem;
  max-height: calc(100vh - 2rem); overflow-y: auto; }
.agentside .sidecard { padding: .8rem .9rem; }
.sideform { display: contents; }   /* one <form>, three cards — Save posts all of it */
.sidehead { display: flex; align-items: center; gap: .5rem; margin-bottom: .45rem; }
.sidehead h3 { margin: 0; font-size: .95rem; }
.sidehead .btn, .sidehead .status { margin-left: auto; }
.sidecard p.ro { margin: 0 0 .5rem; }
.sidecard input[type=text], .sidecard input:not([type]), .sidecard textarea { width: 100%; }
.sidelabel { display: flex; flex-direction: column; gap: .25rem; font-size: .85rem;
  color: var(--dim); margin-bottom: .6rem; }
.sidecard .unichips { max-height: 13rem; overflow-y: auto; }
/* the rail stacks under the work once it can no longer hold two readable columns */
@media (max-width: 1100px) {
  .agentcols { grid-template-columns: 1fr; }
  .agentside { position: static; }
}

.startthread { display: flex; align-items: center; gap: 1rem; padding: .9rem 1rem;
  margin-bottom: 1.2rem; }
.startthread .btn { margin-left: auto; white-space: nowrap; }

/* CSS-only tabs: a radio per view, so Threads/Memory works with JS off and keeps the
   browser's own focus and keyboard handling. */
.agenttabs { margin-top: 1.4rem; }
.agenttabs > input[type=radio] { position: absolute; opacity: 0; pointer-events: none; }
.agenttabs .tabrow { display: flex; gap: .2rem; align-items: flex-end;
  border-bottom: 1px solid var(--line); margin-bottom: .9rem; }
.agenttabs .tabrow label { display: inline-flex; align-items: center; gap: .4rem;
  padding: .45rem .8rem; color: var(--dim); font-size: .92rem; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px; }
.agenttabs .tabrow label:hover { color: var(--accent); }
.agenttabs .tabpane { display: none; }
#av-threads:checked ~ .tabrow label[for="av-threads"],
#av-memory:checked ~ .tabrow label[for="av-memory"] {
  color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }
#av-threads:checked ~ [data-pane="threads"],
#av-memory:checked ~ [data-pane="memory"] { display: block; }

.threadwell { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .45rem; text-align: center; padding: 3.5rem 1rem; border: 1px solid var(--line);
  border-radius: .5rem; background: var(--panel); }
.threadwell p { margin: 0; }

.threadlist { list-style: none; margin: .55rem 0 0; padding: 0;
  display: flex; flex-direction: column; gap: .15rem; }
.threadlist li { display: flex; align-items: baseline; gap: .6rem; padding: .5rem .2rem;
  border-bottom: 1px solid var(--line); }
.threadlist li:last-child { border-bottom: 0; }
.threadlist li .ro { margin-left: auto; white-space: nowrap; font-size: .8rem; }

/* ── in-page view tabs ──────────────────────────────────────────────── */
/* Underlined tabs, deliberately NOT .chip pills: chips are FILTERS elsewhere in the cockpit
   (News categories, Social platforms) and refine one list, while these switch between two
   lists with different content and different actions. Same visual language would say the
   wrong thing. Server-rendered off a query param, so each tab is a real, bookmarkable URL. */
/* wraps rather than overflowing: each .vtab is nowrap by design, so on a phone the row could
   only grow past the viewport. On /audio the third child is a button ("N shows ⚙") and the two
   tabs carry count badges — once a followed show puts a number on Discovered the row ran 2px
   past a 375px viewport and gave the whole page a horizontal scroll. The empty state fitted,
   which is why it stayed hidden. */
.viewtabs { display: flex; flex-wrap: wrap; gap: .2rem; align-items: flex-end;
  margin: 1.1rem 0 .9rem; border-bottom: 1px solid var(--line); }
.vtab { display: inline-flex; align-items: center; gap: .4rem; padding: .45rem .8rem;
  color: var(--dim); font-size: .92rem; border-bottom: 2px solid transparent;
  margin-bottom: -1px; white-space: nowrap; }
.vtab:hover { color: var(--accent); text-decoration: none; }
.vtab.on { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }
/* the count is the reason to switch tabs, so it reads as data, not decoration */
.vtab .n { font: 600 .7rem/1 var(--mono); color: var(--dim); background: var(--panel2);
  border: 1px solid var(--line); border-radius: 9999px; padding: .16rem .42rem; }
.vtab.on .n { color: var(--ink); }
/* something in this tab matches coverage — the one signal worth pulling the eye across */
.vtab .n.alert { color: var(--accent); background: var(--accent-soft);
  border-color: var(--accent-line); }
/* an ACTION parked on the tab row (e.g. the followed-shows settings): sits right, never takes
   the active underline — it opens a dialog rather than switching the view */
.vtab.tabaction { margin-left: auto; background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: .82rem; }
.vtab.tabaction:hover { color: var(--accent); }

/* followed-shows dialog: one row per show, sized for a 38rem modal rather than the
   full-width .filters toolbar (which wrapped every control onto its own line here) */
.addshow { display: flex; gap: .4rem; margin: .2rem 0 .3rem; }
.addshow input { flex: 1 1 auto; min-width: 0; }
.showrow { display: flex; align-items: center; gap: .5rem; padding: .5rem 0;
  border-top: 1px solid var(--line-soft); }
.showrow form { margin: 0; flex: none; }
.showname { min-width: 0; flex: 1 1 auto; }
.showname b { display: block; font-size: .9rem; }
.showname .ro { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.showrow select { font-size: .8rem; padding: .2rem 1.35rem .2rem .35rem; }

/* ── audio: discovered-episode inbox, grouped into per-show dropdowns ─── */
/* Discovered episodes render the SHARED .audioep card (styled in _audio.html's audio_assets
   block) so both lists on the page are one visual system; only the deltas live here. Those
   rules load later in the document, so anything overridden needs 2 classes to win. */
.sechead { display: flex; align-items: baseline; gap: .6rem; margin: 1.5rem 0 .2rem; }
.sechead h2 { margin: 0; }
/* summaries keep their native disclosure marker — `display:flex` on a <summary> removes it,
   so spacing here comes from inline margins, never a flex container */
.recents > summary { cursor: pointer; margin: 1.5rem 0 .2rem; }
/* `details summary` sets color:var(--dim) — an h2 inside it would inherit the muted grey */
.recents > summary h2 { display: inline; margin: 0; color: var(--ink); }
.recents > summary .ro { margin-left: .6rem; }
.showgroup > summary { cursor: pointer; padding: .5rem .1rem; border-radius: .35rem; }
.showgroup > summary .ro { margin-left: .5rem; }
.showgroup > summary:hover { background: var(--accent-soft); }
/* the show's cards sit indented under its dropdown */
.showgroup > .card { margin: .5rem 0 .5rem 1.3rem; }
/* a discovered episode is the readout card minus the readout: same head/id/pills, tighter,
   since there is no analysis body to separate from them yet */
.audioep.discep { gap: .5rem; }
/* Coverage match: a left edge, not a full outline. Down a 15-row inbox the eye follows one
   vertical line far faster than it compares four borders per card, and the card keeps its
   normal frame. A borderline match (competitor read-through) gets the lighter tone rather
   than the full accent — the marker shouldn't claim more than the match does. */
.audioep.discep.matched { border-left: 3px solid var(--accent); }
.audioep.discep.matched.borderline { border-left-color: var(--accent-line); }
/* The episode you were SENT to, from an Inbox open. Same wash as the /news and /social
   landings, but a RING instead of their left bar: the left edge already means "coverage
   match" here, and every episode that raised an inbox signal is one - a bar would be
   invisible against the marker it duplicates. The ring is an outline, so it neither shifts
   the card nor fights the border-left it sits outside of.
   scroll-margin-top matches the feed rows: land the card mid-page with its show heading
   above it, not jammed against the top edge. */
.audioep.discep { scroll-margin-top: 5rem; }
.audioep.discep:target { background: var(--accent-soft);
  outline: 2px solid var(--accent-line); outline-offset: 3px; }
/* separators between id-line parts, so an absent part never leaves a dangling "·" */
.audioep.discep .audioep-id > * + *::before { content: "·"; margin: 0 .4rem; opacity: .5; }
.epstate { font: .72rem/1.5 var(--mono); color: var(--dim); }
.epstate.fail { color: var(--err); }
.epstate.live { color: var(--accent); }
@media (max-width: 720px) {
  .showgroup > .card { margin-left: 0; }
}

/* ── digest ─────────────────────────────────────────────────────── */
.item { padding: .55rem 0; border-top: 1px solid var(--line-soft); }
.item .cat { font: 600 .64rem/1.5 var(--mono); color: var(--accent); text-transform: uppercase;
  letter-spacing: .08em; margin-right: .4rem; }
.item.mgmt { border-left: 2px solid var(--line); padding-left: .6rem; }
.count { float: right; color: var(--dim); font-size: .78rem; font-family: var(--mono); }
.quarantine { border-color: rgba(223, 138, 138, .42); }
.quarantine h2 { color: var(--err); }
.digestfoot { color: var(--dim); font-size: .78rem; font-family: var(--mono); margin: 1rem 0 3rem; }

/* ── ask your feed ──────────────────────────────────────────────── */
.askbox label { font-family: var(--serif); font-weight: 600; font-size: .98rem;
  letter-spacing: -.04em;
  display: block; margin-bottom: .5rem; }
.askrow { display: flex; gap: .5rem; flex-wrap: wrap; }
.askrow input { flex: 1 1 240px; min-width: 0; }
.askresult .criteria { font-size: .9rem; margin-bottom: .5rem; }
/* The BOX only. Everything the agent's own words render as is the .mdbody skin below - this
   used to carry that skin itself, which is why the chat bubble, which is not a summary box,
   had none of it. */
.summarybox { line-height: 1.6; padding: .8rem 1rem; background: var(--panel2);
  border-left: 2px solid var(--accent); border-radius: .375rem; margin: .5rem 0; max-width: 70ch; }

/* ── the mini-markdown skin (md_lite) ───────────────────────────── */
/* One skin for everything an agent WRITES, wherever it is shown: chat replies, summary
   boxes, a theme synopsis, a document's summary. app.py's md_lite emits its whole result as
   one .mdbody and every table inside it as one .mdtable, so a surface is styled by having
   rendered through the filter rather than by remembering to opt in. */
.mdbody p { margin: .5rem 0; }
.mdbody h4 { font: 600 .72rem/1.5 var(--mono); color: var(--accent);
  text-transform: uppercase; letter-spacing: .08em; margin: .8rem 0 .3rem; }
.mdbody ul, .mdbody ol { margin: .4rem 0; padding-left: 1.15rem; }
.mdbody li { margin: .2rem 0; }
.mdbody li::marker { color: var(--accent); }
.mdbody hr { border: none; border-top: 1px solid var(--line-soft); margin: .7rem 0; }
/* the block owns its outer spacing, so the first and last thing the agent wrote sit flush
   with the top and bottom of whatever is holding them (a bubble has no padding to absorb a
   stray leading margin) */
.mdbody > :first-child { margin-top: 0; }
.mdbody > :last-child { margin-bottom: 0; }

/* A table is the one shape that cannot simply reflow, so it gets its own scroll box: at
   380px (the sidebar chat) a six-column table will not fit however it is styled, and the
   choice is scrolling it or letting it push the page sideways. */
.mdtable { overflow-x: auto; margin: .5rem 0; }
.mdtable table { width: auto; max-width: 100%; border-collapse: collapse; font-size: .84rem;
  font-variant-numeric: tabular-nums; }
/* Below a certain width per column shrink-to-fit stops being a fit at all: the browser takes
   the one prose column down to its longest word and pays for it in row HEIGHT, so a six-column
   table in the 380px sidebar came out as five readable cells beside fifteen lines of wrapping.
   Past five columns the table gets a floor and .mdtable scrolls what does not fit, which is the
   honest trade at that width. Keyed on the header's column count so a three-column table is
   never stretched to meet a floor it does not need. Both floors stay under the 46rem chat
   reading column: that is the surface these are read on, and a table it could have FITTED must
   never be the one it has to scroll. */
.mdtable table:has(thead th:nth-child(5)) { min-width: 36rem; }
.mdtable table:has(thead th:nth-child(7)) { min-width: 44rem; }
/* THE column-width rule. A chat bubble sets overflow-wrap: anywhere so a bare URL in prose
   cannot push the column wide; inherited into a cell that makes the browser's min-content
   width for the column ONE CHARACTER, and auto layout then took every short column down to
   nothing to feed the one prose column - "Ticker" came out as "Ti/ck/er" and "$34.2bn" as
   "$34./2bn". `break-word` puts the longest WORD back as each column's floor, which is what
   distributes the width sensibly: the short columns take exactly what they need and the
   prose column absorbs the slack. It still breaks a pathological unbroken token, so the
   reset costs nothing where `anywhere` was earning its keep. */
.mdtable th, .mdtable td { overflow-wrap: break-word; word-break: normal;
  text-align: left; vertical-align: top; padding: .3rem .7rem .3rem 0; }
.mdtable th:last-child, .mdtable td:last-child { padding-right: 0; }
.mdtable th { font: 600 .62rem/1.5 var(--mono); color: var(--accent); text-transform: uppercase;
  letter-spacing: .07em; padding-top: .25rem; padding-bottom: .25rem;
  border-bottom: 1px solid var(--line); }
.mdtable td { border-bottom: 1px solid var(--line-soft); }
/* the separator row's colons (---: and :---:), the only alignment signal a markdown table
   carries - see _md_align */
.mdtable th.r, .mdtable td.r { text-align: right; }
.mdtable th.c, .mdtable td.c { text-align: center; }

.askpost { padding: .5rem 0; border-top: 1px solid var(--line-soft); }
.askpost:first-of-type { border-top: none; }

/* ── misc ───────────────────────────────────────────────────────── */
.search.wide { margin: .6rem 0; }
/* grow into the column instead of sitting at a fixed 420px — the placeholder explains what
   typing an uncovered name does, and at 420px that explanation was the part that got cut. */
.search.wide input { flex: 1 1 420px; min-width: 0; max-width: 100%; }
.notfound { text-align: center; padding: 4rem 0; }
.notfound h1 { font-size: 3rem; }

/* ── usage / token cost history ─────────────────────────────────── */
.tiles .tile { background: var(--panel); border: 1px solid var(--line);
  border-radius: .5rem; padding: .8rem 1rem; }
.breakdowns { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin: .6rem 0 0; }
.mini { width: 100%; border-collapse: collapse; font-size: .82rem; }
.mini td { padding: .28rem 0; border-top: 1px solid var(--line-soft); }
.mini tr:first-child td { border-top: none; }
.mini td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
/* .tablewrap (the scroll box this table needs) is declared once, up in the tables section. */
.usagetbl { width: 100%; border-collapse: collapse; font-size: .84rem; min-width: 640px; }
.usagetbl th { text-align: left; font: 600 .64rem/1.4 var(--mono); color: var(--dim);
  text-transform: uppercase; letter-spacing: .07em; padding: .4rem .6rem; border-bottom: 1px solid var(--line); }
.usagetbl th.num { text-align: right; }
.usagetbl td { padding: .42rem .6rem; border-top: 1px solid var(--line-soft); vertical-align: top; }
.usagetbl td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.usagetbl td.detail-cell { max-width: 22rem; }
.usagetbl .pinnedrow td { background: var(--accent-soft); border-bottom: 1px solid var(--accent-line); }
/* Dashboard: elapsed-since-last-run is the column the page exists for, so it gets the
   tabular mono treatment; a never-run lane reads as absent, not as a value. */
.usagetbl td.lastrun { font-family: var(--mono); font-variant-numeric: tabular-nums;
  white-space: nowrap; }
.usagetbl td.lastrun.never { color: var(--dim); font-style: italic; }
.usagetbl td .ro { display: block; margin-top: .18rem; }
.usagetbl td.detail-cell .ro { display: inline; }
.usagetbl td.detail-cell .ro + .ro { display: block; margin-top: .25rem; }

/* ── chat ───────────────────────────────────────────────────────── */
/* ONE centred reading column. The thread list moved to the nav's history flyout (.histmenu)
   precisely so nothing sits beside the conversation pushing it off the page's centre line. */
.chatmain { min-width: 0; max-width: 46rem; margin: 0 auto; width: 100%; }
/* the head shares the column's edges: page title left, thread actions right */
.chathead { display: flex; align-items: flex-end; justify-content: space-between;
  gap: .9rem; margin: .9rem 0 1.2rem; }
.chathead h1 { margin: 0; font-size: 1.3rem; overflow-wrap: anywhere; }
.chathead .headacts { display: flex; align-items: center; gap: .4rem; flex: none; }
.chathead .headacts form { display: inline; margin: 0; }
/* the landing: everything centred on the composer, which is the only thing to do here */
.chathead.solo { display: block; text-align: center; margin: 0 0 1.1rem; }
.chathead.solo h1 { font-size: 1.85rem; }
.chathead.solo p { max-width: 46ch; margin: .45rem auto 0; }
.nothread { margin-top: 13vh; }
.nothread .chatbox { position: static; }
/* centred, not top-aligned: both sides are a single line now that the prefs inputs moved
   into the gear's dialog — flex-start was there to hold the tall open panel */
.chatfoot { display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin: 1.1rem 0 0; }
/* the gear and its label ARE one control — the whole row-left opens the dialog, so the
   cursor has to say so over the word too, not only over the icon */
.chatfoot .prefsopen { display: flex; align-items: center; gap: .4rem; cursor: pointer; }
.chatfoot .prefsopen:hover .infobtn { color: var(--accent); background: var(--accent-soft); }
.chatfoot .prefsopen:hover .ro { color: var(--accent); }
.chatfoot .agentslink { flex: none; margin-top: 0; }
/* on a thread page it sits ABOVE the log, so the gap belongs under it, not over */
.chathead + .chatfoot, .deskbadge + .chatfoot { margin: 0 0 1.1rem; }
.trace { margin: 0 0 .25rem; font-size: .76rem; }
.trace summary { cursor: pointer; color: var(--dim); font-family: var(--mono);
  font-size: .7rem; }
.trace ol { margin: .3rem 0 .3rem 1.2rem; padding: 0; }
.trace li { margin: .12rem 0; overflow-wrap: anywhere; }
.livesteps { margin: .45rem 0 0 1.2rem; padding: 0; font-size: .76rem;
  font-style: normal; color: var(--dim); }
.livesteps li { margin: .12rem 0; overflow-wrap: anywhere; }
/* the page scrolls, not an inner box — the composer stays pinned below (claude.ai model) */
.chatlog { display: flex; flex-direction: column; gap: 1.05rem; margin: 0 0 1rem; }
.msg { display: flex; flex-direction: column; max-width: 100%; }
.msg.user { align-self: flex-end; align-items: flex-end; max-width: 82%; }
.msg.assistant { align-self: stretch; align-items: stretch; }
.msg .bubble { font-size: .92rem; line-height: 1.55; overflow-wrap: anywhere; }
/* user turns: borderless rounded block; assistant turns: plain prose on the page */
.msg.user .bubble { padding: .6rem .95rem; border-radius: 1.05rem;
  background: var(--accent-soft); white-space: pre-wrap; }
.msg.assistant .bubble { padding: 0; border: none; background: transparent; }
.msg .bubble.thinking { color: var(--dim); font-style: italic; }
.msg .bubble.errbubble { padding: .55rem .8rem; border-radius: .65rem;
  background: var(--err-soft); border: 1px solid rgba(223, 138, 138, .32); color: var(--err); }
/* a reply the analyst stopped: kept, and visibly incomplete. A left rule rather than a
   tint — the prose is real and still worth reading, it just ends mid-thought. */
.msg .bubble.stopped { padding-left: .7rem; border-left: 2px solid var(--line); color: var(--dim); }
/* a reply's headings, lists, rules and tables are the .mdbody / .mdtable skin - the bubble
   itself only sets the reading column's type */
.msgmeta { margin-top: .25rem; }
/* inline citation buttons: the [n] markers ARE the provenance links now (the footer list
   is gone; the label rides in the tooltip). Provenance blue per the token contract — a
   source you can open. .dead = unverifiable ref: visible, struck, unlinked on purpose.
   .citerest catches citations whose marker never appeared in the body. */
.cite { display: inline-block; font-family: var(--mono); font-size: .66rem; line-height: 1;
  padding: .13rem .4rem; margin: 0 .08rem; border: 1px solid var(--accent-line);
  border-radius: 9999px; color: var(--accent); text-decoration: none;
  vertical-align: .16em; font-variant-numeric: tabular-nums; }
a.cite:hover { background: var(--accent-soft); text-decoration: none; }
.cite.dead { border: 1px dashed var(--line); color: var(--dim);
  text-decoration: line-through; cursor: help; }
.citerest { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .4rem; }
.citerest .cite { font-size: .72rem; line-height: 1.25; overflow-wrap: anywhere; }
/* the two prefs inputs inside the gear's dialog: label, field, then the one line that says
   what the field actually does — these are free text steering every turn, and a placeholder
   alone left the analyst guessing which one narrowed anything */
.preffield { display: flex; flex-direction: column; gap: .2rem; margin: .85rem 0;
  font-size: .74rem; color: var(--dim); }
.preffield input { font-size: .85rem; }
.preffield .ro { font-size: .68rem; }
/* desks: composer picker + thread badge (management lives on /agents) */
/* A quiet PILL on the composer card rather than the square field the base rule gives every
   other select — it sits beside Send, so it has to read as one of the composer's own controls.
   Everything else (appearance reset, caret, hover border) comes from the base select rule. */
.deskpick { display: flex; align-items: center; gap: .4rem; font-size: .74rem; }
/* sans, not the label's mono: the value is an agent's NAME, not a metadata readout */
.deskpick select { font-family: var(--sans); font-size: .78rem; max-width: 14rem;
  text-overflow: ellipsis; padding: .24rem 1.55rem .24rem .6rem; border-radius: 9999px;
  background-color: var(--panel2); }
.deskpick select:hover { background-color: var(--accent-soft); }
.deskbadge { margin: 0 0 .5rem; font-size: .78rem; }
.agentslink { display: block; margin-top: .9rem; }
/* refs: what the analyst POINTED at, as chips above the user bubble (right-aligned with
   it) and on the landing composer when /chat?ref= pre-attached an object. Each links to
   the object's own page; the [n] is the number the reply's SOURCES cite it under. */
.axrefs { display: flex; flex-wrap: wrap; gap: .3rem; justify-content: flex-end;
  margin: 0 0 .25rem; max-width: 100%; }
.chatbox .axrefs { justify-content: flex-start; margin: 0 0 .35rem; }
.axref { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font: .7rem/1.4 var(--mono); color: var(--ax);
  border: 1px solid var(--ax-line); background: var(--ax-soft); border-radius: 9999px;
  padding: .08rem .55rem; }
.axref .n { font-weight: 700; margin-right: .3rem; }
a.axref:hover, a.axref:focus-visible { text-decoration: none; color: var(--ink);
  border-color: var(--ax); }

/* ── the pointer layer + the sidebar chat (docs/in-page-agent-plan.md) ── */
/* Point mode: hover outlines a ref, click opens the card. OUTLINE, not box-shadow, so the
   same rule paints a <tr> (Chrome paints no shadow on a table row) and an inline span
   alike; a row's cells take the tint since the row box itself has no background.
   `html body` prefix (0,2,2): the rows' own hover rules are (0,2,1) - a.docrow:hover -
   and sit LATER in this file, so a bare [data-ax].ax-hover (0,2,0) lost the tint to the
   row's blue while the outline (which nothing else sets) painted fine. */
body.ax-point [data-ax] { cursor: crosshair; }
html body [data-ax].ax-hover { outline: 2px solid var(--ax); outline-offset: -1px;
  background: var(--ax-soft); border-radius: .35rem; }
html body [data-ax].ax-selected { outline: 2px solid var(--ax); outline-offset: 1px;
  background: var(--ax-soft); border-radius: .35rem; }
html body tr[data-ax].ax-hover > td, html body tr[data-ax].ax-selected > td {
  background: var(--ax-soft); }
.dlinks [data-ax] { border-radius: .25rem; }
.dlinks [data-ax].ax-hover, .dlinks [data-ax].ax-selected { padding: 0 .2rem; }
/* the card: a heading naming the object, a text box and Send - nothing else (decision 5) */
.axcard { position: fixed; z-index: 45; width: 21rem; max-width: calc(100vw - 1rem);
  background: var(--panel); border: 1px solid var(--ax-line); border-radius: .6rem;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .6); padding: .6rem .7rem .55rem;
  display: flex; flex-direction: column; gap: .5rem; }
.axcard-head { display: flex; align-items: baseline; gap: .5rem; min-width: 0; }
.axkind { flex: none; font: 600 .62rem/1.5 var(--mono); text-transform: uppercase;
  letter-spacing: .08em; color: var(--ax); background: var(--ax-soft); border-radius: .3rem;
  padding: .04rem .4rem; }
.axcard-head b { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* a selected passage, quoted back under the heading. Selecting one is a POINT-MODE
   interaction: the card takes focus, which collapses the live selection, so the pointer tint
   is carried by the highlight API instead for as long as the card is open */
.axcard .axquote { font-size: .78rem; line-height: 1.45; color: var(--dim); font-style: italic;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
::highlight(ax-sel) { background-color: var(--ax-soft); color: inherit; }
.axcard textarea { min-height: 3.2rem; resize: none; background: var(--panel2); }
.axcard textarea:focus { border-color: var(--ax-line); box-shadow: 0 0 0 3px var(--ax-soft); }
.axcard-foot { display: flex; justify-content: flex-end; }
/* the sidebar footer control: Point toggles the mode. Chat's launcher used to share this row
   and left it for the fixed .axlaunch below: the nav fills a 900px window exactly, so this
   footer scrolls below the fold on a laptop screen, and a launcher nobody can see is none. */
.axctl { display: flex; gap: .25rem; margin-top: .2rem; }
.pointctl { display: flex; align-items: center; justify-content: center; gap: .4rem;
  flex: 1 1 0; min-width: 0; margin: 0; background: none; border: 1px solid var(--line-soft);
  border-radius: .45rem; padding: .26rem .45rem; color: var(--dim); font: inherit;
  font-size: .78rem; cursor: pointer; white-space: nowrap; }
.pointctl .icon { width: 14px; height: 14px; }
.pointctl .dot { width: .5rem; height: .5rem; border-radius: 50%; border: 1.5px solid currentColor;
  flex: none; }
.pointctl kbd { font: .62rem/1 var(--mono); color: var(--dim); border: 1px solid var(--line);
  border-radius: .3rem; padding: .12rem .28rem; }
.pointctl:hover { background: var(--accent-soft); color: var(--accent); }
.pointctl[aria-pressed="true"] { background: var(--ax-soft); color: var(--ax);
  border-color: var(--ax-line); }
.pointctl[aria-pressed="true"] .dot { background: currentColor; }
.pointctl[aria-pressed="true"] kbd { color: inherit; border-color: var(--ax-line); }
/* the Chat row's badge: a reply landed while the sidebar was closed. A button (it opens the
   panel), in the pointer hue so it never reads as the mail or the Activity count. */
.navcount.ax { color: var(--ax); background: var(--ax-soft); border-color: var(--ax-line);
  cursor: pointer; font-family: var(--mono); }
.navcount.ax[hidden] { display: none; }
/* The launcher: a fixed pill at the bottom right of every page that carries the shell, so the
   chat is one click away everywhere - the footer's "Chat here" sat at y=891 in a 765px window,
   and the chat could not be found. Layered under the dock (30) and the point card (45), over
   the nav rail (10) and the history flyout (20). Hidden while the dock is open: the dock covers
   that corner and carries its own close. Solid fills at rest and on hover, because it floats
   over page text. */
.axlaunch { position: fixed; right: 1.1rem; bottom: 1.1rem; z-index: 29;
  display: inline-flex; align-items: center; gap: .45rem; padding: .6rem 1rem .6rem .85rem;
  border-radius: 9999px; background: var(--panel); border: 1px solid var(--line);
  color: var(--ink); font: 600 .82rem/1 var(--sans); cursor: pointer;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .72), 0 0 0 1px rgba(188, 204, 226, .10); }
.axlaunch .icon { width: 16px; height: 16px; }
.axlaunch:hover { border-color: var(--accent-line); background: var(--panel2);
  color: var(--accent); }
/* a reply landed while the dock was closed: the pointer hue, the same as the Chat row's badge */
.axlaunch .axdot { width: .55rem; height: .55rem; border-radius: 50%; background: var(--ax);
  box-shadow: 0 0 0 3px var(--ax-soft); }
.axlaunch .axdot[hidden] { display: none; }
body.ax-open .axlaunch { display: none; }
/* the sidebar chat: docked right, above the nav rail and the history flyout, below the
   toasts. The page yields the width while it is open (main recentres in what is left). */
:root { --axw: 380px; }
.axside { position: fixed; top: 0; right: 0; bottom: 0; width: var(--axw); z-index: 30;
  display: flex; flex-direction: column; background: var(--panel);
  border-left: 1px solid var(--line); }
.axside[hidden] { display: none; }
body.ax-open { padding-right: var(--axw); }
body.ax-open .flashes { left: calc(50% + var(--sbw) / 2 - var(--axw) / 2); }
.axbody { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.axhead { position: relative; display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: .35rem .6rem; padding: .7rem .9rem .55rem;
  border-bottom: 1px solid var(--line-soft); }
.axhead .deskbadge { flex: 1 1 100%; margin: 0; }
.axtitle { flex: 1 1 auto; min-width: 0; font: 600 .92rem/1.35 var(--serif);
  letter-spacing: -.03em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.axacts { display: flex; align-items: center; gap: .3rem; flex: none; }
.axacts .histbtn { width: 1.6rem; height: 1.6rem; }
.axhow { font: .7rem/1.5 var(--mono); color: var(--dim); padding: .35rem .9rem;
  border-bottom: 1px solid var(--line-soft); }
.axlog { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding: .8rem .9rem; }
.axlog .chatlog { margin: 0; }
.axlog .msg.user { max-width: 92%; }
.axempty { margin: .4rem 0; font-size: .8rem; }
.axqueue { display: flex; flex-direction: column; gap: .25rem; margin: 0 .9rem .5rem;
  padding: .45rem .6rem; border-radius: .6rem; background: var(--panel2);
  border: 1px dashed var(--line-soft); font-size: .82rem; }
.axqueue .qitem { display: flex; align-items: baseline; gap: .5rem; }
.axqueue .qbody { flex: 1; overflow-wrap: anywhere; }
.axfoot { margin: 0; border-top: 1px solid var(--line-soft); padding: .55rem .9rem .7rem;
  display: flex; flex-direction: column; gap: .4rem; }
.axfoot textarea { min-height: 2.7rem; resize: none; background: var(--panel2);
  border-radius: .5rem; }
.axbar { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.axbar .spend { margin-left: auto; }
.axnote { flex: 1 1 100%; color: var(--err); font-size: .78rem; }
.axnote[hidden] { display: none; }
/* the thread switcher, the history flyout's own look inside the panel head */
.axmenu { position: absolute; right: .7rem; top: 2.6rem; z-index: 6; width: 17.5rem;
  max-height: 60vh; overflow-y: auto; background: var(--panel); border: 1px solid var(--line);
  border-radius: .45rem; padding: .3rem; box-shadow: 0 10px 28px rgba(0, 0, 0, .6); }
.axmenu[hidden] { display: none; }
.axmenu .hhead { font: 600 .62rem/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim); padding: .35rem .5rem .45rem; }
.axmenu .hnone { padding: .4rem .5rem .6rem; color: var(--dim); font-size: .8rem; }
.axmenu button { display: block; width: 100%; text-align: left; background: none; border: 0;
  border-radius: .35rem; padding: .4rem .5rem; color: var(--ink); font: inherit;
  font-size: .8rem; cursor: pointer; }
.axmenu button:hover, .axmenu button.on { background: var(--accent-soft); color: var(--accent); }
.axmenu .tt { display: block; font-weight: 600; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.axmenu .ro { display: block; margin-top: .1rem; font-size: .7rem; }
/* a narrow window: a bottom sheet instead of a dock, and the page yields height, not width */
@media (max-width: 876px) {
  .axside { top: auto; height: 72vh; width: 100%; border-left: none;
    border-top: 1px solid var(--line); border-radius: .8rem .8rem 0 0; }
  body.ax-open { padding-right: 0; padding-bottom: 72vh; }
  body.ax-open .flashes { left: 50%; }
  .axcard { width: calc(100vw - 1rem); }
}

/* ── agents page ────────────────────────────────────────────────── */
/* index: each agent is one clickable row linking to its own page */
.agentrow { display: flex; align-items: center; gap: .8rem; text-decoration: none;
  color: inherit; margin-bottom: .55rem; }
.agentrow:hover { border-color: var(--accent); }
.agentrow h2 { margin: 0 0 .15rem; }
.agentrow .chev { margin-left: auto; font-size: 1.1rem; }
/* detail page: the agent's own run buttons */
.runrow { gap: .5rem; flex-wrap: wrap; }
.deskmem { margin-top: .9rem; border-top: 1px solid var(--line); padding-top: .6rem; }
.memrow { display: flex; align-items: baseline; gap: .45rem; font-size: .85rem;
  margin: .3rem 0; }
.memrow .memtext { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.memadd { display: flex; gap: .45rem; margin-top: .5rem; }
.memadd input { flex: 1; }
.formactions { grid-column: 1 / -1; display: flex; gap: .5rem; }
.stagedfoot { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap;
  margin-top: .3rem; font-size: .78rem; color: var(--dim); }
.stagedfoot .chip { padding: .12rem .5rem; font-size: .72rem; }
.actionrow { display: flex; align-items: center; gap: .5rem; margin-top: .35rem; flex-wrap: wrap; }
/* the agent asked instead of guessing: up to 4 scoped questions, answered with one click */
.qpanel { margin-top: .5rem; }
.qblock { border: 1px solid var(--line); border-radius: 6px; padding: .5rem .65rem .6rem;
  margin: 0 0 .45rem; }
.qblock legend { font-size: .7rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--dim); padding: 0 .3rem; }
.qtext { margin: 0 0 .4rem; font-size: .88rem; }
.qopt { display: flex; align-items: baseline; gap: .45rem; padding: .28rem .35rem;
  border-radius: 5px; cursor: pointer; }
.qopt:hover { background: var(--hover, rgba(127, 127, 127, .1)); }
.qopt input { margin: 0; flex: none; }
.qlabel { font-size: .86rem; }
.qdesc { font-size: .76rem; }
.opfail { color: var(--err); font-size: .82rem; }
.opdone { margin-top: .35rem; }
.oppast { margin-top: .3rem; font-size: .78rem; }
.oppast summary { cursor: pointer; color: var(--dim); font-family: var(--mono); font-size: .7rem; }
.oppast ul { margin: .25rem 0 0 1.1rem; padding: 0; }
.oppast li { margin: .15rem 0; overflow-wrap: anywhere; }
.persplist { margin: .25rem 0 0; font-size: .85rem; }
.persplist dt { font-weight: 600; text-transform: capitalize; margin-top: .4rem; }
.persplist dd { margin: .1rem 0 0; }
/* ONE queue component, two lanes. Chat queues messages behind a running turn and Audio queues
   episodes behind a running analysis; they are the same object - an ordered list of work the
   analyst can reorder out of by dropping - so they read the same. Only the placement differs,
   which is all .chatqueue adds. */
.runqueue { display: flex; flex-direction: column; gap: .3rem; margin: .55rem 0 .45rem;
  padding: .5rem .6rem; border-radius: .8rem;
  background: var(--panel2); border: 1px dashed var(--line-soft); }
.runqueue .qhead { display: block; margin-bottom: .1rem; }
/* held by a stop: the work is waiting on a decision, not on a clock */
.runqueue.held { border-style: solid; border-color: var(--warn-line, rgba(229, 192, 123, .32)); }
.runqueue.held .qhead { color: var(--warn); }
/* Sticky with the composer so the queue never scrolls away from the box that fills it - the
   next thing to be said, in the place the next thing is said. */
.chatqueue { position: sticky; bottom: 5.4rem; z-index: 1; margin-top: 0; }
.qitem { display: flex; align-items: baseline; gap: .5rem; font-size: .86rem; }
.qitem .qbody { flex: 1; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.qitem .qnote { flex: none; max-width: 40%; }
.qitem .qdrop { flex: none; padding: .1rem .35rem; }
/* the place in line, monospaced so 1/2/10 stay in one column down the list */
.qitem .qpos { flex: none; min-width: 1.4em; font-variant-numeric: tabular-nums;
  color: var(--dim); }
/* layout only - the template already carries .ro, which is what dims it. A colour here would
   out-specify that class and make the show name the loudest thing in its own row. Same shape
   as .qnote, chat's equivalent. */
.qitem .qshow { flex: none; }
/* Resume / Drop all, under the list they act on. Centred because Resume is replaced by a line
   of text while a Resume is already on its way, and a stretched label sits at the top of the
   button-height row it shares with Drop all. */
.runqueue .qfoot { display: flex; align-items: center; gap: .4rem; margin-top: .25rem; }
/* Stop sits beside the primary action, quiet but not hidden: it is reached in a hurry, and a
   ghost button that reads as decoration is one nobody finds mid-run. */
.stopbtn { display: inline-flex; align-items: center; gap: .3rem; }
.stopbtn:hover, .stopbtn:focus-visible { color: var(--err); border-color: rgba(223, 138, 138, .45); }
/* the running-lane line on Analyze / Audio / Documents: prose left, Stop right.
   .hint.livehint (0-2-0) so the max-width lift beats .hint's own 68ch — as a flex CONTAINER
   that cap squeezed the sentence into a narrow column with the button stranded beside it.
   The measure moves to the prose itself, which is what it was ever for. */
.livehint { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem; }
.hint.livehint { max-width: none; }
.livehint > span { max-width: 68ch; }
.livehint .inline { flex: none; }

/* floating composer, pinned to the bottom of the viewport while the page scrolls */
.chatbox { position: sticky; bottom: .8rem; border-radius: 1rem; padding: .6rem .75rem;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .62), 0 0 0 1px rgba(188, 204, 226, .10); }
.chatbox textarea { width: 100%; resize: none; border: none; background: transparent;
  padding: .25rem .3rem; max-height: 15rem; }
.chatbox textarea:focus { border: none; box-shadow: none; }
.chatactions { display: flex; align-items: center; justify-content: flex-end;
  gap: .6rem; margin-top: .3rem; }
.chatactions .chatusage { margin-right: auto; }   /* model · thread spend, pinned left of Send */

/* ── about ──────────────────────────────────────────────────────── */
.aboutgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: .8rem; margin: .9rem 0; }
.aboutcard { display: flex; flex-direction: column; padding: .9rem 1rem; }
.aboutcard p { margin: .45rem 0 .7rem; font-size: .86rem; color: var(--ink); flex: 1; }
.abouthead { display: flex; align-items: baseline; justify-content: space-between; gap: .6rem; }
.abouthead h2 { margin: 0; font-size: 1.05rem; }
.aboutfoot { display: flex; align-items: center; justify-content: space-between; gap: .6rem; }
.badge.claude { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }
.badge.freebadge { background: var(--ok-soft); border-color: rgba(138, 223, 138, .32); color: var(--ok); }
.abouttbl td { padding: .32rem .6rem .32rem 0; vertical-align: top; }
.abouttbl td.ro { white-space: nowrap; }

/* ── narrow windows ─────────────────────────────────────────────── */
/* below 876px (the 640px phone breakpoint + the 236px rail) a fixed sidebar would squeeze
   the content column into a sliver (worst case: the chat transcript) — collapse it to a
   stacked wrap-row block and give the content the full width; --sbw: 0 resets the
   body / flashes / graphwrap offsets with it. */
@media (max-width: 876px) {
  :root { --sbw: 0px; }
  .sidebar { position: static; width: auto; flex-direction: row; flex-wrap: wrap;
    align-items: center; gap: .1rem .3rem; padding: .55rem .9rem;
    border-right: none; border-bottom: 1px solid var(--line); overflow: visible; }
  .sidebar .brand { padding: .1rem .4rem .1rem 0; }
  .sidebar .search { flex: 1 1 100%; margin: .3rem 0 .4rem; order: 10; }
  /* both are flex ITEMS of the wrapped .sidebar row as well as flex containers. Without
     min-width:0 the default min-width:auto pins them to their min-content width (~666px of
     nav links), so the whole document scrolled sideways on every page at phone widths. */
  .snav { flex-direction: row; flex-wrap: wrap; align-items: center;
    flex: 1 1 100%; min-width: 0; }
  .sgroup { flex-direction: row; align-items: center; margin-top: 0;
    flex-wrap: wrap; min-width: 0; }
  .sgroup + a { margin-top: 0; }
  .slabel { padding: .3rem .3rem .3rem .45rem; }
  .sgroup a.sub { padding-left: .6rem; }
  .sfoot { margin-top: 0; padding-top: 0; border-top: none; }
  .axctl { margin-top: 0; flex: none; }
  .pointctl { flex: none; }
}

/* ── mobile ─────────────────────────────────────────────────────── */
@media (max-width: 640px) {
  main { padding: 0 .9rem; }
  .grid2 { grid-template-columns: 1fr; }
  /* a phone has no room for a 13vh runway above the composer */
  .nothread { margin-top: 2vh; }
  .chathead { flex-direction: column; align-items: flex-start; gap: .5rem; }
  .chatfoot { flex-direction: column; gap: .6rem; }
  .prog { grid-template-columns: 1fr 1fr; }
  .assetgrid { grid-template-columns: 1fr 1fr; }
  .sxs { grid-template-columns: 1fr; }
  .readgrid { grid-template-columns: 1fr; }
  .flagrow { flex-direction: column; gap: .4rem; }
  .pagehead { align-items: flex-start; }
}

/* ── audio: Summary/Transcript tabs + AlphaSense-style two-pane transcript ──── */
.eptabs { display: flex; gap: .1rem; border-bottom: 1px solid var(--line); margin: .5rem 0 1.2rem; }
.eptab { padding: .45rem .9rem; font-size: .84rem; font-weight: 600; color: var(--dim);
         text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.eptab:hover { color: var(--ink); }
.eptab.is-active { color: var(--accent); border-bottom-color: var(--accent); }

.xscript-head { border-bottom: 2px solid var(--ink); padding-bottom: .6rem; margin-bottom: 1rem; }
.xscript-head h1 { font-size: 1.25rem; line-height: 1.3; margin: 0 0 .3rem; }
.xscript-meta { font-family: var(--mono); font-size: .7rem; letter-spacing: .03em;
                text-transform: uppercase; color: var(--dim); }
.xscript-meta a { color: var(--accent); }

/* two-pane: LHS key-quote index (sticky, own scroll) · RHS transcript */
.xlayout { display: grid; grid-template-columns: minmax(230px, 320px) 1fr; gap: 1.6rem; align-items: start; }
.xquotes { position: sticky; top: .5rem; max-height: calc(100vh - 1.5rem); overflow-y: auto;
           border-right: 1px solid var(--line); padding-right: 1.1rem; }
.xquotes-head { font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; font-weight: 700;
                color: var(--dim); margin-bottom: .6rem; }
.xquotes-head .n { color: var(--accent); margin-left: .2em; }
.xquotes-empty { font-size: .78rem; padding: .3rem 0; }
.xquotes ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.xquotes ul[hidden] { display: none; }
.qcard { border: 1px solid var(--line); border-left: 3px solid var(--accent-line); border-radius: 6px;
         padding: .5rem .6rem; cursor: pointer; transition: background .12s, border-color .12s; }
.qcard:hover { background: var(--panel); border-left-color: var(--accent); }
.qcard.active { background: var(--accent-soft); border-left-color: var(--accent); }
.qcard-text { font-size: .8rem; line-height: 1.5; color: var(--ink); }
.qcard-text::before { content: '\201C'; } .qcard-text::after { content: '\201D'; }
.qcard-meta { display: flex; flex-wrap: wrap; gap: .35rem; align-items: center; margin-top: .35rem; }
.qcard-ts { font-family: var(--mono); font-size: .62rem; color: var(--dim); }
.qcard-tag { font-family: var(--mono); font-size: .66rem; text-transform: uppercase; letter-spacing: .02em;
             color: var(--accent); background: var(--accent-soft); border-radius: 4px; padding: .05em .4em; }

.xmain { min-width: 0; }
.xsearch { position: sticky; top: 0; z-index: 3; background: var(--bg);
           padding: .1rem 0 .55rem; margin-bottom: .6rem; border-bottom: 1px solid var(--line); }
.xsearch input { width: 100%; box-sizing: border-box; padding: .4rem .6rem; border: 1px solid var(--line);
                 border-radius: 6px; font-size: .82rem; background: var(--panel); color: var(--ink); }
.xsearch input:focus { outline: none; border-color: var(--accent); }
.xsearch-nav-row { display: flex; align-items: center; gap: .35rem; margin-top: .35rem; }
.xsearch-count { flex: 1; font-family: var(--mono); font-size: .64rem; color: var(--dim); }
.xsearch-nav { border: 1px solid var(--line); background: var(--panel); border-radius: 5px;
               width: 1.8em; height: 1.8em; line-height: 1; cursor: pointer; color: var(--dim); }
.xsearch-nav:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }
.xsearch-nav:disabled { opacity: .4; cursor: default; }
.xsearch-syn { font-family: var(--mono); font-size: .6rem; color: var(--dim); margin-top: .3rem; }
.mcard .qcard-text::before, .mcard .qcard-text::after { content: ''; }
.xparts { font-size: .78rem; margin-bottom: 1rem; color: var(--ink); }
.xparts .n { text-transform: uppercase; letter-spacing: .04em; font-size: .66rem; color: var(--dim); margin-right: .35em; }
.xparts b { font-weight: 700; }

.turn { display: grid; grid-template-columns: 52px 1fr; gap: .7rem; padding: .5rem 0; scroll-margin-top: 3.5rem; }
.turn-gutter { text-align: right; padding-top: .2rem; }
.turn-gutter .ts, .turn-gutter .ts-plain { font-family: var(--mono); font-size: .68rem; color: var(--dim); white-space: nowrap; }
.turn-gutter a.ts, .turn-gutter button.ts { text-decoration: none; background: none; border: 0; padding: 0; cursor: pointer; font: inherit; }
.turn-gutter a.ts:hover, .turn-gutter button.ts:hover { color: var(--accent); text-decoration: underline; }
.turn-speaker { font-weight: 700; font-size: .82rem; color: var(--ink); margin-bottom: .15rem; }
.turn-body p { margin: 0; line-height: 1.72; font-size: .95rem; color: var(--ink); }

mark.anno-q { background: var(--warn-soft); border-bottom: 2px solid var(--warn); color: inherit;
              border-radius: 2px; padding: .04em 0; scroll-margin-top: 4rem; }
mark.anno-cov { background: var(--accent-soft); color: inherit; border-radius: 2px; padding: .02em .1em; }
mark.anno-find { background: #ffe58a; color: #3a2c00; border-radius: 2px; }
mark.anno-find.current { background: #f59e0b; color: #241a02; }
mark.anno-q.flash { animation: xflash 1.6s ease-out; }
.turn.flash { animation: xflashturn 1.6s ease-out; border-radius: 6px; }
@keyframes xflash { 0%, 15% { background: var(--warn); color: var(--on-accent); } 100% { background: var(--warn-soft); color: inherit; } }
@keyframes xflashturn { 0%, 15% { background: var(--warn-soft); } 100% { background: transparent; } }
.xscript-player { width: 100%; margin-top: 1.2rem; position: sticky; bottom: .5rem; }

@media (max-width: 760px) {
  .xlayout { grid-template-columns: 1fr; gap: 1rem; }
  .xquotes { position: static; max-height: 300px; border-right: 0; padding-right: 0;
             border-bottom: 1px solid var(--line); padding-bottom: 1rem; }
}

/* ── Review: the earned zero state (Phase 1, docs/review-queue-redesign.md) ────────── */
/* A cleared queue is a RESULT, not a blank page — one verdict line in the same serif-italic
   voice as .empty/.pempty (the per-section .empty blocks died this phase), run facts under it. */
.allclear { text-align: center; padding: 2.2rem 0 1.2rem; }
.allclear .zeroline { font-family: var(--sans); font-style: italic; font-size: .97rem;
  color: var(--dim); margin: 0 0 .5rem; }
.allclear .ro { font-size: .8rem; margin: 0; }
/* per-section explainers sit behind a summary now */
details.hint > p { margin: .3rem 0 0; }

/* ── Review Phase 2: one feed — chip bar, company groups, one-line rows ────────────── */
.chips { display: flex; flex-wrap: wrap; gap: .4rem; margin: .2rem 0 .8rem; }
.chip { display: inline-flex; align-items: baseline; gap: .35rem; border: 1px solid var(--line);
  border-radius: 999px; background: var(--panel); padding: .26rem .7rem; font-size: .8rem;
  font-weight: 600; color: var(--ink); }
.chip b { font-variant-numeric: tabular-nums; }
.chip:hover { text-decoration: none; border-color: var(--accent-line); color: var(--accent); }
.chip.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.chip.on b { color: rgba(4, 7, 13, .72); }
.rgroup { margin: 1.1rem 0; }
.ghead { display: flex; align-items: baseline; gap: .55rem; padding: 0 .2rem .35rem; flex-wrap: wrap; }
.ghead .co { font-family: var(--serif); font-weight: 700; font-size: .95rem;
  letter-spacing: -.04em; color: var(--ink); }
a.co:hover { color: var(--accent); }
.ghead .cnt { font-size: .74rem; color: var(--dim); }
.ghead .grpact { margin-left: auto; }
.rrows { background: var(--panel); border: 1px solid var(--line); border-radius: .55rem;
  box-shadow: none; }
.rrow { display: flex; align-items: flex-start; gap: .7rem; padding: .45rem .8rem;
  border-top: 1px solid var(--line-soft); }
.rrow:first-child { border-top: none; }
.rline { flex: 1; min-width: 0; }
.rline > summary { display: flex; align-items: baseline; gap: .5rem; cursor: pointer;
  color: var(--ink); padding: .1rem 0; }
/* display:flex on a summary kills the native marker (see .recents) — draw our own */
/* JetBrains Mono draws U+25B8 as a genuinely SMALL triangle (the old system fallback drew it
   larger), so the disclosure marker needs the size back to stay a visible affordance. Costs
   nothing: measured, the clipped-title count on /review is identical at .7rem and 1rem. */
.rline > summary::before { content: "▸"; flex: none; color: var(--dim); font-size: 1rem; }
.rline[open] > summary::before { content: "▾"; }
.rline > summary .rtitle { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: .82rem; }
.rline[open] > summary .rtitle { white-space: normal; }
.rline > summary .status { flex: none; }
.rbody { padding: .3rem 0 .5rem 1.1rem; }
/* Activity takes the whole window now (GE-1), and a paragraph handed 2300px is one line of
   350 characters - the eye loses the row on the way back. The evidence a decision is taken
   on keeps a measure: the same 78ch the document page reads at, and a ceiling on the KPI
   table so a unit does not sit a screen away from the KPI it belongs to (the trap .kvlist
   already names). Scoped to main.wide: at 960px none of them bind. */
main.wide .rbody .quote, main.wide .rbody .detail, main.wide .rbody .dupcmp,
main.wide .rbody > .ro { max-width: 78ch; }
main.wide .rbody .mini { max-width: 52rem; }
.kpill { flex: none; font: 600 .64rem/1.5 var(--mono); text-transform: uppercase;
  letter-spacing: .05em; color: var(--accent); background: var(--accent-soft);
  border-radius: .35rem; padding: .06rem .45rem; }
/* T1 origin pill: who decided the ledger row — quieter than the kind pill on purpose,
   an attribution, not a category */
.kpill.via { color: var(--dim); background: var(--panel2); }
/* An origin pill that links stays un-underlined, so hover is what says "clickable" — it
   used to lean on a ↗ glyph, and those are gone from the site. */
a.kpill { text-decoration: none; }
a.kpill.via:hover, a.kpill.via:focus-visible { color: var(--accent); background: var(--accent-soft); }
.rrow > .flagactions { padding-top: .12rem; }
/* The verdicts surface on hover or keyboard focus, the catalyst table's rule (table.cats
   .rowact above): painted on every row, Approve and Reject said "decide me" thirteen times
   down one page. The FORMS hide, not the cluster, so a state pill in it (Generating…) stays
   put and the row keeps its width. A row you are reading (its evidence open) or were sent to
   (:target, from a company page's "proposed" / "pending" link) shows them outright; touch
   devices get them in the (hover: none) block beside the feed rows'. The reveals sit at
   (0,3,1) over the hide at (0,2,1); the :has() reveal is its own rule so an engine without it
   drops that one alone, not the hover reveal. */
.rrow > .flagactions > form { visibility: hidden; }
.rrow:hover > .flagactions > form, .rrow:focus-within > .flagactions > form,
.rrow:target > .flagactions > form { visibility: visible; }
.rrow:has(> .rline[open]) > .flagactions > form { visibility: visible; }
/* a row reached from a company page's "proposed" / "pending" link lands lit and mid-page
   (the feed rows' :target rule) */
.rrow { scroll-margin-top: 5rem; }
.rrow:target { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }

/* An audio card's actions surface the same way (the Activity rows' rule above), with one
   constraint that rule does not have: `visibility: hidden` takes an element OUT of the tab
   order, so :focus-within can only ever fire from some OTHER focusable thing in the row. An
   Activity row has its evidence <summary>; a readout card has its title link; a DISCOVERED
   card has nothing but these buttons. So a discovered row keeps its decision painted and
   reveals only the secondary Hide - `form + form`, so a row whose one action IS Hide keeps it.
   The FORMS hide, never the cluster, so the head keeps its width either way. A card you were
   sent to from the Inbox (#e<id>) shows them outright, and so does a touch device (the
   (hover: none) block below). .epfull is the readout on its OWN page: no list to scan. */
.audioep:not(.epfull):not(.discep) .flagactions > form { visibility: hidden; }
.audioep:not(.epfull):not(.discep):hover .flagactions > form,
.audioep:not(.epfull):not(.discep):focus-within .flagactions > form,
.audioep:not(.epfull):not(.discep):target .flagactions > form { visibility: visible; }
.audioep.discep .flagactions > form + form { visibility: hidden; }
.audioep.discep:hover .flagactions > form + form,
.audioep.discep:focus-within .flagactions > form + form,
.audioep.discep:target .flagactions > form + form { visibility: visible; }
@media (max-width: 760px) {
  .rrow { flex-direction: column; gap: .3rem; }
  .rline > summary .rtitle { white-space: normal; }
  .ghead .grpact { margin-left: 0; }
  /* stacked, the verdict cluster is a line of its own under the row, and a hidden form there
     is an empty band on every row; paint the verdicts outright (same (0,2,1) as the hide,
     later in the file) */
  .rrow > .flagactions > form { visibility: visible; }
}

/* a button that reads as a link, for prose that opens a dialog (Social's empty state →
   sources gear). Element-qualified base only — .sfoot .linklike below outranks it in the
   sidebar by class count, not file order. */
button.linklike { background: none; border: 0; padding: 0; margin: 0; font: inherit;
  color: var(--accent); cursor: pointer; }
button.linklike:hover { text-decoration: underline; text-underline-offset: 2px; }

/* ── auth (Phase 2, walled mode only) ─────────────────────────────────────── */
.sfoot .linklike { display: block; width: 100%; background: none; border: 0; cursor: pointer;
  color: var(--ink); font-size: .88rem; font-family: inherit; text-align: left;
  padding: .42rem .6rem; border-radius: .45rem; }
.sfoot .linklike:hover { background: var(--accent-soft); color: var(--accent);
  text-decoration: none; }   /* the base button.linklike hover underlines; nav rows don't */
.authwrap { min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 2rem 1rem; }
.authcard { width: 100%; max-width: 24rem; }
.authcard h1 { font-size: 1.15rem; margin: 0 0 .2rem; }
.authcard label { display: block; font-size: .82rem; color: var(--dim); margin: .7rem 0 .2rem; }
.authcard input { width: 100%; }
.authcard .btn { width: 100%; margin-top: 1rem; }

/* ── "OAuth login" menu + connected-account rows ─────────────────────────────────────
   The menu is a <details>, the repo's disclosure idiom. Two things it must NOT do: grow
   the login card when closed (the password form is the primary path), and reuse the
   .authcard .btn full-width rule on the rows inside it - hence the explicit width resets
   below, which beat that rule on specificity rather than hoping to. */
.oauthmenu { margin-top: 1.1rem; border-top: 1px solid var(--line); padding-top: .9rem; }
.oauthmenu > summary { cursor: pointer; font-size: .84rem; color: var(--dim);
  list-style: none; display: flex; align-items: center; gap: .4rem; }
.oauthmenu > summary::-webkit-details-marker { display: none; }
/* Own disclosure caret: rotates on open, so the control says which way it goes. */
.oauthmenu > summary::before { content: ""; width: 0; height: 0; flex: none;
  border-left: 4px solid currentColor; border-top: 3.5px solid transparent;
  border-bottom: 3.5px solid transparent; transition: transform .12s ease; }
.oauthmenu[open] > summary::before { transform: rotate(90deg); }
.oauthmenu > summary:hover { color: var(--ink); }
.oauthmenu ul { list-style: none; margin: .6rem 0 0; padding: 0; display: grid; gap: .4rem; }
.oauthmenu li a { display: block; border: 1px solid var(--line); border-radius: .45rem;
  padding: .55rem .7rem; background: var(--panel); color: var(--ink); }
.oauthmenu li a:hover { text-decoration: none; border-color: var(--accent-line);
  background: var(--accent-soft); }
.oauthmenu .opname { display: block; font-size: .88rem; font-weight: 600; }
.oauthmenu .opblurb, .oauthmenu .opnote { display: block; font-size: .74rem; color: var(--dim);
  margin-top: .12rem; }
.oauthmenu .opnote { font-family: var(--mono); }

/* Paste page: numbered because the two steps happen in different tabs. */
.pastecard { max-width: 30rem; }
.pastesteps { margin: .4rem 0 0; padding-left: 1.15rem; display: grid; gap: 1.1rem; }
.pastesteps li { padding-left: .15rem; }
.pastesteps p { margin: 0 0 .5rem; font-size: .85rem; color: var(--dim); max-width: 60ch; }
.pastesteps input { width: 100%; margin-bottom: .7rem; }
.authcard .pastesteps .btn { width: auto; margin-top: 0; }

/* /account rows: label on the left, one action on the right. */
.oauthlist { list-style: none; margin: .2rem 0 0; padding: 0; display: grid; gap: .5rem; }
.oauthlist li { display: flex; align-items: center; gap: .7rem; border: 1px solid var(--line);
  border-radius: .45rem; padding: .55rem .7rem; background: var(--panel); }
/* min-width:0 lets the label shrink inside the flex row; overflow-wrap on the LABEL, not on
   the .ro line alone, because the name above it is free text too - an MCP token is named by
   the analyst in a 60-character field, and one unbreakable run of it ("claude_code_on_
   brians_macbook_pro_16_2026") sized this grid's only column to 346px and pushed the whole
   /account document 101px past a 390px phone. The .ro lines already wrapped; the <b> was the
   one value in the row that could not. */
.oauthlist .oalabel { min-width: 0; display: grid; gap: .1rem; overflow-wrap: anywhere; }
.oauthlist .oalabel b { font-size: .88rem; }
.oauthlist form, .oauthlist > li > a { margin-left: auto; flex: none; }
.oauthlist .btn { width: auto; margin-top: 0; }

/* ── Review Phase 5: focus mode + the third verdict ────────────────────────────────── */
/* While a session is running the list, its chips and its explainers are OUT of the way — one
   card is the whole page. The rows stay in the DOM (hidden): they are what the card is a view
   of, and what undo puts back. */
body.focusing .chips, body.focusing .rgroup, body.focusing details.hint,
body.focusing .recentlog, body.focusing .feedfoot, body.focusing .qhint { display: none; }
/* the bulk sweep and the way back in are both out of reach mid-session: one is the opposite
   of deciding one at a time, the other would restart the tally you are watching */
body.focusing .pagehead form, body.focusing #focusgo { display: none; }
.focuspanel { margin: 1rem 0 1.4rem; }
.fcard { background: var(--panel); border: 1px solid var(--line); border-radius: .7rem;
  padding: 1rem 1.1rem; box-shadow: 0 2px 10px rgba(0, 0, 0, .55), 0 0 0 1px rgba(188, 204, 226, .10); max-width: 46rem;
  margin: 0 auto; }
.fhead { display: flex; align-items: baseline; gap: .6rem; padding-bottom: .5rem;
  border-bottom: 1px solid var(--line-soft); margin-bottom: .7rem; }
.fhead .fcount { font: 600 .72rem/1.4 var(--mono); color: var(--dim); }
.fhead .fco { font-family: var(--serif); font-weight: 700; font-size: .95rem;
  letter-spacing: -.04em; }
/* the card's copy of the row: the flex row of .rrow is gone, the inner pills/quote/meta
   styles all still apply */
.fcardrow > .rline > summary { cursor: default; }
.fcardrow .rbody { padding-left: 1.1rem; }
.facts { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .9rem; }
.facts .btn { display: inline-flex; align-items: center; gap: .4rem; }
.facts kbd, .ffix kbd { font: 600 .68rem/1 var(--mono); border: 1px solid var(--line);
  border-bottom-width: 2px; border-radius: .25rem; padding: .16rem .32rem;
  background: var(--panel2); color: var(--dim); }
.ffix { display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; margin-top: .6rem;
  padding-top: .6rem; border-top: 1px solid var(--line-soft); }
.ffix label { display: flex; align-items: center; gap: .4rem; flex: 1; min-width: 14rem;
  font-size: .78rem; color: var(--dim); }
.ffix input { flex: 1; min-width: 10rem; }
/* what the server said when a decision changed nothing — a toast fades, this doesn't */
.fnote { margin: .7rem 0 0; padding: .4rem .6rem; border-radius: .4rem;
  background: var(--warn-soft); color: var(--ink); font-size: .82rem; }
.fdone { text-align: center; }
.fdone .zeroline { font-family: var(--sans); font-style: italic; font-size: .97rem;
  color: var(--dim); margin: 0 0 .5rem; }
.fdone .ro { margin: .2rem 0; }
.fdone .facts { justify-content: center; }
/* the possible-duplicate comparison: both rows on screen, because comparing them IS the call */
.dupcmp { border-left: 2px solid var(--warn); padding: .1rem 0 .1rem .55rem; margin: .35rem 0;
  font-size: .82rem; }
.dupcmp .ro { font-size: .74rem; }
/* the third verdict, quiet inside the collapsed body until it is wanted */
.fixform { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .5rem; }
/* max-width for the same reason .stageform's fields have one: this box holds a corrected
   date, and on Activity at 2560 an uncapped flex: 1 made it 1957px wide to hold "Q1 2027" */
.fixform input { flex: 1; min-width: 12rem; max-width: 32rem; }

/* (Review Phase 4's separate .chips.bands row merged into the main chip row in the ModelML
   pass — a .push spacer keeps the two axes reading as two questions on one line.) */

/* ── Review Phase 3: fetch decisions — toast + recent-decisions ledger ─────────────── */
.rtoast { position: fixed; left: 50%; bottom: 1.2rem; transform: translateX(-50%);
  display: flex; align-items: center; gap: .7rem; background: var(--ink); color: var(--bg);
  border-radius: .55rem; padding: .55rem .9rem; font-size: .84rem; z-index: 60;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .7), 0 0 0 1px rgba(188, 204, 226, .14); max-width: min(92vw, 640px); }
.rtoast.err { background: var(--err); color: var(--on-accent); }
.rtoast .btn { flex: none; }
.recentlog .lrow { display: flex; align-items: baseline; gap: .55rem; padding: .3rem 0;
  border-top: 1px solid var(--line-soft); }
.recentlog .lrow:first-of-type { border-top: none; }
.recentlog .ltext { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* the date is one value, not prose — without this it wrapped mid-token ("2026-08-" / "05")
   and pushed the row to two lines whenever the title was long. */
.recentlog .lrow .meta { flex: none; white-space: nowrap; }
.recentlog[open] > summary { margin-bottom: .3rem; }
/* Activity's ledger reads as a TABLE, so it gets the full column width (.hint's 68ch prose
   cap is for prose) and one shared column geometry: every row is the same seven-track grid,
   so the kind, the origin, the company, the verdict badge, the time and Undo each sit on one
   vertical line down the page instead of drifting with the length of the row above. Track
   widths are the widest real value each column can hold, measured in the browser, not
   guessed: "competitor" (5.06rem), "dismissed" (4.77rem), "yesterday 11:59pm" (7.96rem).
   Qualified with .recentlog (0,3,0) so it outranks the flex row above it whatever the order;
   the other .recentlog lists (company timeline, archived themes) keep that flex row. */
.recentlog.ledger { max-width: none; }
.recentlog.ledger .lrow { display: grid; column-gap: .8rem;
  grid-template-columns: 5.3rem 3.1rem minmax(0, 13rem) minmax(0, 1fr) 5rem 8rem 4.3rem; }
/* start, not the grid default stretch: a badge blockified into a fixed track would grow to
   the whole track and stop reading as a pill */
.recentlog.ledger .lrow > * { justify-self: start; }
/* the two text cells are the exception and MUST stretch: a shrink-to-fit box floors at its
   min-content width, and for white-space:nowrap that is the whole string — start-aligned,
   a long name simply overflowed its track and printed over the next column instead of
   ellipsing. (0,4,0) so it outranks the row rule above whatever the order. */
/* min-width:0 belongs to the text cells ALONE — put on every cell it also un-floors the
   badge, the time and the Undo button, which the narrow fallback below then shrinks into
   each other. */
.recentlog.ledger .lrow > .lwho, .recentlog.ledger .lrow > .ltext { justify-self: stretch;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recentlog.ledger .lwho { font-weight: 600; color: var(--ink); }
/* narrower than the tracks' own width the columns would squeeze the text to nothing — fall
   back to the plain inline row (same rule as every other .recentlog list). The two text
   cells share what the fixed pills leave, and the row wraps rather than running off the
   page once the badge, time and Undo alone fill a phone's width. */
@media (max-width: 900px) {
  .recentlog.ledger .lrow { display: flex; flex-wrap: wrap; }
  .recentlog.ledger .lrow > .lwho, .recentlog.ledger .lrow > .ltext { flex: 1 1 0; }
}
/* on a phone the pills, verdict, time and Undo alone fill the width, so sharing what is
   left truncates the name to a letter — give the name and the change a full line each and
   let them read in full (nothing here is wide enough to need the ellipsis) */
@media (max-width: 620px) {
  .recentlog.ledger .lrow > .lwho, .recentlog.ledger .lrow > .ltext {
    flex: 1 0 100%; white-space: normal; overflow: visible; }
}

/* ── Trust mode T2: the HELD chip + the ledger's reading dialog ─────────────────────── */
/* HELD is neither a gate warning (amber) nor a pass (blue): a human pulled this row back
   after it applied, so it gets a neutral high-contrast chip and reads as deliberate rather
   than as one more thing the automation couldn't bless. It sits after .status.pending in
   this file on purpose — equal specificity, and order is what decides ties here. */
.status.held { color: var(--ink); background: rgba(188, 204, 226, .11);
  border-color: rgba(188, 204, 226, .40); }

/* The ledger's description cell is a BUTTON now (it opens the reading dialog), so it has to
   be un-styled back into a table cell: a UA button brings its own font, padding, border and
   centred text, every one of which would break the seven-track geometry above. This is a
   typographic reset ONLY — the layout rules all still address it as .ltext, and none of them
   set a property this touches. */
button.ltext { font: inherit; color: inherit; text-align: left; background: none; border: 0;
  padding: 0; margin: 0; width: 100%; cursor: pointer; }
/* the affordance: it reads as ledger text until you aim at it */
button.ltext:hover { color: var(--accent); }
button.ltext:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px;
  border-radius: .2rem; }

/* the dialog itself — a reading surface like .pinfo, but with a foot, because the row's Undo
   has to be reachable from here too. pre-wrap is belt: desk-memory content is whitespace-
   collapsed on write (_desk_memory_clean), so today no ledger text carries a newline — but a
   catalyst or document title arrives from elsewhere and this is the one surface that shows
   it whole. */
.modal.ldetail { width: min(36rem, calc(100vw - 2rem)); text-align: left; }
.ldetail .modalhead h2 { font-size: 1.02rem; min-width: 0; overflow-wrap: anywhere; }
.ldetail .modalbody { padding: .8rem 1.1rem 1rem; }
dialog.ldetail .modalbody p { margin: 0; max-width: none; font-size: .92rem; line-height: 1.6;
  white-space: pre-wrap; overflow-wrap: anywhere; }
/* the foot is [meta ....] [Undo] [Close]. The shared `.modalfoot .btn:first-of-type
   { margin-left: auto }` (0,3,0) cannot do it here: on a row with no inverse the Undo button
   is [hidden], and the auto margin goes with it — Close would fall back to the left edge.
   Push from the meta instead, and out-specify the shared rule at (0,4,0). */
.modal.ldetail .modalfoot .btn { margin-left: 0; }
.modal.ldetail .modalfoot .ldmeta { margin-right: auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }

/* ── ModelML adoption, Pass 1: one icon family + a fuller button vocabulary ─────────── */
/* The sprite is _icons.html (vendored Lucide subset, same-document <use>); this block is the
   sizing/coloring contract. .icon is the SVG's class only — a button never takes it (see
   .btn.iconbtn), or the 16px width rule would shrink the button itself. */
.icon { width: 16px; height: 16px; stroke: currentColor; stroke-width: 2; fill: none;
  stroke-linecap: round; stroke-linejoin: round; flex: none; vertical-align: -.19em; }
.btn .icon { width: 15px; height: 15px; }
.btn.small .icon { width: 13px; height: 13px; }
/* sidebar rows lead with their icon — muted at rest, the row's accent when active/hovered */
.snav a { display: flex; align-items: center; gap: .55rem; }
.snav a .icon { width: 15px; height: 15px; color: var(--dim); }
.snav a:hover .icon, .snav a.on .icon { color: inherit; }
/* icon-only button: square, and ALWAYS carrying title + aria-label in the markup */
.btn.iconbtn { padding: 0; width: 1.9rem; height: 1.9rem; }
.btn.iconbtn.small { width: 1.6rem; height: 1.6rem; }
/* (switch + kebab components were drafted here and cut before landing: no consumer existed —
   the adversarial pass held them against simplicity-first, and the trust-mode toggle chose a
   plain .btn. Re-add either WITH its first consumer; the spec lives in the design artifact.) */
/* the page-top rule (title · one hint · one toolbar): run telemetry leaves the header and
   lives behind a clock icon-button. */
details.runpop { position: relative; display: inline-block; }
details.runpop > summary { list-style: none; display: inline-flex; cursor: pointer; }
details.runpop > summary::-webkit-details-marker { display: none; }
details.runpop[open] > summary.btn { background: var(--accent-soft);
  border-color: var(--accent-line); color: var(--accent); }
.runpanel { position: absolute; right: 0; top: calc(100% + .3rem); z-index: 30; width: 24rem;
  max-width: 82vw; background: var(--panel); border: 1px solid var(--line);
  border-radius: .55rem; box-shadow: 0 12px 32px rgba(0, 0, 0, .72), 0 0 0 1px rgba(188, 204, 226, .10);
  padding: .75rem .9rem; font-size: .82rem; text-align: left; }
.runpanel .ro { display: block; margin: 0 0 .55rem; white-space: normal; }
.runpanel form { margin: 0 0 .45rem; }
/* on a narrow window the wrapped headacts cluster sits at the LEFT edge, so a right-anchored
   absolute panel would hang past the left viewport edge (unreachable — no scroll that way in
   LTR). Pin it to the viewport instead. */
@media (max-width: 640px) {
  .runpanel { position: fixed; left: .75rem; right: .75rem; top: auto; width: auto;
    max-width: none; }
}
/* page-head action cluster: buttons and icon-buttons on one centered row */
.pagehead .headacts { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
/* one chips row, two axes: a spacer pushes the confidence chips to the right edge */
.chips .push { flex: 1 1 auto; }

/* ── ModelML adoption, Pass 2: lists — one container, hairline rows ─────────────────── */
/* The container owns the border (Review's .rrows pattern, promoted to the feeds): each row
   is content on a hairline, not its own card. .newsrow keeps its name and inner anatomy —
   the scroll-drain script and the chat deep-link anchors key on it. */
.feedrows { background: var(--panel); border: 1px solid var(--line); border-radius: .55rem;
  margin: .8rem 0; }
.feedrows .newsrow { background: none; border: none; border-radius: 0; box-shadow: none;
  margin: 0; padding: .7rem 1rem; border-top: 1px solid var(--line-soft); }
/* A row that paints its own background (the :target wash below, an unread mail row) has to
   end where the container's rounded border does, or its square corners show past the curve.
   Inner radius = the container's, minus its 1px border. (0,3,0) beats the radius reset above. */
.feedrows .newsrow:first-child { border-top: none;
  border-top-left-radius: calc(.55rem - 1px); border-top-right-radius: calc(.55rem - 1px); }
.feedrows .newsrow:last-child { border-bottom-left-radius: calc(.55rem - 1px);
  border-bottom-right-radius: calc(.55rem - 1px); }
/* A row you were SENT to - an Inbox open, or a chat citation - has to announce itself. Both
   roads land on #p<id> / #n<id>, and the browser's default is to park that row flush against
   the top edge: no pagehead, no chips, no neighbour above it, in a stack of near-identical
   cards. That reads as "the top of the list", not "the one you asked for", which is how you
   arrive already lost.
   scroll-margin-top buys the room that makes it read as YOUR row, mid-list. The wash and the
   bar say which one, and they hold as long as the hash points here, so scrolling away and
   back does not lose the answer.
   Inset shadow, not border-left: a real border would shift the row's content sideways on
   arrival. The (0,3,0) selector beats the .feedrows .newsrow reset above on SPECIFICITY, not
   on source order, so a later rule cannot silently undo it. */
.newsrow { scroll-margin-top: 5rem; }
.newsrow:target,
.feedrows .newsrow:target { background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent); }
/* votes surface on hover or keyboard focus; a CAST vote stays visible — it is state, and
   visibility on a child overrides the hidden parent */
.feedrows .cardfoot .actions { visibility: hidden; }
.feedrows .newsrow:hover .cardfoot .actions,
.feedrows .newsrow:focus-within .cardfoot .actions { visibility: visible; }
.feedrows .cardfoot .vote.on { visibility: visible; }
/* A Social card whose body opens in the reading dialog says so the way the Inbox row does:
   a pointer over the whole card and a wash under it. `.opens` is put on by social.html's
   script, so the affordance appears only where the behaviour does - with no JS the card is
   the inert box it has always been and the title link is still the road.
   (0,4,0) beats the .feedrows .newsrow reset (0,2,0) on specificity rather than on source
   order; --panel2 is the lift a read Inbox row gets, over a card that paints nothing. */
.feedrows .newsrow.opens { cursor: pointer; }
.feedrows .newsrow.opens:hover { background: var(--panel2); }
/* no hover, no reveal: a touch device shows the votes outright (the hosted pilot is phones) */
@media (hover: none) {
  .feedrows .cardfoot .actions { visibility: visible; }
  table.cats .rowact > form, .prog .rm, .rrow > .flagactions > form { visibility: visible; }
  .audioep:not(.epfull):not(.discep) .flagactions > form,
  .audioep.discep .flagactions > form + form { visibility: visible; }
}
/* quiet approve: the row-level verdict steps down from filled purple — the ONE primary on a
   review page is the bulk sweep in the page head (focus mode's verdicts() knows this class) */
.btn.approve { color: var(--ok); border-color: rgba(138, 223, 138, .38); }
.btn.approve:hover { background: var(--ok-soft); border-color: var(--ok); }
/* A catalyst's lifecycle move ASKS: "Occurred?" carries the warn yellow the cockpit already
   uses for a state that wants a look, and the click answers it in green. The green rides the
   same .busy latch base.html puts on every data-working button, so the row confirms itself
   before the reload lands; opacity is restored because a latched answer is not a dimmed one,
   and the spinner is dropped for the same reason. (0,3,0) over .btn.busy's (0,2,0). */
.btn.occurs { color: var(--warn); border-color: rgba(229, 192, 123, .42);
  background: var(--warn-soft); }
.btn.occurs:hover { border-color: var(--warn); background: var(--warn-soft); }
.btn.occurs.busy { color: var(--ok); border-color: var(--ok); background: var(--ok-soft);
  opacity: 1; }
.btn.occurs.busy::after { content: none; }
/* companies table: membership is the checkbox alone; the ticker is a right-edge readout */
table.companies td.ticker { text-align: right; }

/* ── ModelML adoption, Pass 3: terminal states + actionable empties ─────────────────── */
/* done/error read as icon + word (ModelML's green-check "Completed"); queued/running keep
   their pills — a transient state earns the louder treatment */
.st { display: inline-flex; align-items: center; gap: .3rem; font-size: .78rem;
  font-weight: 600; }
.st .icon { width: 14px; height: 14px; }
.st.ok { color: var(--ok); }
.st.err { color: var(--err); }
/* the icon tile that heads an actionable empty state */
.itile { display: inline-flex; align-items: center; justify-content: center; width: 2.4rem;
  height: 2.4rem; border-radius: .6rem; background: var(--panel2);
  border: 1px solid var(--line); color: var(--dim); }
.itile .icon { width: 17px; height: 17px; }
.emptywell { display: flex; flex-direction: column; align-items: center; gap: .45rem;
  text-align: center; padding: 2.4rem 1rem; background: var(--panel);
  border: 1px solid var(--line); border-radius: .5rem; margin: 1rem 0; }
.emptywell p { margin: 0; }

/* ── Documents: the library shape (one filtered list, rows that open) ───────────────── */
/* The drop is a DIALOG (docs/ui-feedback-2026-09-14.md, DO-4) - an upload form is used
   weekly and the library is read daily, so the library gets the whole page and the drop
   gets a button in the head. It keeps the house 38rem: the hint above the box is prose at
   the 68ch measure, and a wider dialog only sets that column adrift in it. The body carries
   its own bottom padding because there is no .modalfoot - the composer's bar is the foot. */
.dropdlg .modalbody { padding: .75rem 1.1rem 1rem; }
/* the submit: this page's one .btn.primary is + Add in the head, so the dialog paints its
   own action instead of claiming a second one - the same split base.html's confirm dialog
   makes, for the same reason */
.dropdlg .btn.dropgo { background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); }
.dropdlg .btn.dropgo:hover { box-shadow: 0 4px 12px rgba(92, 156, 245, .3); }
/* ── the drop composer ─────────────────────────────────────────────────────────
   One surface, the chat-composer idiom: the text box IS the control, attachments
   are chips inside it, and + sits on the action bar beside the submit. The whole
   card is still the drag target (#dropzone keeps its id), so dragging a file
   anywhere lands here — it just no longer needs a separate well to land IN. */
.composer { border: 1px solid var(--line); border-radius: .6rem; background: var(--panel2);
  padding: .55rem .6rem .5rem; display: flex; flex-direction: column; gap: .45rem;
  transition: border-color .12s, box-shadow .12s, background .12s; }
.composer:focus-within { border-color: var(--accent-line);
  box-shadow: 0 0 0 2px var(--accent-soft); }
/* the drag state paints the COMPOSER, so a dragged file has an obvious destination
   without a dashed rectangle sitting there permanently asking for one */
.composer.dragging { border-color: var(--accent); border-style: dashed;
  background: var(--accent-soft); }
/* borderless inside the shell: the shell is the field, this is just the text in it */
.composer .cmp-text { width: 100%; border: 0; background: transparent; resize: none;
  padding: .3rem .25rem; min-height: 3.2rem; max-height: 20rem; font: inherit;
  color: var(--ink); }
/* the SHELL shows focus, not the field inside it — two nested rings on one box reads as a
   bug. Killing the base textarea ring here means the shell (or, on the analyzer/audio
   cards, the card itself) must supply one, which is what the rules below do. */
.composer .cmp-text:focus { outline: none; box-shadow: none; border-color: transparent; }
.composer .cmp-text::placeholder { color: var(--dim); }
.cmp-files { display: flex; flex-wrap: wrap; gap: .35rem; padding: 0 .25rem; }
/* an author `display` beats the UA's `[hidden] { display: none }` — the FOURTH time this
   file has needed the companion rule (.estcell, .btn, .maphits/.mappanel, .mapadd). Without
   it `chips.hidden = !list.length` is inert and the empty row stays a flex item, leaving a
   permanent gap inside every composer and a hidden flag that lies. */
.cmp-files[hidden] { display: none; }
.cmp-file { display: inline-flex; align-items: center; gap: .4rem; max-width: 22rem;
  padding: .2rem .3rem .2rem .55rem; border: 1px solid var(--line); border-radius: 9999px;
  background: var(--panel); font: .78rem/1.3 var(--mono); color: var(--ink); }
.cmp-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-x { border: 0; background: none; color: var(--dim); cursor: pointer; font-size: .95rem;
  line-height: 1; padding: 0 .15rem; }
.cmp-x:hover { color: var(--err); }
.cmp-bar { display: flex; align-items: center; gap: .6rem; }
/* + is the attach affordance, not a second submit: quiet until you reach for it */
.cmp-attach { display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem; flex: none; padding: 0; border: 1px solid var(--line);
  border-radius: 9999px; background: var(--panel); color: var(--dim); cursor: pointer; }
.cmp-attach:hover { border-color: var(--accent-line); color: var(--accent);
  background: var(--accent-soft); }
.cmp-attach .icon { width: 15px; height: 15px; }
/* a control that lives ON the bar (the analyzer's and audio's company field): quiet until
   focused, so the bar reads as chrome rather than a second form */
.cmp-field { flex: 1 1 12rem; min-width: 0; border: 0; background: transparent;
  color: var(--ink); font-size: .82rem; padding: .25rem .3rem; }
.cmp-field:focus { outline: none; background: var(--panel); border-radius: .3rem; }
.cmp-field::placeholder { color: var(--dim); }
.cmp-bar .btn { flex: none; margin-left: auto; }
/* the inline "which company?" ask — a question in place, not a flash that fades */
.askco { border-left: 3px solid var(--warn); margin-top: .8rem; }
.askco h2 { font-size: 1rem; margin: 0 0 .3rem; }
.askco .chips { margin: .55rem 0 .5rem; }
.askco .chips .chip { cursor: pointer; font-family: inherit; }
.askco .chips .chip:hover { border-color: var(--accent-line); color: var(--accent);
  background: var(--accent-soft); }
.askpick { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.askpick .cmp-field { flex: 1 1 16rem; border: 1px solid var(--line); border-radius: .35rem;
  background: var(--panel2); padding: .34rem .5rem; }
.askpick .btn { flex: none; margin-left: 0; }
/* The Company field only exists when all four ladder rungs came back empty, so it is the one
   thing on the page that needs an answer — carry the card's warn colour into the control
   rather than leaving it looking like the composer field it replaced. */
/* .askpick .askfield, NOT .askfield: `.askpick .cmp-field` above is (0,2,0) and sets `border`
   and `background`, so the single-class version lost both and the field rendered plain grey —
   yellow only while :focus happened to tie. The nudge has to hold when nothing is focused,
   which on the multi-candidate card (no autofocus) is always. */
.askpick .askfield { border-color: rgba(229, 192, 123, .5); background: var(--warn-soft); }
.askpick .askfield::placeholder { color: var(--warn); opacity: .8; }
.askpick .askfield:focus { outline: none; border-color: var(--warn);
  box-shadow: 0 0 0 2px rgba(229, 192, 123, .16); }
/* the old .analyzebox rules laid out a card of stacked rows; the composer IS the card now */
.analyzebox { padding: .6rem; }
/* on these two the CARD is already the box, so the composer drops its own border rather
   than nesting a second one — and the card takes over showing focus */
.analyzebox .composer { border: 0; background: transparent; padding: 0; }
.analyzebox .composer:focus-within { box-shadow: none; }
.analyzebox:focus-within { border-color: var(--accent-line);
  box-shadow: 0 0 0 2px var(--accent-soft); }
.libsearch { display: flex; gap: .4rem; margin: .2rem 0 .8rem; }
.libsearch input { flex: 1 1 auto; max-width: 34rem; }
.libcount { margin: .1rem 0 .6rem; }
.chips.themehits { align-items: baseline; margin-bottom: .8rem; }
/* One-line document rows (the /review .rrow idiom): kind · title · links · date. The row
   is an ANCHOR to the document's own page — it must not read as a body-text link, the
   chevron is the affordance that says it goes somewhere, and the title truncates so a long
   one cannot wrap the row to two lines. */
/* the library list is its own scroll window so the drop panel and the filter bar never
   leave the top of the page — a long library scrolls HERE, not the page. The frame moves
   to the window (the inner .rrows yields its border/radius at (0,2,0) over (0,1,0)) so
   the rows still read as one bordered list while they scroll. */
.docwin { max-height: 60vh; overflow-y: auto; overscroll-behavior: contain;
  border: 1px solid var(--line); border-radius: .55rem; }
.docwin .rrows { border: none; border-radius: 0; }
a.docrow { color: var(--ink); text-decoration: none; align-items: baseline; }
a.docrow:hover { background: var(--accent-soft); text-decoration: none; }
a.docrow .chev { flex: none; opacity: 0; }
a.docrow:hover .chev, a.docrow:focus-visible .chev { opacity: 1; }
a.docrow .rtitle { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: .82rem; }
.docrow .dlinks, .docrow .when { flex: none; font-size: .74rem; color: var(--dim);
  font-family: var(--mono); white-space: nowrap; }
.docrow .dlinks { max-width: 22ch; overflow: hidden; text-overflow: ellipsis; }
.docrow .when { margin-left: auto; font-variant-numeric: tabular-nums; }
/* delete removes the stored file with no undo — it reads as the one destructive action here */
.btn.ghost.danger:hover { color: var(--err); border-color: rgba(223, 138, 138, .42);
  background: var(--err-soft); }
@media (max-width: 760px) {
  .docrow .dlinks { display: none; }   /* the title is the identity on a narrow window */
  .docrow .when { margin-left: 0; }
}

/* ── Document detail (/kb/<id>): the source and the agent's read, in two tabs ───────── */
/* It was a two-column grid - scrolling source left, sticky analysis right - until the rail's
   26rem column cost the document a third of its width and gave the analysis a column too
   narrow to read a paragraph in (docs/ui-feedback-2026-09-14.md, DO-3). One tab at a time
   now, each with the whole page; the pane keeps its own scroll box, because the jump that
   lands a cited passage mid-pane scrolls THE PANE and nothing else. */
/* The page asks for the width with `main.wide` (GE-1, 2026-09-16) and keeps 1500px as its
   OWN cap: a document is still read, and past about that the lines stop being lines. The
   header and the chips carry the same cap and the same auto margins so the title, the
   actions and the chips line up with the panel instead of hanging left of it.
   This was three copies of a left: 50% / translateX(-50%) breakout plus a body.ax-open
   correction, because a transform cannot see the padding an open sidebar puts on the body -
   and selecting a passage opens the sidebar on THIS page, so that was the routine state. */
.dochead, .chips.doclinks, .doclayout { max-width: 1500px;
  margin-left: auto; margin-right: auto; }
.doclayout { margin-top: .9rem; }
/* the strip sits inside the capped container, so it lines up with the panel under it without
   a second copy of the arithmetic */
.doclayout > .tabstrip { margin: 0 0 .9rem; }
/* ONE panel at a time, each the width of the page. The pane keeps a scroll box of its own
   because the centred jump scrolls the pane, never the window. */
.docpane { max-height: calc(100vh - 4rem); overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line); border-radius: .5rem;
  padding: 0 1.1rem 1.1rem; }
.docside { display: flex; flex-direction: column; gap: .1rem; }
/* ONE reading measure, centred in the width the breakout gives the page, and the SAME one
   in both tabs so the words do not jump sideways when you switch. Each tab needs it for its
   own reason: released from the grid the source ran to 126 characters a line at 1440 (it
   read at 78 beside the rail), and the analysis had been living in a 26rem gutter, which is
   no place to read a sentence either. 78ch is .postpage's measure - the cockpit's other
   long-prose surface. The pane's card, its sticky header rule and the strip above stay the
   full width: they are the frame, and the column is what you read. */
.docpane .pageblock, .docpane .paneempty, .docpane .paneheadrow,
.docside { max-width: 78ch; margin-left: auto; margin-right: auto; }
/* an author `display` beats the UA's `[hidden] { display: none }` - the same companion rule
   .cmp-files, .estcell, .btn and .mapadd each needed. Without it the hidden tab renders. */
.docside[hidden] { display: none; }
/* the pane's own header stays put while its text scrolls under it */
.paneheadrow { position: sticky; top: 0; z-index: 1; display: flex; align-items: baseline;
  gap: .6rem; padding: .8rem 0 .5rem; background: var(--panel);
  border-bottom: 1px solid var(--line-soft); margin-bottom: .7rem; }
/* nothing to report, so no header row - the pane takes back the top padding it gave */
.docpane.nohead { padding-top: 1.1rem; }
.seclabel { font: 600 .64rem/1.5 var(--mono); color: var(--dim); text-transform: uppercase;
  letter-spacing: .09em; }
.paneempty { padding: 1.4rem 0; }
.pageblock { padding-top: .5rem; border-radius: .35rem; }
.pageblock + .pageblock { margin-top: 1.1rem; border-top: 1px solid var(--line-soft); }
/* unselectable on purpose: the label is the pane's own furniture, not the document's
   words, so a passage dragged across a page break never carries it into the quote */
.pagemark { font: 600 .62rem/1.5 var(--mono); color: var(--accent);
  text-transform: uppercase; letter-spacing: .08em; margin: .35rem 0 .4rem;
  -webkit-user-select: none; user-select: none; }
/* the source is read, not skimmed: sans, generous leading, and white-space preserved so
   the extractor's own line breaks survive */
.pagetext { font-family: var(--sans); font-size: .89rem; line-height: 1.72; color: var(--ink);
  white-space: pre-wrap; overflow-wrap: anywhere; margin: 0 0 .75rem; }
.pagetext:last-child { margin-bottom: 0; }
/* the document's own heading — weight and air, not a second type scale competing with
   the page's */
.pagetext.pagesub { font-weight: 700; font-size: .92rem; line-height: 1.45;
  margin: 1.4rem 0 .4rem; }
.pagetext.pagesub:first-child { margin-top: 0; }
/* a long figure label wraps under itself rather than shoving the value off its own row */
.figtable .flabel { hyphens: auto; }
.pageblock.landed { background: var(--accent-soft); }
.docside .sec { background: var(--panel); border: 1px solid var(--line);
  border-radius: .5rem; padding: .8rem .95rem; margin-bottom: .7rem; }
.docside .seclabel { display: block; margin-bottom: .5rem; }
.takeaways { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
/* The marker sits on the FIRST line of its takeaway and a hairline parts one from the next
   (docs/ui-feedback-2026-09-14.md, DO-1). Baseline-aligned, the smaller glyph sat low against
   line one, and a three-line takeaway read as unanchored with nothing to say where it ended.
   The marker's line box is the text's first line box (.88rem x 1.55), so the glyph centres on
   that line whatever the takeaway wraps to. */
.takeaways li { display: flex; align-items: flex-start; gap: .4rem; font-family: var(--sans);
  font-size: .88rem; line-height: 1.55; padding: .45rem 0; }
.takeaways li + li { border-top: 1px solid var(--line-soft); }
.takeaways li:first-child { padding-top: 0; }
.takeaways li:last-child { padding-bottom: 0; }
.takeaways li::before { content: "▪"; color: var(--accent); flex: none; font-size: .7rem;
  line-height: calc(.88rem * 1.55); }
.takeaways .ttext { min-width: 0; }
/* a page cite is a control when the pane can scroll there, and plainly inert when it
   cannot — a button that goes nowhere is worse than no button */
.pcite { flex: none; font: 600 .62rem/1.4 var(--mono); color: var(--accent);
  background: var(--accent-soft); border: 1px solid var(--accent-line); border-radius: .3rem;
  padding: .04rem .32rem; cursor: pointer; }
.pcite:hover { background: var(--accent); color: var(--on-accent); }
.pcite.dead { color: var(--dim); background: var(--panel2); border-color: var(--line);
  cursor: help; }
/* An evidence row: a rail item whose exact words were LOCATED in the text, so clicking it
   marks them. Styled as text rather than as a control — the rail is a reading surface, and
   fifteen buttons in a figure table would read as a toolbar. The affordance is the dotted
   underline plus the cursor, and it only ever renders on items that actually have a span. */
.evrow { display: inline; text-align: left; font: inherit; color: inherit; padding: 0;
  margin: 0; border: 0; background: none; cursor: pointer;
  border-bottom: 1px dotted var(--accent-line); }
.evrow:hover, .evrow:focus-visible { border-bottom-color: var(--accent);
  background: var(--accent-soft); }
.evrow.on { background: var(--accent-soft); border-bottom-style: solid;
  border-bottom-color: var(--accent); }
/* A takeaway's anchor is the paragraph it was WRITTEN FROM, not a quotation of it. It must
   never read like the verbatim ones, so both the row and the mark are dashed and dimmer:
   the page is showing where a claim came from, which is an inference, and saying so. */
.evrow.derived { border-bottom-style: dashed; border-bottom-color: var(--line); }
.evrow.derived:hover, .evrow.derived:focus-visible { border-bottom-color: var(--dim); }
.figtable .evrow { border-bottom: 1px dotted var(--accent-line); }
/* the located span itself, and the paragraph holding it */
.evmark { background: var(--accent-soft); color: inherit; border-radius: .15rem;
  box-shadow: 0 0 0 1px var(--accent-line); }
.evmark.derived { background: none; box-shadow: none;
  border-bottom: 1px dashed var(--dim); border-radius: 0; }
.pagetext.evtarget { background: var(--panel2); border-radius: .35rem;
  box-shadow: 0 0 0 .45rem var(--panel2); }
.figtable { width: 100%; border-collapse: collapse; font-size: .84rem; }
.figtable td { padding: .3rem 0; border-top: 1px solid var(--line-soft); vertical-align: top; }
.figtable tr:first-child td { border-top: none; }
.figtable .flabel { color: var(--dim); font-family: var(--sans); padding-right: .6rem; }
.figtable .fval { font-family: var(--mono); color: var(--ink); font-variant-numeric: tabular-nums;
  text-align: right; white-space: nowrap; }
.figtable .fcite { text-align: right; padding-left: .5rem; width: 1%; white-space: nowrap; }
.docquote { font-size: .87rem; }
.docquote + .docquote { margin-top: .6rem; }
.silenton { font-family: var(--sans); font-size: .86rem; line-height: 1.6; color: var(--dim);
  margin: 0; }
.fullsum > summary { list-style: none; cursor: pointer; }
.fullsum > summary::-webkit-details-marker { display: none; }
.fullsum > summary::before { content: "▸ "; color: var(--dim); }
.fullsum[open] > summary::before { content: "▾ "; }
.fullsum .summarybox { margin-top: .6rem; max-width: none; }
.dochead .docmeta { margin: .2rem 0 0; }
/* `auto`, not 0: the shorthand here sets all four sides, and a 0 would un-centre the chips
   from the header and the panel they sit between (the cap above is what centres them) */
.chips.doclinks { margin: .1rem auto .2rem; }
@media (max-width: 1100px) {
  /* The 1500px cap never binds at this width - the window minus the nav is well under it -
     so nothing about the page's width needs resetting here any more. The pane's scroll box
     does: a 100vh-4rem box on a short window leaves no page under it. */
  .docpane { max-height: 70vh; }
}
/* a verbatim quote the extracted text does not contain: the analyst should see that the
   passage could not be located before they rely on it (the page cite goes inert too) */
.docquote.ungrounded { border-left-color: var(--warn); }
/* The rail is the analysis: its section labels carry the accent so the eye can find the
   sections, and a weighted figure inside any of them is what an analyst scans for. */
.docside .seclabel { color: var(--accent); }
.docside b, .docquote b, .silenton b { font-weight: 700; color: var(--ink); }
.summarybox b { font-weight: 700; color: var(--ink); }
.docside .summarybox { border-left: none; background: none; padding: 0; margin: 0; }
.docside .summarybox p { margin: 0 0 .7rem; }
.docside .summarybox p:last-child { margin-bottom: 0; }
/* the disclosure marker and its label belong on one line — .docside .seclabel is a block,
   which was leaving the triangle stranded above the words */
.fullsum > summary .seclabel { display: inline; margin: 0; }

/* ── The Inbox (phase A) ─────────────────────────────────────────────────────
   Three visual jobs: keep the mail stream's anatomy identical to News and Social (it reuses
   .newsrow wholesale; the one inbox-only restyle is read state, below), make that state
   legible at a glance the way mail does, and make the pinned decision band read as a
   DIFFERENT KIND OF THING from the mail below it. */

/* The mail badge. (0,2,0) beats the plain .navcount (0,1,0) on specificity, not on source
   order, so it cannot be undone by a later rule the way an equal-specificity override
   could. Accent, never --err: Activity's red is a debt, and mail arriving is normal - a
   permanent red number in the corner of the eye is a number you stop seeing. */
.navcount.mail { color: var(--accent); background: var(--accent-soft);
  border-color: var(--accent-line); }

/* The decision band: Activity's open rows, pinned above the stream. Warm, because it is
   the one thing on this page that is waiting on the analyst. */
.deckband { border: 1px solid rgba(229, 192, 123, .32); background: var(--warn-soft);
  border-radius: .5rem; padding: .6rem .75rem .4rem; margin: .3rem 0 1rem; }
.deckband .dbhd { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-bottom: .45rem; font-size: .85rem; }
.deckband .dbhd .icon { color: var(--warn); flex: none; }
.deckband .dbhd b { color: var(--warn); }
.deckband .dbhd .pmore { margin-left: auto; }
.deckband .dbrow { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  padding: .4rem 0; border-top: 1px solid var(--line-soft); font-size: .85rem; }
.deckband .dbrow .dbtext { color: var(--dim); min-width: 0; overflow-wrap: anywhere; }
.deckband .dbrow .actions { display: inline-flex; gap: .3rem; margin-left: auto; flex: none;
  align-items: center; }
.deckband .dbrow .actions form { display: inline; }

/* THE tab strip - the Inbox's four lanes and the document page's two views (docs/
   ui-feedback-2026-09-14.md, IN-2 and DO-3). Gmail's shape: text on a rule, the one you are
   in underlined in the accent, a count beside it in muted mono so it reads as data rather
   than as part of the word.
   Its own class, NOT .filters and NOT .chip: chips are refinements of one list elsewhere in
   the cockpit, while these switch between whole views, and .filters restyles any bare
   <span> it contains into a kicker - which is every count on this strip.
   Generic on purpose: the Inbox's tabs are LINKS (each lane is its own URL) and the
   document page's are role=tab buttons (both panels are already in the page), and the two
   have to look identical or the cockpit grows two tab languages.
   Wraps rather than overflowing (the .viewtabs lesson): each tab is nowrap by design, so on
   a narrow viewport the row could only ever grow past the edge and scroll the whole page. */
.tabstrip { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .1rem;
  margin: .5rem 0 .9rem; border-bottom: 1px solid var(--line); }
.tab { display: inline-flex; align-items: baseline; gap: .4rem; padding: .45rem .8rem;
  color: var(--dim); font-size: .92rem; border-bottom: 2px solid transparent;
  margin-bottom: -1px; white-space: nowrap; }
/* a <button> tab has to shed the button chrome the shared .btn family never gave it */
button.tab { background: none; border: 0; border-bottom: 2px solid transparent;
  font-family: inherit; cursor: pointer; }
.tab:hover { color: var(--accent); text-decoration: none; background: var(--accent-soft); }
.tab.on { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }
.tab .n { font: .72rem/1 var(--mono); color: var(--dim); font-variant-numeric: tabular-nums; }
.tab.on .n { color: var(--accent); }
/* The door out of the mail and back. Muted and pushed to the far end, because it is the exit
   rather than a fifth lane; when you are through it, a wash says so instead of the accent
   underline - the underline is which LANE you are in, and you are still in one in there. */
.tab.door { margin-left: auto; }
.tab.door.on { color: var(--ink); background: var(--panel2); border-bottom-color: transparent;
  border-top-left-radius: .4rem; border-top-right-radius: .4rem; }
/* the lit door still NAVIGATES (it is the way back out), so it still answers the pointer -
   its own (0,3,0) wash would otherwise swallow .tab:hover's (0,2,0) one */
.tab.door.on:hover { background: var(--accent-soft); color: var(--accent); }

/* The search box, above the strip on the page's right edge - the same edge News puts its
   own on, and the same column the page head's buttons stand in, so the two read as one
   corner. .newssearch carries the box itself, so the two pages cannot drift apart; this is
   only what puts it there. */
.mailsearch { display: flex; align-items: center; margin: .9rem 0 0; }

/* Opening a row is what marks it read, so the title has to be a submit button - a bare
   anchor could navigate but not write. Strip the button chrome so it reads as the link it
   replaces, including the wrap behaviour a long headline needs. */
.openform { display: inline; }
button.openlink { background: none; border: none; padding: 0; margin: 0; font: inherit;
  color: var(--accent); cursor: pointer; text-align: left; overflow-wrap: anywhere; }
button.openlink:hover { text-decoration: underline; }
.newsrow.unread button.openlink { font-weight: 600; }
/* Read state, the way mail shows it. An UNREAD row is lit: the same wash and inset bar a row
   you were sent to wears on News and Social (.newsrow:target), so one look means "this one"
   everywhere. A row that is not unread - read, or in the Trash - goes grey: title, company and
   body drop to --dim and only the source badge keeps its colour, so the backlog stands out from
   what has already been worked. Scoped to the mail stream (.feedrows.mail), never to .newsrow:
   News drains by scroll, and a wash that vanished as a row was counted read would flicker under
   the pointer; the Inbox changes state only when you click. (0,3,0) beats the .feedrows .newsrow
   reset (0,2,0) on specificity, not source order; the grey rules sit above button.openlink
   (0,1,1), the bare `a` and .summary (0,1,0) the same way. */
.feedrows.mail .newsrow.unread { background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent); }
.feedrows.mail .newsrow:not(.unread) .openlink,
.feedrows.mail .newsrow:not(.unread) .newshead a,
.feedrows.mail .newsrow:not(.unread) .summary { color: var(--dim); }
/* Under the pointer (or keyboard focus) a read row wakes up together with its actions: the
   title turns back into the link it is, so grey never reads as disabled. */
.feedrows.mail .newsrow:not(.unread):hover .openlink,
.feedrows.mail .newsrow:not(.unread):focus-within .openlink { color: var(--accent); }
/* The whole row opens the item now, so the whole row has to say so: a pointer over all of
   it, and a wash under it. Two washes, because the row already has a background it must
   stay distinguishable from - an unread row is lit accent-soft and deepens, a read one is
   flat and lifts. Both hover selectors are (0,5,0) - :not() carries its argument's weight -
   which beats the .feedrows .newsrow reset and the lit-row rule above on specificity rather
   than on source order.
   The title keeps its own underline on hover: it is still the row's accessible name and the
   keyboard road, and a row-wide wash is not a focus indicator. */
.feedrows.mail .newsrow { cursor: pointer; }
.feedrows.mail .newsrow.unread:hover { background: rgba(92, 156, 245, .17); }
.feedrows.mail .newsrow:not(.unread):hover { background: var(--panel2); }

/* Source badges for the three lanes that had none (News already owns .badge.news). Each
   borrows the colour its own tab uses, so a row's origin is recognisable before it is read. */
.badge.social { background: rgba(232, 164, 101, .12); border-color: rgba(232, 164, 101, .45);
  color: var(--star); }
.badge.podcast { background: rgba(102, 217, 194, .10); border-color: rgba(102, 217, 194, .40);
  color: var(--live); }
.badge.audio { background: var(--accent-soft); border-color: var(--accent-line);
  color: var(--accent); }

/* The why-line: the row says, in words, why it reached you. Inline so it flows after the
   arrow rather than dropping to its own line under .meta.matchline's display:block. */
.newsrow .why { font-family: var(--mono); font-size: .74rem; color: var(--dim); }

/* The screen cannot see part of the book. Distinct from the decision band above it: that
   one is work waiting, this one says the number on this page is an understatement. */
.unimiss { border: 1px solid rgba(223, 138, 138, .32); background: var(--err-soft);
  border-radius: .5rem; padding: .6rem .8rem; margin: .3rem 0 1rem; font-size: .85rem;
  line-height: 1.55; }
.unimiss b { color: var(--err); }
.unimiss .ro { display: block; margin-top: .3rem; }

/* ══ the public landing page (GET /, templates/landing.html) ══════════════════
   Everything here is scoped .lp so it cannot reach a product page, and every rule
   is additive - no class the app owns is redefined. The type scale is the only
   thing a landing page needs that the product does not have: the product's h1 is
   1.62rem, sized for a page sitting inside the 236px sidebar, and a hero is not.
   body.landing drops that sidebar offset, the way the auth cards have no sidebar. */
body.landing { padding-left: 0; }
.lp { --lpshell: 1000px; --lpgut: 156px; background: var(--bg); }
.lp .lpshell { width: 100%; max-width: var(--lpshell); margin: 0 auto; padding: 0 26px; }

/* nav */
.lp .lpnav { border-bottom: 1px solid var(--line); }
.lp .lpnavin { display: flex; align-items: center; gap: 2rem; height: 56px; }
.lp .lpmark { display: flex; align-items: baseline; gap: .5rem;
  font-family: var(--serif); font-weight: 700; font-size: .88rem;
  letter-spacing: -.05em; color: var(--ink); }
.lp .lpmark i { font-style: normal; color: var(--accent); font-size: .78rem; }
.lp .lpnavlinks { display: flex; gap: 1.5rem; }
.lp .lpnavlinks span { color: var(--dim); font-size: .81rem; }
.lp .lpnavcta { display: flex; align-items: center; gap: 1rem; margin-left: auto; }
.lp .lpnavcta .plain { color: var(--dim); font-size: .81rem; }

/* hero: full width, no gutter - the widest type on the page needs the room */
.lp .lphero { padding: 5.6rem 0 4rem; display: flex; flex-direction: column; gap: 1.6rem; }
.lp .lphero h1 { font-family: var(--serif); font-weight: 500;
  font-size: clamp(1.6rem, 4.6vw, 3.1rem); line-height: 1.06; letter-spacing: -.045em;
  margin: 0; max-width: 21ch; text-wrap: balance; }
.lp .lphero h1 em { font-style: normal; color: var(--accent); }
.lp .lpsub { max-width: 60ch; font-size: .97rem; line-height: 1.64; color: var(--dim); }
.lp .lpsub b { color: var(--ink); font-weight: 500; }
.lp .lpacts { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem;
  margin-top: .3rem; }
.lp .lpacts .btn { padding: .6rem 1.2rem; font-size: .85rem; }
.lp .lpfine { color: var(--dim); font-size: .78rem; opacity: .82; }

/* section skeleton: hairline dividers, eyebrow in a left gutter (the app's pagehead
   reads label-over-title; at landing width it reads better label-beside-title) */
.lp .lpsec { border-top: 1px solid var(--line); }
.lp .lpsecin { display: grid; grid-template-columns: var(--lpgut) minmax(0, 1fr);
  gap: 2.4rem; padding: 4.8rem 0; }
.lp .lpsechd { display: flex; flex-direction: column; gap: .35rem; }
.lp .lpsechd .n { font: 400 .66rem/1.5 var(--mono); color: var(--dim); opacity: .72;
  font-variant-numeric: tabular-nums; }
.lp .lpsecin.full { grid-template-columns: minmax(0, 1fr); }
.lp .lpsecbody { display: flex; flex-direction: column; gap: 2rem; min-width: 0; }
.lp .lpsectitle { font-family: var(--serif); font-weight: 500; letter-spacing: -.04em;
  font-size: clamp(1.25rem, 2.2vw, 1.65rem); line-height: 1.18; margin: 0;
  text-wrap: balance; }
.lp .lpbody { max-width: 64ch; color: var(--dim); font-size: .9rem; line-height: 1.66; }
.lp .lpbody b { color: var(--ink); font-weight: 500; }

/* the walkthrough stage: the one loud object on the page */
.lp .lpstage { border-top: 1px solid var(--line); padding: 4rem 0 5rem; }
.lp .lpstagehd { display: flex; flex-wrap: wrap; align-items: flex-end;
  justify-content: space-between; gap: 1rem; margin-bottom: 1.4rem; }
.lp .lpstagehd h2 { font-family: var(--serif); font-weight: 500; letter-spacing: -.04em;
  font-size: clamp(1.2rem, 2.1vw, 1.55rem); margin: .4rem 0 0; }
.lp .lpframe { border: 1px solid var(--line); border-radius: .55rem; overflow: hidden;
  background: #000; box-shadow: 0 24px 70px rgba(0, 0, 0, .85); }
.lp .lpfbar { display: flex; align-items: center; gap: .6rem;
  padding: .5rem .85rem; border-bottom: 1px solid var(--line-soft); }
.lp .lpfbar .m { font-family: var(--serif); font-weight: 700; font-size: .68rem;
  letter-spacing: -.05em; color: var(--accent); }
.lp .lpfbar .path { color: var(--dim); font-size: .73rem; }
.lp .lpfbar .path b { color: var(--ink); font-weight: 500; }
.lp .lpfbar .tail { margin-left: auto; color: var(--dim); opacity: .74;
  font-size: .71rem; font-variant-numeric: tabular-nums; }
.lp .lpstagecap { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 2.2rem; margin-top: 1.4rem; }

/* chapter list: a real sequence, so the timecodes carry information */
.lp .lpchaps { display: flex; flex-direction: column; }
.lp .lpchap { display: grid; grid-template-columns: 3.2rem minmax(0, 1fr); gap: .9rem;
  padding: .5rem 0; border-top: 1px solid var(--line-soft); font-size: .83rem; }
.lp .lpchap:first-child { border-top: 0; }
.lp .lpchap .t { color: var(--dim); opacity: .74; font-variant-numeric: tabular-nums; }
.lp .lpchap .d { color: var(--dim); }
.lp .lpchap.on .t { color: var(--accent); opacity: 1; }
.lp .lpchap.on .d { color: var(--ink); }
.lp .lpchap.on .d em { font-style: normal; color: var(--accent); }

/* the house rule */
.lp .lprule { font-family: var(--serif); font-weight: 400;
  font-size: clamp(1.15rem, 2.4vw, 1.8rem); line-height: 1.26; letter-spacing: -.045em;
  margin: 0; }
.lp .lprule b { font-weight: 700; color: var(--accent); }
.lp .lpsteps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.8rem; }
.lp .lpstep { display: flex; flex-direction: column; gap: .5rem;
  padding-top: .8rem; border-top: 2px solid var(--line); }
.lp .lpstep h3 { font-family: var(--serif); font-weight: 600; font-size: .88rem;
  letter-spacing: -.03em; margin: 0; }
.lp .lpstep p { margin: 0; color: var(--dim); font-size: .82rem; line-height: 1.6; }
.lp .lpstep.s1 { border-top-color: var(--warn); }
.lp .lpstep.s2 { border-top-color: var(--accent); }
.lp .lpstep.s3 { border-top-color: var(--ok); }

/* the pull-quote slot, deliberately and visibly empty */
.lp .lpqslot { display: flex; flex-direction: column; gap: .9rem;
  padding: 2rem 1.9rem; border: 1px dashed var(--line); border-radius: .5rem;
  background: var(--panel); }
.lp .lpqslot .q { font-family: var(--serif); font-weight: 300;
  font-size: clamp(1rem, 1.9vw, 1.3rem); line-height: 1.42; letter-spacing: -.04em;
  color: var(--dim); opacity: .74; max-width: 46ch; margin: 0; }

/* posture */
.lp .lpfacts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem 2.2rem; }
.lp .lpfact { display: flex; flex-direction: column; gap: .3rem; }
.lp .lpfact h3 { font-family: var(--serif); font-weight: 600; font-size: .85rem;
  letter-spacing: -.03em; margin: 0; }
.lp .lpfact p { margin: 0; color: var(--dim); font-size: .82rem; line-height: 1.6;
  max-width: 46ch; }

/* close + footer */
.lp .lpclose { border-top: 1px solid var(--line); padding: 4.8rem 0;
  display: flex; flex-direction: column; gap: 1.2rem; }
.lp .lpclose h2 { font-family: var(--serif); font-weight: 500; letter-spacing: -.045em;
  font-size: clamp(1.4rem, 3vw, 2.15rem); line-height: 1.12; margin: 0; max-width: 25ch;
  text-wrap: balance; }
.lp .lpfoot { border-top: 1px solid var(--line); padding: 2.2rem 0 2.8rem; }
.lp .lpfootin { display: flex; flex-wrap: wrap; gap: 2.2rem; align-items: flex-start; }
.lp .lpfootcol { display: flex; flex-direction: column; gap: .4rem; min-width: 8rem; }
.lp .lpfootcol span { font-size: .79rem; color: var(--dim); }
.lp .lpfootcol .eyebrow { color: var(--dim); opacity: .8; margin-bottom: .2rem; }
.lp .lpfootend { margin-left: auto; text-align: right; display: flex;
  flex-direction: column; gap: .35rem; align-items: flex-end; }

/* the /inbox snippet */
.lp .lpinbox { padding: 1rem; box-shadow: none; }
.lp .lpinbox .deckband { margin-bottom: 1rem; }
.lp .lpinbox .cardfoot .actions { visibility: visible; }
.lp .lpinbox .openlink { color: var(--ink); font: 500 .88rem/1.5 var(--sans); }
.lp .lpinbox .newsrow.unread .openlink { color: var(--ink); font-weight: 600; }
.lp .lpinbox .btn.small { pointer-events: none; }

/* the constellation figure: annotatable inline SVG, no canvas, no script */
.kbmap { display: block; width: 100%; height: auto; }
.kbmap .halo { opacity: .17; }
.kbmap .lbl { font: 500 12px/1 var(--mono); }
.kbmap .co { fill: var(--accent); }
.kbmap .th { fill: #c98ac9; }          /* the peer_reported hue already in app.css */
.kbmap .doc { fill: var(--warn); }
.kbmap .comp { fill: #000; stroke: var(--dim); stroke-width: 1.2; }
.kbmap .e-co { stroke: var(--accent); stroke-opacity: .34; }
.kbmap .e-th { stroke: #c98ac9; stroke-opacity: .32; }
.kbmap .e-doc { stroke: var(--warn); stroke-opacity: .26; }
.kbmap .hull { fill: #c98ac9; fill-opacity: .07; stroke: #c98ac9; stroke-opacity: .22;
  stroke-dasharray: 5 5; }
.kbmap .lbl.co { fill: var(--accent); }
.kbmap .lbl.th { fill: #c98ac9; }
.lp .lplegend { display: flex; flex-wrap: wrap; gap: .35rem .9rem;
  padding: .55rem .9rem .75rem; font-size: .71rem; color: var(--dim); }
.lp .lplegend span { display: inline-flex; align-items: center; gap: .35rem; }
.lp .lplegend i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.lp .lplegend .l-co { color: var(--accent); }
.lp .lplegend .l-th { color: #c98ac9; }
.lp .lplegend .l-doc { color: var(--warn); }
.lp .lplegend .l-comp { color: var(--dim); }
.lp .lplegend .l-comp i { background: transparent; border: 1px solid var(--dim); }

@media (max-width: 900px) {
  .lp .lpsecin { grid-template-columns: minmax(0, 1fr); gap: 1.2rem; padding: 3.6rem 0; }
  .lp .lpsteps, .lp .lpfacts, .lp .lpstagecap { grid-template-columns: minmax(0, 1fr);
    gap: 1.4rem; }
  .lp .lpnavlinks { display: none; }
  .lp .lpnavin { flex-wrap: wrap; height: auto; gap: .8rem; padding: .8rem 0; }
  .lp .lpfootend { margin-left: 0; text-align: left; align-items: flex-start; }
}
