/* Opinionated Systems — the one stylesheet.
   Banknote idiom (after csszengarden.com/126 "C-Note"): intaglio green-black
   ink on note paper, guilloché lattice at the head, engraved letterspaced
   caps, double-hairline certificate rules, square corners. The Tufte rules
   underneath are unchanged: type carries the page, color is strictly
   semantic, and no number renders without its denominator. */

:root {
  --ink: #212823;      /* intaglio green-black */
  --paper: #f7f6ef;    /* note paper — cream with a green hint */
  --muted: #5d6b5d;    /* the C-Note gray-green, darkened for contrast */
  --rule: #d5dacb;
  --rule-soft: #e4e7da;
  --up: #266b45;       /* treasury green — supports / hit, semantic only */
  --down: #8f2f28;     /* seal red — challenges / miss, semantic only */
  --accent: #42557b;   /* guilloché slate — interactive elements only */
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --serif: Georgia, "Times New Roman", serif;
  --display: Didot, "Bodoni MT", "Bodoni 72", "GFS Didot", Georgia, serif;
  /* vertical rhythm scale — every gap derives from these, so stacked
     components share one grid instead of drifting on hand-picked rems */
  --space-xs: 0.4rem;
  --space-sm: 0.7rem;
  --space: 1.4rem;    /* one rhythm unit */
  --space-lg: 2.1rem;
  --space-xl: 2.8rem;
  /* content measures: prose reads narrow (--measure), the page frame is wider.
     --frame stays at the current 44rem for now — the research-sidebar rail
     geometry is pinned to it; widening it needs a coordinated rail pass. */
  --measure: 36rem;   /* text column */
  --frame: 44rem;     /* page frame */
  /* one quiet surface tint — depth without chrome */
  --surface: color-mix(in srgb, var(--ink) 4%, var(--paper));
  /* ground behind transparent logos/marks — stays LIGHT in both themes
     (brand marks are drawn for light grounds; the assets themselves ship
     with transparency so this is the only place the ground is chosen) */
  --logo-chip: #f7f6ef;
  --tint-accent: color-mix(in srgb, var(--accent) 9%, transparent);
  --tint-up: color-mix(in srgb, var(--up) 12%, transparent);
  --tint-down: color-mix(in srgb, var(--down) 12%, transparent);
  /* engine-turned lattice line, for the masthead band */
  --lattice: color-mix(in srgb, var(--muted) 55%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e4e8dc;    /* pale sage — ink on the engraver's plate */
    --paper: #141713;
    --muted: #97a292;
    --rule: #2c3129;
    --rule-soft: #232720;
    --up: #79b893;
    --down: #d18f85;
    --accent: #94aacf;
    /* Graph kind palette, lifted for the dark plate — the light values are
       mid-tone and go muddy against #141713. Same hues, so a kind keeps its
       identity across themes. */
    --k-company: #8fabd8;
    --k-security: #79bda6;
    --k-person: #c6a570;
    --k-filing: #b3a3c4;
    --k-prediction: #d8a48c;
  }
}

* { box-sizing: border-box; }

/* the hidden attribute must always win, whatever display the element has */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font: 17px/1.55 var(--serif);
  color: var(--ink);
  background: var(--paper);
  /* the page itself never scrolls sideways; wide tables scroll inside their
     own .table-scroll instead of being clipped off the right edge (mobile) */
  overflow-x: hidden;
}

/* the note's engraved edge: an engine-turned band across every page head */
body::before {
  content: "";
  display: block;
  height: 9px;
  background:
    repeating-linear-gradient(45deg, var(--lattice) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(-45deg, var(--lattice) 0 1px, transparent 1px 4px);
  border-bottom: 1px solid var(--rule);
}

main {
  max-width: var(--frame);
  margin: 0 auto;
  padding: var(--space-lg) 1.25rem calc(var(--space) * 4);
}

/* ---- type scale: engraved display caps over Georgia text ---------------- */

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 500;
  line-height: 1.25;
  text-wrap: balance;
}
h1 {
  font-size: 1.4rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  margin: 0 0 var(--space);
}
h2 {
  font-size: 1.18rem;
  font-variant-caps: small-caps;
  letter-spacing: 0.08em;
  margin: var(--space-xl) 0 var(--space-sm);
  padding-bottom: var(--space-xs);
  border-bottom: 3px double var(--rule);
}
/* h3 is a sub-section divider; a single hairline relates it to .band-head
   without the heavier double-rule the page heading (h2) carries */
h3 {
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  margin: var(--space-lg) 0 var(--space-sm);
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--rule-soft);
}

p { max-width: var(--measure); margin: var(--space-sm) 0; }
ul, ol { max-width: var(--measure); }

a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
  text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
a:hover { text-decoration-color: var(--accent); }

/* statement links read as prose, not chrome */
a.plain { color: inherit; text-decoration: none; }
a.plain:hover { text-decoration: underline; text-decoration-color: var(--muted); }

hr {
  border: 0;
  border-top: 3px double var(--rule);
  margin: calc(var(--space) * 1.5) 0;
}

/* page inscription: the microprint line under a page h1 naming the surface's
   nature on the shared/private axis (like a note stating what it is) */
h1 + p.label { margin: -1.1rem 0 0.9rem; }

/* labels above data — mono microprint, letterspaced */
.label {
  font-family: var(--mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--muted);
}

/* numbers align in columns; every figure carries its denominator inline */
.num { font-family: var(--mono); font-size: 0.92em; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* a heading with an inline action (e.g. a share button) beside the title text */
.title-with-action { display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
.title-with-action .title-action button { font-size: 0.8rem; }
/* leaderboard sits above the voices directory — separate them with the site's
   double-rule so the two lists don't run together */
#leaderboard-sec { border-bottom: 3px double var(--rule); padding-bottom: 1.5rem; margin-bottom: 1.75rem; }
/* by-security holdings: a dense, reader-sortable table (click a header to sort,
   a row to reveal that security's filings) */
table.holdings th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
table.holdings th.sortable:hover { color: var(--ink); }
table.holdings th[aria-sort="ascending"]::after { content: " ▲"; font-size: 0.65em; vertical-align: middle; }
table.holdings th[aria-sort="descending"]::after { content: " ▼"; font-size: 0.65em; vertical-align: middle; }
table.holdings tr.sec-row { cursor: pointer; }
table.holdings tr.sec-row:hover td,
table.holdings tr.sec-row[aria-expanded="true"] td { background: var(--rule-soft); }
table.holdings tr.sec-detail > td { padding: 0.25rem 0.75rem 0.9rem; }
/* Ticker + company stacked in the merged "Security" cell: ticker leads, the
   company name sits quieter beneath it. */
.sec-ticker { display: block; font-family: var(--mono); }
.sec-company { display: block; font-size: 0.85em; color: var(--muted); margin-top: 0.05rem; }
.denom { color: var(--muted); font-size: 0.85em; }

.up { color: var(--up); }
.down { color: var(--down); }
.muted { color: var(--muted); }

/* ---- shell nav: the note's title strip ---------------------------------- */

header nav {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.35rem 1.1rem;
  max-width: 44rem;
  margin: 0 auto;
  padding: 0.9rem 1.25rem 0.75rem;
  border-bottom: 1px solid var(--rule-soft);
  font-family: var(--mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
}
header nav a {
  text-decoration: none;
  padding: 0.35rem 0;   /* honest touch target without visual bulk */
}
header nav a[aria-current="page"] {
  color: var(--ink);
  border-bottom: 2px solid var(--accent);
}
header nav .nav-id {
  margin-left: auto;
  display: inline-flex;
  align-items: baseline;
  gap: 1.1rem;
}
header nav .nav-right { text-transform: none; letter-spacing: 0; }

@media (max-width: 40rem) {
  header nav .nav-right { display: none; }  /* email yields to the links */
  main { padding-left: 0.75rem; padding-right: 0.75rem; }  /* reclaim edge width */
  .co-name-sm { max-width: 8rem; }
}

/* ---- forms: quiet, obvious focus --------------------------------------- */

form { max-width: 28rem; }
/* thesis intake: as wide as the theses table above it, not the generic
   28rem login-style form — a multi-line thesis reads cramped in a narrow box
   sitting under a table that spans the whole center column. */
#intake-form, #confirm-form { max-width: none; }
label { display: block; margin-bottom: 0.9rem; }
input[type="email"],
input[type="text"],
input[type="url"],
input[type="number"],
textarea {
  display: block;
  width: 100%;
  margin-top: 0.35rem;
  padding: 0.6rem 0.65rem;
  font: inherit;          /* 17px base also prevents iOS focus zoom */
  color: inherit;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 0;
}
input:focus-visible, textarea:focus-visible,
button:focus-visible, a:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
button {
  font-family: var(--mono);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  min-height: 2.6rem;     /* comfortable touch target */
  padding: 0.5rem 1.4rem;
  color: var(--paper);
  background: var(--accent);
  border: 0;
  border-radius: 0;
  cursor: pointer;
}
button[disabled] { opacity: 0.5; cursor: default; }

/* secondary actions: quiet outline, same target size */
button.quiet, a.quiet {
  font-family: var(--mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  min-height: 2rem;
  padding: 0.25rem 0.7rem;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 0;
  cursor: pointer;
  text-decoration: none;
}
button.quiet:hover, a.quiet:hover { border-color: var(--accent); }

/* status lines rendered by JS — text only, never markup */
.status { margin-top: 1rem; }
.status.error { color: var(--down); }
.status:empty { margin: 0; }

/* ---- data tables ------------------------------------------------------- */
/* Wide tables scroll inside their own container; the page never scrolls
   sideways. */

.table-scroll { overflow-x: auto; max-width: 100%; -webkit-overflow-scrolling: touch; }
table {
  border-collapse: collapse;
  width: 100%;
  margin: 0.4rem 0;
}
th {
  text-align: left;
  font-family: var(--mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--muted);
  font-weight: 400;
  border-bottom: 1px solid var(--rule);
  padding: 0.35rem 0.9rem 0.35rem 0;
  white-space: nowrap;
}
td {
  padding: 0.55rem 0.9rem 0.55rem 0;
  border-bottom: 1px solid var(--rule-soft);
  vertical-align: baseline;
}
td:last-child, th:last-child { padding-right: 0; }
td .event-meta { display: block; margin-top: 0.1rem; }

/* ---- event lists: statements as prose, metadata as a quiet suffix ------ */

ul.events { list-style: none; margin: 0.4rem 0 0; padding: 0; }
ul.events li {
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--rule-soft);
  max-width: none;
}
ul.events li:last-child { border-bottom: 0; }
/* Theme subheader inside a stance's evidence list — leads each grouped run of
   rows (byThemeSize). Quieter than the stance .label above it, and it heads
   rows rather than being one, so no rule line. */
ul.events li.theme-head {
  padding: 0.85rem 0 0.1rem;
  border-bottom: 0;
  font-family: var(--mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}
ul.events li.theme-head:first-child { padding-top: 0.2rem; }
/* a site's feeds, nested under one host header on the voice page — indented,
   tighter, and quieter than a top-level channel row */
ul.events.channel-sub { margin: 0.3rem 0 0 1.1rem; }
ul.events.channel-sub li { padding: 0.28rem 0; border-bottom: 0; font-size: 0.95em; }
.event-meta {
  font-family: var(--mono);
  font-size: 0.72rem;
  line-height: 1.6;
  color: var(--muted);
  display: block;
  margin-top: 0.25rem;
}
.event-meta a { color: var(--muted); }
.event-meta a:hover { color: var(--accent); }

/* officials-trading list: politician heads the group, each disclosed trade a
   compact indented line beneath (no trade merged into another) */
.official-head { margin-bottom: 0.15rem; }
.official-trade { padding: 0.1rem 0 0.1rem 1rem; line-height: 1.5; }
.official-trade .event-meta { display: inline; margin-top: 0; }

/* ---- ticker headings carry their counts inline ------------------------- */

.ticker-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.35rem 0.8rem;
  margin: calc(var(--space) * 1.4) 0 0;
}
.ticker-head h1, .ticker-head h3 { margin: 0; }
/* company name beside the ticker symbol — named, never a bare symbol */
.ticker-head h1 .co-name {
  font-weight: 400; font-size: 0.9rem; color: var(--muted);
  margin-left: 0.6rem; letter-spacing: 0;
}
/* Wikidata one-line company description under the header */
p.co-descr { margin: 0.15rem 0 0.6rem; font-size: 0.95rem; max-width: 44rem; }
/* compact company name under a ticker in tables — truncated so it can't widen
   the column (keeps narrow/mobile layouts from overflowing) */
.co-name-sm {
  display: block; font-size: 0.78rem; font-weight: 400; color: var(--muted);
  max-width: 11rem; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; margin-top: 0.1rem; letter-spacing: 0;
}
.ticker-head h3 { font-family: var(--mono); font-size: 1rem; letter-spacing: 0; }
.ticker-head .spacer { flex: 1; }

/* ---- panels: one quiet surface, framed like a certificate --------------- */

.panel {
  background: var(--surface);
  border: 3px double var(--rule);
  padding: 0.9rem 1.1rem;
  margin: 0.8rem 0;
}
.panel > :first-child { margin-top: 0; }
.panel > :last-child { margin-bottom: 0; }
.panel ul.events li:last-child { border-bottom: 0; padding-bottom: 0.2rem; }

/* table rows that lead somewhere respond to the pointer */
tbody tr:has(a):hover td { background: var(--tint-accent); }

/* ---- pills: status as text, semantic color only ------------------------ */

.pill {
  font-family: var(--mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border: 1px solid var(--rule);
  padding: 0.12rem 0.45rem;
  white-space: nowrap;
  background: var(--surface);
}
.pill.up { border-color: var(--up); color: var(--up); background: var(--tint-up); }
.pill.down { border-color: var(--down); color: var(--down); background: var(--tint-down); }
/* Disclosed official-trade flag: not a value judgment (not up/down) — a
   notable-attention signal. Accent, not a semantic colour; bold + ⚑ so the
   "an official traded this" flag reads at a glance. */
.pill.flag {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--tint-accent);
  font-weight: 600;
}
/* committee-access overlap — the insider-access signal, stronger than .flag */
.pill.access {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  font-weight: 700;
}
.access-flag { color: var(--accent); font-weight: 600; }
/* Rows an official traded carry a quiet accent rail so the signal is prominent
   down the whole idea row, not just in its cell. The rail is an inset shadow on
   the left edge, so the desk's first column needs a left gutter (header + body,
   to keep the column aligned) or the ticker butts right against the rail. */
.desk-table th:first-child, .desk-table td:first-child { padding-left: 0.75rem; }
tr.has-officials > td:first-child { box-shadow: inset 0.18rem 0 0 var(--accent); }
/* the ⚑ officials pill wraps below the ticker in the desk name cell — give it
   vertical breathing room so it isn't cramped against the symbol */
tr.lead td:first-child .pill.flag { display: inline-block; margin-top: 0.3rem; }

/* ---- stance history: bare inline sparkbar, no chart library ------------- */

.spark-fig { margin: 0.8rem 0 0; }
.spark { display: flex; align-items: flex-end; gap: 2px; height: 2.2rem; }
.spark span { width: 5px; background: var(--rule); }
.spark span.up { background: var(--up); }
.spark span.down { background: var(--down); }

/* ---- backfill progress: horizontal fill bars, no chart library ---------- */

.bf-agg { display: flex; align-items: baseline; gap: 0.6rem 1.1rem;
  flex-wrap: wrap; margin: 0.6rem 0 1rem; }
.bf-agg .bf-headline { font-size: 1.5rem; font-variant-numeric: tabular-nums;
  color: var(--accent); font-weight: 600; }
/* one wide bar for the whole universe, mirrors the per-row track */
.bf-overall { height: 0.7rem; border: 1px solid var(--rule);
  background: var(--surface); border-radius: 2px; overflow: hidden;
  width: 100%; max-width: 44rem; margin: 0.2rem 0 0.4rem; }
.bf-overall > i { display: block; height: 100%; background: var(--accent);
  transition: width 0.5s ease; }

.bf-list { display: grid;
  grid-template-columns: minmax(4rem, max-content) 1fr minmax(3rem, max-content);
  align-items: center; gap: 0.3rem 0.7rem; max-width: 44rem;
  margin: 0.4rem 0; }
.bf-list .bf-sym { font-family: ui-monospace, Menlo, monospace;
  font-size: 0.85rem; }
.bf-list .bf-sym.off { color: var(--muted); }
.bf-track { height: 0.6rem; border: 1px solid var(--rule);
  background: var(--surface); border-radius: 2px; overflow: hidden; }
.bf-track > i { display: block; height: 100%; background: var(--accent);
  transition: width 0.5s ease; }
.bf-track.done > i { background: var(--up); }
.bf-track.err { border-color: var(--down); }
.bf-pct { font-variant-numeric: tabular-nums; font-size: 0.8rem;
  text-align: right; color: var(--muted); }
.bf-pct.done { color: var(--up); }

/* ---- admin: collapsible sections (native <details>, no JS) -------------- */
details.admin-section { border-top: 1px solid var(--rule); margin: 0.4rem 0; }
details.admin-section > summary,
details.admin-group > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center;
  gap: 0.5rem;
}
details.admin-section > summary::-webkit-details-marker,
details.admin-group > summary::-webkit-details-marker { display: none; }
details.admin-section > summary::before,
details.admin-group > summary::before {
  content: "\25B8"; color: var(--muted); transition: transform 0.15s ease;
  flex: none;
}
details.admin-section[open] > summary::before,
details.admin-group[open] > summary::before { transform: rotate(90deg); }
details.admin-section > summary {
  padding: 0.7rem 0; font-size: 1.2rem; font-weight: 600; color: var(--ink);
}
details.admin-group { margin: 0.45rem 0; }
details.admin-group > summary { padding: 0.3rem 0; font-size: 0.95rem; }
details.admin-group > summary .grp-title { font-weight: 600; }
/* the disclosure focus ring should track the row, not the glyph */
details > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- ticker desk (Plate 5) ---------------------------------------------- */

.thesis-strip {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.35rem 0.9rem;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding: 0.6rem 0;
  margin: 0.9rem 0;
}
.thesis-strip .grow { flex: 1; min-width: 12rem; }
.price-canvas { margin: calc(var(--space) * 1.2) 0; }
.price-canvas svg { display: block; }
.price-canvas figcaption { margin-top: 0.35rem; }
.price-canvas g.mark { cursor: help; }
.price-canvas g.mark:hover circle:not([fill="transparent"]),
.price-canvas g.mark:hover rect { stroke-width: 3; }

blockquote.quote {
  margin: 0.45rem 0 0.2rem;
  padding-left: 0.8rem;
  border-left: 2px solid var(--rule);
  color: var(--muted);
  font-style: italic;
  max-width: 34rem;
}

/* folded evidence: native details, quiet marker */
details.quote-fold { margin: 0.3rem 0 0.1rem; }
details.quote-fold summary { cursor: pointer; width: fit-content; }
details.quote-fold summary:hover { color: var(--accent); }

/* ---- intake parse-confirm: editable premise blocks ---------------------- */

fieldset.premise {
  border: 1px solid var(--rule);
  margin: 0 0 0.9rem;
  padding: 0.7rem 0.9rem;
}
fieldset.premise legend {
  font-family: var(--mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--muted);
  padding: 0 0.3rem;
}

/* suggested-ticker chips */
#suggestion-chips button {
  margin: 0 0.35rem 0.3rem 0;
  font-family: var(--mono);
  font-size: 0.72rem;
}
#suggestion-chips button[disabled] { opacity: 0.45; }

/* ticker typeahead (universe add form) */
.suggest-wrap { position: relative; display: block; }
ul.suggest {
  position: absolute;
  left: 0; right: 0;
  z-index: 20;
  margin: 2px 0 0;
  padding: 0;
  list-style: none;
  background: var(--paper);
  border: 1px solid var(--rule);
  max-height: 16rem;
  overflow-y: auto;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--ink) 12%, transparent);
}
ul.suggest li {
  padding: 0.45rem 0.65rem;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
ul.suggest li .num { font-family: var(--mono); }
ul.suggest li:hover, ul.suggest li.active { background: var(--tint-accent); }

/* sources add-forms */
form.add-form { margin: 0.9rem 0 0; }
form.add-form .status { margin-top: 0.4rem; }
/* Clear separation below the "Follow a voice" box before the directory table. */
#follow-voice { margin-bottom: 2.75rem; }

/* sources: a followed show expanded into its imported episodes */
tr.feed-episodes > td { padding-left: 0.9rem; }
/* Podcast show-detail panel: a real header (hosts/stats/description) +
   paginated episodes, opened in place of the old raw-RSS title link. */
button.linklike { background: none; border: none; padding: 0; margin: 0;
  color: var(--link, #94aacf); font: inherit; cursor: pointer;
  text-align: left; text-decoration: underline; }
/* The detail panel reads as PART OF the show above it: an accent bar down
   the left, an inset background, and (on mobile) NO top-border separator —
   the flat separator made it look like a separate show entirely. */
tr.show-detail > td {
  padding: 0.75rem 0.9rem 0.9rem;
  border-left: 3px solid var(--link, #94aacf);
  background: var(--inset, rgba(148,170,207,0.06));
}
tr.show-detail .show-detail-title { margin: 0 0 0.3rem; font-size: 1.02rem; }
tr.show-detail .show-detail-head { margin-bottom: 0.7rem; }
tr.show-detail .show-detail-head p { margin: 0.2rem 0; }
tr.show-detail ul.events { margin: 0; }
tr.show-detail ul.events li { padding: 0.45rem 0; }
tr.show-detail .actions { display: flex; align-items: center; gap: 0.75rem;
  margin-top: 0.6rem; }
/* the show whose detail is open loses its bottom rule so the two read as one
   block, and gains the same accent so the pair is unmistakably grouped. */
tr.has-open-detail > td { border-bottom: 0; }
@media (max-width: 40rem) {
  /* kill the card separator that made the panel look like its own show */
  .stack-sm tbody tr.show-detail { border-top: 0; padding-top: 0; }
  .stack-sm tbody tr.has-open-detail { border-bottom: 0; }
  .stack-sm tbody tr.show-detail > td:first-child {
    display: block; padding: 0.75rem 0.9rem 0.9rem;  /* keep the panel padding */
  }
}
tr.feed-episodes ul.events li { padding: 0.45rem 0; }

/* identifiers (killswitch names, worker names) read as code */
.mono { font-family: var(--mono); font-size: 0.85em; }
/* admin: inline reason input sits beside its confirm/cancel buttons */
td .filter-input { max-width: 14rem; margin: 0 0.4rem 0 0; }

/* ---- sources: sub-headings, episode preview + filter -------------------- */
h3.sub { margin: calc(var(--space) * 1.2) 0 0.15rem; font-size: 1rem; }
.preview-actions { display: flex; align-items: baseline; gap: 0.6rem;
  flex-wrap: wrap; margin: 0.6rem 0; }
input.filter-input {
  width: 100%; max-width: 24rem; margin: 0.4rem 0 0.6rem;
  padding: 0.35rem 0.5rem; font: inherit;
  border: 1px solid var(--rule); border-radius: 0;
  background: var(--surface); color: var(--ink);
}

/* ---- motion: only where it means something ------------------------------ */

@media (prefers-reduced-motion: no-preference) {
  button, a, summary { transition: opacity 120ms ease, border-color 120ms ease, color 120ms ease; }
}

select {
  display: block;
  margin-top: 0.35rem;
  padding: 0.55rem 0.65rem;
  font: inherit;
  color: inherit;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 0;
}

/* Plate 3 desk table: sparkline column fixed, loudest takes the room */
table.desk-table td.loudest { min-width: 14rem; max-width: 22rem; }
table.desk-table td svg { display: block; }
/* Top-align every cell: the spark <svg>'s baseline sits at its bottom edge, so
   under baseline alignment the tall signal cell starts low and its wrapped tail
   spills onto the row rule. Top alignment lets its padding-bottom clear the
   edge. Width floor keeps short buckets from wrapping to five lines. */
table.desk-table td { vertical-align: top; }
table.desk-table td.sig { min-width: 8rem; }

/* Fit six columns inside the 44rem reading column so the desk never has to
   clip/scroll sideways on desktop: the sparkline and per-cell padding are the
   width drivers, trimmed here. Still overflowed by ~26-70px with realistic
   content (a long "N filing statements" signal + a large "· N unseen"
   backlog both grew past the original budget, user-reported 2026-08-21) —
   trimmed further and let New's backlog clause drop to its own line instead
   of padding the column out with a nowrap run. */
table.desk-table th, table.desk-table td { padding-right: 0.35rem; }
table.desk-table th:last-child, table.desk-table td:last-child { padding-right: 0; }
/* long column headers wrap rather than forcing the table past the reading column */
table.desk-table th { white-space: normal; }
table.desk-table .spark-cell svg { width: 84px; height: auto; }
table.desk-table .spark-cell { gap: 0.3rem; }
table.desk-table .ret { min-width: 2.4rem; }
table.desk-table .newdelta { max-width: 6.5rem; }
table.desk-table .newdelta .backlog { display: block; }
/* any opted-in table: let headers wrap so columns can narrow to fit the
   reading column on desktop instead of clipping/scrolling sideways */
.stack-sm th { white-space: normal; }

/* Articles listing: the free-text title is the only wrapping column. Left to
   itself it squeezes to a sliver and a long headline wraps to eight lines while
   every numeric cell sits alone on line one — a tall band of dead space. Hand
   the title the room and clamp it to two lines with an ellipsis so row height
   stays bounded; the other cells then keep their own single line too. */
#articles-table td:first-child { width: 48%; }
#articles-table td:first-child a {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
#articles-table td { vertical-align: top; }

/* ---- narrow screens: desk tables reflow to stacked cards ----------------- */
/* Below the reading column each ticker becomes a card and every cell a
   labelled line (label from the cell's data-label), so nothing is clipped and
   there is no sideways scroll. Opt-in via .stack-sm on the .table-scroll. */
@media (max-width: 40rem) {
  .stack-sm { overflow-x: visible; }
  .stack-sm table { display: block; width: 100%; }
  .stack-sm thead { display: none; }         /* labels come from data-label */
  .stack-sm tbody { display: block; }
  .stack-sm tbody tr {
    display: block;
    padding: 0.7rem 0 0.35rem;
    border-top: 1px solid var(--rule);
  }
  .stack-sm tbody tr:first-child { border-top: 0; }
  .stack-sm tbody td {
    display: flex; align-items: baseline; flex-wrap: wrap;
    gap: 0 0.4rem; text-align: right;
    padding: 0.16rem 0; border-bottom: 0; white-space: normal;
  }
  /* label pinned left, value pieces flow together at the right as one unit */
  .stack-sm tbody td[data-label]::before {
    content: attr(data-label);
    flex: none; margin-right: auto; text-align: left;
    font-family: var(--mono); font-size: 0.64rem; text-transform: uppercase;
    letter-spacing: 0.08em; color: var(--muted);
  }
  /* first cell heads the card: full width, no label, left-aligned */
  .stack-sm tbody td:first-child {
    display: block; text-align: left;
    padding: 0 0 0.2rem 0.75rem;
    font-size: 1.02rem;
  }
  .stack-sm .co-name-sm { max-width: none; }
  /* the voice second tier stays prose beneath its lead, part of the same card */
  .stack-sm tbody tr.voice:not(.lead) { border-top: 0; padding-top: 0; }
  .stack-sm td.voice-line { display: block; text-align: left; padding: 0 0 0.4rem; }
  /* the spark cell keeps its own layout on the value side */
  .stack-sm .spark-cell { justify-content: flex-end; }
  /* actions (watch button / pump pill) align right without a label */
  .stack-sm td.actions { justify-content: flex-end; }
  /* the thesis stance strip wraps instead of running off the right edge
     (specificity beats the base .stance nowrap defined later in the sheet) */
  .thesis-head .stance, .deskbar .stance { white-space: normal; }
}

/* transcript: turns as prose, sentences are the seek handles */
.turn { margin: 1.1rem 0; }
.turn .label { margin: 0 0 0.15rem; }
.turn-text { max-width: 38rem; margin: 0; }
.sentence { cursor: pointer; border-radius: 2px; }
.sentence:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
/* deliberately NOT --accent: a cited sentence needs to read as marked-up,
   the way a highlighter pen does, not as another interactive-blue element —
   translucent so it still sits on both the light and dark paper correctly */
.sentence.highlight { background: color-mix(in srgb, #ffd400 42%, transparent); }
audio { margin: 0.6rem 0 0.2rem; }

/* thought boundaries within a speaker's turn: a quiet gap + hairline tick */
.turn.thought { margin-top: 0.7rem; padding-top: 0.55rem; position: relative; }
.turn.thought::before {
  content: "";
  position: absolute; top: 0; left: 0;
  width: 2.2rem; border-top: 1px solid var(--rule);
}

/* Sponsor reads: marked AND faded — the whole turn recedes so an ad never
   reads as the speaker's own view; hover restores it for anyone who wants
   to actually read the copy. */
.turn.turn-ad { opacity: 0.5; transition: opacity 0.15s ease; }
.turn.turn-ad:hover, .turn.turn-ad:focus-within { opacity: 1; }
.turn.turn-ad .turn-text { color: var(--muted); }
.ad-chip {
  margin-left: 0.5rem;
  padding: 0 0.3rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  vertical-align: 1px;
}
.speaker-link { color: inherit; }
.speaker-link:hover { color: var(--accent); }
.speaker-filter { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.6rem 0 0; }
/* the avatar is a block-level .agent-img — without flex it drops the name
   to its own line inside the button instead of sitting beside it. Even
   padding on all four sides: button.quiet's 0.25rem/0.7rem (tuned for text
   alone) framed the avatar tight top/bottom and loose left/right. */
.speaker-filter button {
  display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.5rem 0.6rem;
}
.speaker-filter button.active {
  border-color: var(--accent);
  color: var(--accent);
}

.thesis-strip.panel { border-top: 3px double var(--rule); border-bottom: 3px double var(--rule); }

/* account: sessions as a readable list, not a cramped 4-column table */
ul.session-list { list-style: none; margin: 0.6rem 0 0; padding: 0; max-width: none; }
ul.session-list li.session {
  display: flex;
  align-items: center;
  gap: 0.75rem 1.2rem;
  flex-wrap: wrap;
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--rule-soft);
}
ul.session-list li.session:last-child { border-bottom: 0; }
.session-main {
  flex: 1;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.session-device { font-weight: 600; }
.session-device .pill { margin-left: 0.35rem; vertical-align: 0.05em; }
ul.session-list button.quiet { white-space: nowrap; }

/* universe: inline weight editor — compact, quiet until focused */
input.weight-input {
  width: 4.2rem;
  margin: 0;
  padding: 0.25rem 0.4rem;
  font-family: var(--mono);
  font-size: 0.9em;
  text-align: right;
  border-color: var(--rule-soft);
  background: var(--surface);
}
input.weight-input:hover { border-color: var(--rule); }

/* public-agent images: engraved-plate square, quiet ring */
.agent-img { border-radius: 2px; background: var(--surface); flex: none; }
/* real images (headshots fill the box; transparent company logos need a
   light ground in dark theme too) */
img.agent-img { object-fit: cover; display: block; background: var(--logo-chip); }
.agent-img.sm { width: 36px; height: 36px; }
.agent-img.md { width: 64px; height: 64px; }
.agent-img.lg { width: 84px; height: 84px; }
/* no-headshot fallback: initials on the accent tint, same footprint as the img */
span.agent-img.avatar-ph {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--tint-accent); color: var(--accent); font-weight: 600;
  font-family: ui-monospace, Menlo, monospace; line-height: 1; letter-spacing: 0.02em;
}
span.agent-img.avatar-ph.sm { font-size: 14px; }
span.agent-img.avatar-ph.md { font-size: 24px; }
span.agent-img.avatar-ph.lg { font-size: 30px; }
.identity-head { display: flex; align-items: center; gap: 0.9rem; margin: 0 0 var(--space); }
.identity-head h1 { margin: 0; }
/* company logo on the ticker head — contain (never crop a logo), align to
   the h1 rather than the flex baseline */
img.ticker-logo {
  width: 56px; height: 56px; object-fit: contain; align-self: center;
  border-radius: 2px; background: var(--logo-chip); padding: 3px; display: block;
}
td.name-cell { display: flex; align-items: center; gap: 0.55rem; }
/* Directory only: title/tickers stack BELOW the name (not beside it, in a
   would-be-separate column) so the row reads as one identity block and costs
   nothing when a voice has no title — the common case. Top-aligns the avatar
   to the name rather than centring it against a block that is tall only when
   a title is present. */
/* Voice carries the avatar + a two-line name/title block now; the four
   numeric columns only ever hold a short count or a date, so fixed widths
   hand the room to the column that actually needs it instead of splitting it
   evenly. DESKTOP ONLY — .stack-sm turns cells into stacked blocks below
   40rem, where a percentage width would crush the card (mobile break,
   2026-07-29, same failure the followed-voices table hit). */
@media (min-width: 40.01rem) {
  #directory-table { table-layout: fixed; }
  #directory-table th:nth-child(1), #directory-table td:nth-child(1) { width: 40%; }
  #directory-table th:nth-child(n+2), #directory-table td:nth-child(n+2) { width: 14%; }
}
/* table-layout:fixed assigns a column's width from the header row and hands
   it to every cell in that column — EXCEPT when the cell's own `display` is
   `flex`: measured in Chrome, such a cell shrinks to its flex content's width
   instead (110px, not the assigned 40%). td.name-cell's flex row moves to an
   inner wrapper so the <td> itself stays a plain table-cell and keeps its
   assigned column width. */
#directory-table td.name-cell { display: table-cell; }
.directory-voice-row { display: flex; align-items: flex-start; gap: 0.55rem; min-width: 0; }

/* ---- followed voices: identity / kind / card cells --------------------- */
/* Fixed layout so the columns hold their share — under auto layout the
   nowrap KIND chips grabbed the width and crushed the name/bio to slivers.
   DESKTOP ONLY: below 40rem .stack-sm turns cells into stacked blocks, and
   these percentage widths crushed each block to a sliver of the card
   (mobile break, 2026-07-29). */
@media (min-width: 40.01rem) {
  #voices-table { table-layout: fixed; }
  /* sum < 100% on purpose: leaves slack so a nowrap chip or the action button
     can't nudge content past the cells and trip .table-scroll's overflow-x */
  #voices-table th:nth-child(1), #voices-table td:nth-child(1) { width: 44%; }
  #voices-table th:nth-child(2), #voices-table td:nth-child(2) { width: 32%; }
  #voices-table th:nth-child(3), #voices-table td:nth-child(3) { width: 10%; }
  #voices-table th:nth-child(4), #voices-table td:nth-child(4) { width: 10%; }
}
/* mobile: the kind/status cells keep their stacked-card flex; the voice lead
   cell heads the card with the avatar row intact */
@media (max-width: 40rem) {
  .stack-sm td.kind-cell { justify-content: flex-end; }
  .stack-sm .chips { justify-content: flex-end; }
  .stack-sm .kind-note { text-align: right; }
}

/* The voice cell stays a real table cell (so fixed layout hands it its 45%);
   the flex row lives on .voice-inner. The avatar aligns to the name at the top
   rather than floating centred against the tall block. */
td.voice-cell { vertical-align: top; }
.voice-inner { display: flex; align-items: flex-start; gap: 0.7rem; min-width: 0; }
.voice-id { display: flex; flex-direction: column; gap: 0.12rem; min-width: 0; }
.voice-id > a { font-weight: 600; font-size: 1.02rem; line-height: 1.25; }
/* a person's bio is prose — serif and readable, not the mono metadata font —
   clamped to two lines so every row keeps the same rhythm and the avatar sets
   the row height instead of an eight-line paragraph */
.voice-bio {
  font-family: var(--serif); font-size: 0.82rem; line-height: 1.4;
  color: var(--muted); margin-top: 0.05rem;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Kind cell: a person is one quiet word; an official stacks a labelled role
   over chips, so seats + universe-overlapping trades read at a glance. */
.kind-plain { color: var(--muted); }
/* tag + role share one line (they fit the widened column); role carries the weight */
.kind-role { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; }
.kind-role > span:last-child { font-weight: 600; }
.kind-tag {
  font-family: var(--mono); font-size: 0.58rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--accent);
  border: 1px solid var(--accent); padding: 0.03rem 0.3rem; white-space: nowrap;
}
.kind-note {
  display: block; font-size: 0.72rem; color: var(--muted);
  font-style: italic; margin-top: 0.15rem;
}
.chips { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.3rem; }

/* Identity-card status: a labelled state, not a lone glyph, so the column
   reads without a hover. */
.card-status {
  display: inline-flex; align-items: baseline; gap: 0.3rem;
  font-family: var(--mono); font-size: 0.72rem;
}
.card-status.ready { color: var(--ink); }
.card-status.ready .up { font-size: 0.85rem; }
.card-status.account, .card-status.pending { color: var(--muted); }

/* ---- desk: temporal anchor + view toggle -------------------------------- */
.deskbar {
  display: flex; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap; gap: 0.3rem 1rem; margin: 0 0 var(--space);
}
.deskbar .anchor { font-family: var(--mono); font-size: 0.75rem; color: var(--muted); }
.deskbar .anchor b { color: var(--ink); font-weight: 600; }

/* ---- desk band headings (briefing / book / ideas) ----------------------- */
.band-head {
  font-family: var(--mono); font-size: 0.7rem; text-transform: uppercase;
  letter-spacing: 0.11em; color: var(--muted);
  margin: var(--space-lg) 0 var(--space-sm);
  display: flex; align-items: baseline; gap: 0.6rem;
}
.band-head::after { content: ""; flex: 1; border-bottom: 1px solid var(--rule-soft); }

/* Group label inside the movers band (grouped by voice or by topic). Quieter
   than .band-head — it divides one band, it does not open a new one — and only
   rendered when a group actually holds more than one card. */
.gi-group {
  font-family: var(--mono); font-size: 0.66rem; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--muted);
  margin: var(--space-md) 0 0.15rem;
}
.gi-group:first-child { margin-top: 0; }

/* stance-shift card: the voice's avatar sits to the left of its headline,
   top-aligned the same way .voice-inner pairs an avatar with a name — the
   headline is a full sentence and can run to two lines. */
.gi-head-row { display: flex; align-items: flex-start; gap: 0.85rem; }
/* Box-top alignment alone still leaves a gap: the body's 1.55 line-height
   pads the first line with leading the avatar doesn't have, so the two
   "tops" read as offset even though the boxes are flush. Tightened line-height
   closes most of it; the remainder is the serif face's own cap-height inset
   (Georgia's caps start a few px below its em-box top) — no line-height value
   removes that, so the avatar gets the last nudge directly. */
.gi-head-row .gi-head { margin: 0; min-width: 0; line-height: 1.3; }
.gi-head-row .agent-img { margin-top: 0.2rem; }

/* ---- BAND 1: since you last looked (interrupts) ------------------------- */
ul.brief { list-style: none; margin: 0.2rem 0 0; padding: 0; max-width: none; }
ul.brief li {
  display: grid; grid-template-columns: 0.9rem 1fr auto; align-items: baseline;
  gap: 0.2rem 0.7rem; padding: 0.5rem 0; border-bottom: 1px solid var(--rule-soft);
  max-width: none;
}
ul.brief li:last-child { border-bottom: 0; }
.brief-dot { width: 7px; height: 7px; border-radius: 50%; align-self: center; }
.brief-dot.up { background: var(--up); }
.brief-dot.down { background: var(--down); }
.brief-dot.info { background: var(--muted); }
.brief-dot.bell { background: var(--accent); border-radius: 1px; transform: rotate(45deg); }
.brief-text { margin: 0; max-width: none; }
.brief-text .kind {
  font-family: var(--mono); font-size: 0.64rem; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--muted); margin-right: 0.45rem;
}
.brief-text .detail { color: var(--muted); }
.brief-at { font-family: var(--mono); font-size: 0.68rem; color: var(--muted); white-space: nowrap; }

/* grouped disclosure block: one person heads it, their trades listed beneath */
ul.brief li.brief-group { grid-template-columns: 0.9rem 1fr; }
.brief-text .actor { font-weight: 600; }
.brief-sub {
  grid-column: 1 / -1; list-style: none; margin: 0.25rem 0 0;
  padding: 0 0 0 0.1rem;
}
ul.brief .brief-sub li {
  display: flex; align-items: baseline; gap: 0.4rem;
  grid-template-columns: none; padding: 0.12rem 0; border-bottom: 0;
}
.brief-sub .detail { color: var(--muted); }
.brief-sub .brief-at { margin-left: auto; }

/* ---- BAND 2: thesis groups --------------------------------------------- */
/* Title and stance strip STACK rather than share one flex row: the strip is
   deliberately white-space:nowrap (a single scannable unit) and long enough
   ("63 support · 172 challenge · net -0.46 · was -0.46, 1d ago") that on
   anything but a wide screen it left the title too little room to keep even
   two words on a line, breaking one-word-per-line beside a cramped rename
   button. A full-width row each, title on top, is never ambiguous. */
.thesis-head {
  display: flex; flex-direction: column; align-items: flex-start; gap: 0.3rem;
  padding: 0.55rem 0 0.5rem; border-bottom: 1px solid var(--rule);
  margin-top: var(--space);
}
.thesis-head .t-title { font-size: 1.02rem; font-weight: 600; width: 100%; }
.thesis-head .t-title a { color: inherit; text-decoration: none; }
.thesis-head .t-title a:hover { text-decoration: underline; text-decoration-color: var(--muted); }
/* rename-in-place: the control is always present, just quiet. Hiding it until
   hover would hide the only clue that a name CAN be changed — and would hide it
   completely on touch. Dimmed and full-strength on hover or keyboard focus. */
.editable-name { display: inline-flex; align-items: center; gap: 0.5rem; }
.editable-name .rename-btn { opacity: 0.55; transition: opacity 0.12s ease; }
.editable-name:hover .rename-btn,
.editable-name .rename-btn:focus-visible { opacity: 1; }
/* thesis list only: push rename to the cell's right edge rather than
   hugging the name — with a wrapped two-line title the button otherwise
   floats mid-word instead of reading as acting on the row. */
#list-table td:first-child .editable-name {
  width: 100%; justify-content: space-between; align-items: flex-start;
}
/* Net stance and Support/Challenge are numbers — right-align them (and their
   headers, so the label sits over its column) the way every other numeric
   desk-table column on the site does. Tickers stays left: symbols, not a
   quantity. */
#list-table th:nth-child(3), #list-table td:nth-child(3),
#list-table th:nth-child(4), #list-table td:nth-child(4) {
  text-align: right;
}
/* Thesis carries a full title (+ rename button) and wrapped to two lines
   while the four short columns beside it sat in a sea of unused width —
   hand it the room. DESKTOP ONLY: .stack-sm turns cells into stacked blocks
   below 40rem, where a percentage width would crush the card. */
@media (min-width: 40.01rem) {
  #list-table { table-layout: fixed; }
  #list-table th:nth-child(1), #list-table td:nth-child(1) { width: 46%; }
  #list-table th:nth-child(2), #list-table td:nth-child(2) { width: 16%; }
  #list-table th:nth-child(3), #list-table td:nth-child(3) { width: 13%; }
  #list-table th:nth-child(4), #list-table td:nth-child(4) { width: 15%; }
  #list-table th:nth-child(5), #list-table td:nth-child(5) { width: 8%; }
  /* fixed layout stops auto-wrapping a header at its natural word breaks —
     "SUPPORT/CHALLENGE" ran past its column into STATUS instead of wrapping
     at the slash (user-reported 2026-08-21). Force the break explicitly. */
  #list-table th { white-space: normal; overflow-wrap: break-word; }
}
.rename-input {
  font: inherit; min-width: 12rem; max-width: 100%;
  padding: 0.15rem 0.35rem; border: 1px solid var(--rule); background: var(--bg);
  color: inherit;
}
.rename-error { font-size: 0.75rem; color: var(--down, #a33); }

.stance { font-family: var(--mono); font-size: 0.78rem; white-space: nowrap; }
.stance .net { font-weight: 600; }
.stance .trend { font-size: 0.72rem; color: var(--muted); }

/* magnitude-honest spark cell: baseline + printed % return */
.spark-cell { display: flex; align-items: center; gap: 0.5rem; }
.spark-cell svg { display: block; }
.ret { font-family: var(--mono); font-size: 0.78rem; font-variant-numeric: tabular-nums; min-width: 3.4rem; }

/* signal cell: scored voice vs filing-only, honestly distinguished */
.sig {
  font-family: var(--mono); font-size: 0.72rem;
  /* the buckets wrap to several lines in a narrow column — give them room to
     breathe and clear the row's bottom edge */
  line-height: 1.5; padding-bottom: 0.6rem;
}
.sig.voice { color: var(--accent); }
.sig.filings { color: var(--muted); }
.sig .badge {
  font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.06em;
  border: 1px solid var(--rule); padding: 0.05rem 0.35rem; margin-right: 0.4rem;
}
.sig.voice .badge { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.newdelta { font-family: var(--mono); font-size: 0.75rem; font-variant-numeric: tabular-nums; }
.newdelta b { color: var(--ink); }

/* the voice statement, second tier — prose, only when a scored voice exists */
table.desk-table tr.voice td { border-bottom: 0; padding-bottom: 0.15rem; }
td.voice-line {
  font-family: var(--serif); font-size: 0.94rem; color: var(--ink);
  padding: 0 0 0.6rem 0; max-width: none;
}
td.voice-line .vmeta {
  font-family: var(--mono); font-size: 0.68rem; color: var(--muted);
  display: block; margin-top: 0.15rem;
}
.role-tag {
  font-family: var(--mono); font-size: 0.6rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--muted); margin-left: 0.4rem;
}

/* ---- desk: thesis core-idea preview (stored premises, inline) ------------ */
.premises {
  margin: 0.15rem 0 0.4rem;
  padding-left: 1.4rem;
  color: var(--muted);
  font-size: 0.86rem;
  line-height: 1.45;
}
.premises li.challenged { color: var(--down); }
.premises li.supported .premise-status { color: var(--up); }
.premises .premise-status { font-variant: small-caps; letter-spacing: 0.03em; }

/* ---- tablekit: sortable headers + pager ---------------------------------- */
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { text-decoration: underline; text-decoration-color: var(--muted); }
.pager { margin: 0.3rem 0 0; font-size: 0.85rem; }
.pager button.quiet { padding: 0 0.4rem; }

/* ---- desk: stance-shift drill-down --------------------------------------- */
button.trend {
  background: none; border: none; font: inherit; color: var(--muted);
  cursor: pointer; padding: 0;
}
button.trend:hover { text-decoration: underline; }
.stance-delta { margin: 0.2rem 0 0.4rem; padding-left: 1.4rem; font-size: 0.86rem; }
.stance-delta .kind { font-variant: small-caps; margin-right: 0.4rem; }
.stance-delta li.up .kind { color: var(--up); }
.stance-delta li.down .kind { color: var(--down); }
.stance-delta-head { margin: 0.25rem 0 0; }

/* ---- desk: graph-insight leads ------------------------------------------- */
/* hairline-separated rows: a 5-card list reads as scannable items, not one
   undifferentiated stream (matches ul.events) */
.graph-insights { list-style: none; padding: 0; margin: var(--space-xs) 0; }
.graph-insights li { padding: var(--space-sm) 0; border-bottom: 1px solid var(--rule-soft); }
.graph-insights li:last-child { border-bottom: 0; }
/* Stance-shift cards only: a long list of hairline-separated cards with no
   fill between them reads as one undifferentiated column. Alternation is
   assigned in JS (each person's shifts sit in their OWN <ul>, so a plain
   nth-child would restart at every group boundary instead of running down
   the page). Horizontal padding + matching negative margin keeps the tint's
   edge off the text without shifting untinted rows out of alignment with it. */
#stance-shifts .graph-insights li {
  padding-left: 0.75rem; padding-right: 0.75rem;
  margin-left: -0.75rem; margin-right: -0.75rem;
}
#stance-shifts .graph-insights li.row-alt { background: var(--surface); }
.graph-insights .gi-head { margin: 0; }
.graph-insights .gi-why { margin: var(--space-xs) 0 0; font-size: 0.9rem; }
.graph-insights .gi-span { margin: var(--space-xs) 0 0; font-size: 0.82rem; }
/* download-the-source glyph on an evidence line: de-emphasized, no underline
   under a bare symbol, quieter than the transcript link it sits beside since
   reading the citation in context is the primary path. */
.source-dl { text-decoration: none; opacity: 0.65; }
.source-dl:hover { opacity: 1; }

/* ---- ticker: OHLC chart host --------------------------------------------- */
.price-chart { width: 100%; min-height: 260px; }

/* ---- ticker: topic-group headings ---------------------------------------- */
.topic-head { font-size: 0.92rem; margin: 0.7rem 0 0.15rem; font-variant: small-caps; letter-spacing: 0.03em; }

/* ---- read-throughs: reached-by-graph statements, grouped by topic -------- */
/* Each topic gets a top rule + more air so the groups read as distinct bands
   rather than one continuous scroll. The label carries its count as a chip. */
.readthroughs .topic-head {
  margin: calc(var(--space) * 1.1) 0 0.35rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--rule);
}
.readthroughs .topic-head:first-child { margin-top: 0.4rem; }
.readthroughs .topic-head .denom { font-variant: normal; letter-spacing: 0; }

/* The graph chain that reaches this name — the defining fact of a read-through,
   lifted onto its own quiet line instead of buried in the meta run-on. */
.readthrough-chain {
  display: block; margin-top: 0.3rem;
  font-family: var(--mono); font-size: 0.72rem; line-height: 1.5; color: var(--muted);
}
.readthrough-chain .chain-arrow { color: var(--accent); }
.readthrough-chain .chain-path { color: var(--accent); }

/* table chrome: filter is part of the table, never a peer of add-forms */
.table-toolbar {
  display: flex; align-items: baseline; justify-content: flex-end;
  gap: 0.6rem; margin: 0.4rem 0 0.2rem;
}
/* One chrome line above a table: a view switch on the left, the rows count +
   filter on the right (tablekit's toolbarSlot). Both sit on the same line
   instead of the switch floating a row above the filter box. */
.table-head {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 0.4rem 0.8rem; margin: 0.4rem 0 0.2rem;
}
.table-head > * { margin: 0; }
.table-head .table-toolbar { margin: 0; }
/* A short list gets no toolbar, so the slot is empty — don't reserve its line. */
.table-head:empty { display: none; }

/* Stance sparkline: a word-sized line of position-over-time sitting on the same
   baseline as the label that follows it. Data-ink only — one faint reference
   rule at neutral, the line, and the current point. */
.stance-line { display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; }
.stance-spark { width: 108px; height: 20px; flex: none; overflow: visible; }
.spark-base { stroke: var(--rule-soft); stroke-width: 0.5; }
.spark-line { stroke: var(--ink); stroke-width: 1; fill: none; stroke-linejoin: round; }
.spark-now { fill: var(--accent); stroke: none; }
.table-toolbar .table-filter {
  width: 22rem; max-width: 70%;
  font-size: 0.85em; padding: 0.15rem 0.4rem;
  border: 1px solid var(--rule); border-radius: 2px;
  background: transparent; color: inherit;
}
.table-toolbar .table-filter:focus { outline: 1px solid var(--accent); }

/* voice rows whose identity card is still assembling: visibly in-progress */
tr.assembling td { background: color-mix(in srgb, var(--accent) 7%, transparent); }
tr.assembling td:first-child { border-left: 2px solid var(--accent); }

/* --- Article reader: prose + inline extraction marks + margin notes ------- */
/* Color stays semantic (up/down): entities are a QUIET dotted underline with
   the type on hover, never a standing color (Tufte rule kept). Prediction /
   knowledge quotes are interactive, so they carry the --accent interactive
   tint, consistent with every other clickable surface. */

.article-reader {
  /* Notes live in a real LEFT-hand margin that protrudes past the article. The
     research rail took the right margin, so the mirror of the original trick
     keeps the property that mattered: pulling the whole grid left by exactly the
     margin width leaves the PROSE where it was, still aligned with the
     header/title, and pushes the notes into the left page margin instead.
     A plain column swap would have shifted the body 18.5rem right while the h1
     stayed put. Placement is by grid-column, not DOM order: article.html emits
     <article> before <aside>, which is correct for reading and tab order. */
  display: grid;
  grid-template-columns: 16rem 40rem;
  column-gap: 2.5rem;
  margin-left: -18.5rem; /* 16rem track + 2.5rem gap */
  align-items: start;
  margin-top: 1.2rem;
}
/* grid-row: 1 on BOTH is load-bearing: with <article> first in the DOM and
   only columns pinned, auto-placement would put the aside in ROW 2 — the
   notes rendering below the article instead of beside it. */
.article-reader .reader-margin { grid-column: 1; grid-row: 1; }
.article-reader .article-body { grid-column: 2; grid-row: 1; }
.article-body { max-width: 40rem; }
.article-body .para { line-height: 1.65; margin: 0 0 1rem; }
.reader-hint { margin-bottom: 0.4rem; }

/* entity — quiet dotted underline, type revealed on hover/focus as a pill */
.ent {
  border-bottom: 1px dotted var(--muted);
  cursor: help;
  position: relative;
  transition: background 0.6s ease;
}
.ent:hover::after,
.ent:focus::after {
  content: attr(data-type);
  position: absolute; left: 0; bottom: 100%; margin-bottom: 3px;
  font: 0.62rem/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.04em; text-transform: uppercase;
  background: var(--ink); color: var(--paper);
  padding: 1px 5px; border-radius: 3px;
  white-space: nowrap; z-index: 5; pointer-events: none;
}

/* prediction / knowledge quote — interactive tint + a bottom rule to tell the
   two kinds apart without a second hue */
.q {
  background: var(--tint-accent);
  border-radius: 2px; padding: 0 1px; cursor: pointer;
  transition: background 0.6s ease;
}
.q:hover, .q:focus { background: color-mix(in srgb, var(--accent) 18%, transparent); }
.q-prediction { box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--accent) 55%, transparent); }
.q-knowledge  { box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--muted) 45%, transparent); }

/* margin notes */
.reader-margin { font-size: 0.85rem; display: flex; flex-direction: column; gap: 0.9rem; }
.note {
  border-left: 2px solid var(--rule);
  padding: 0.1rem 0 0.1rem 0.7rem;
  transition: background 0.6s ease, border-color 0.2s ease;
}
.note p { margin: 0.15rem 0; }
.note .kind { margin-bottom: 0.1rem; }
/* Border weight reads as the same value order the tiers already sort by, so
   the fold split (actionable/prediction always open, context behind a
   summary) looks like what it is — value-ordered — instead of arbitrary
   (user-reported 2026-08-23: "inconsistently folded, no clear pattern").
   Strongest ink for the always-open top tier, unchanged accent for
   prediction, and the existing faint default for context is now doing
   double duty as the "this one folds, it matters least" signal. */
.note-actionable { border-left-width: 3px; border-left-color: var(--accent); }
.note-prediction { border-left-color: color-mix(in srgb, var(--accent) 55%, var(--rule)); }
.note.linked { cursor: pointer; }
.note.linked:hover { border-left-color: var(--accent); background: var(--tint-accent); }
/* The context note's fold is the one behavior difference a border color
   alone can't say — spell it out with the same disclosure marker the admin
   panel uses, rather than leaving "click to expand" undiscoverable. */
.note-inline.note-context > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: 0.4rem;
}
.note-inline.note-context > summary::-webkit-details-marker { display: none; }
.note-inline.note-context > summary::before {
  content: "\25B8"; color: var(--muted); transition: transform 0.15s ease; flex: none;
}
.note-inline.note-context[open] > summary::before { transform: rotate(90deg); }

/* transient jump highlight (inline anchor <-> margin note) */
.flash { background: color-mix(in srgb, var(--accent) 22%, transparent); border-radius: 3px; }

/* Not enough width to hold the protruding right margin without the notes
   running off the (never-horizontally-scrolling) page: stack them under the
   article and let both use the full body measure. */
/* ≤79rem: annotations RETURN TO THEIR ANCHORS instead of restacking as a
   flat pile after the content — a margin note's entire meaning is its
   sentence, and the restack divorced every note from it (Tufte review
   2026-08-23). The margin column disappears; the inline copies (.note-inline,
   rendered beside their anchor turn) carry the same ink. Exactly one
   representation is visible at any width. */
.note-inline { display: none; }
@media (max-width: 79rem) {
  .article-reader { grid-template-columns: minmax(0, 1fr); gap: 1.4rem; margin-left: 0; }
  .article-body { max-width: 100%; }
  .article-reader .reader-margin { display: none; }
  .note-inline {
    display: block;
    margin: 0.4rem 0 0.9rem 1rem;
    font-size: 0.85rem;              /* reader-margin scale */
    max-width: 34rem;
  }
  .note-inline summary { cursor: pointer; }
}
/* Both margins are spoken for once the rail is open. At 44rem centred content
   each side margin is (viewport-44rem)/2: 23rem at 90rem. Notes want 18.5rem and
   the rail ~20rem, so between 80 and 100rem one of them would be clipped by
   `overflow-x: hidden` — silently, which is the worst outcome. Restack the notes
   instead. This is a visible reflow on open; it is the honest trade. */
@media (max-width: 100rem) {
  body.sidebar-open .article-reader {
    grid-template-columns: minmax(0, 1fr);
    margin-left: 0;
    gap: 1.4rem;
  }
  body.sidebar-open .article-body { max-width: 100%; }
  body.sidebar-open .reader-margin {
    border-top: 1px solid var(--rule-soft); padding-top: 1rem;
  }
}
.note-quote {
  font-style: italic; color: var(--muted); font-size: 0.95em;
  border-left: 1px solid var(--rule-soft); padding-left: 0.5rem; margin-top: 0.3rem;
}

/* --- Research rail ------------------------------------------------------- */
/* An ANNOTATION COLUMN, the mirror of the reader's margin notes: it sits in
   the right page margin beside the 44rem body — anchored to the CONTENT
   (left: 50% + half the body + gap), never to the viewport corner — with the
   notes' own idiom: quiet small type, left-ruled entries, no box chrome.
   position: fixed keeps it in view while the page scrolls under it (the log
   scrolls internally). It NEVER borrows .num/.denom: streamed prose cannot
   honour "no number renders without its denominator", and a rail that mimics
   page typography only sometimes teaches the reader two rules. */
.rail {
  position: fixed;
  top: 4.75rem;                        /* below the nav's natural height */
  left: calc(50% + 22rem + 2.5rem);    /* body half-width + reader gap */
  width: 22.5rem;
  max-height: calc(100vh - 6rem);
  display: flex;
  flex-direction: column;
  font-size: 0.85rem;                  /* reader-margin scale */
  z-index: 20;
}
.rail-head {
  /* flex-start, NOT baseline: the head holds a bare text label and a PADDED
     input, and baseline alignment drags the label down by the input's
     padding+border so their text baselines meet. That moved RESEARCH out of
     line with the nav's identity row (which alignRail measures against), and
     it moved again whenever the filter showed/hid per page. flex-start keeps
     the label's box top fixed regardless of what sits beside it. */
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 0.5rem; padding: 0 0 0.3rem;
  border-bottom: 1px solid var(--rule-soft);
  font-size: 0.72rem;   /* the nav's size, so the aligned rows are consonant */
}
.rail-toggle { font-size: 0.72rem; padding: 0.05rem 0.35rem; white-space: nowrap; }
.rail-head-actions { display: inline-flex; gap: 0.55rem; align-items: baseline; }
/* A real switch: hairline track, sliding knob, accent when on — the platform
   toggle idiom rendered in the site's quiet language. Side labels (summary ⇄
   full) let the knob visibly point at the active option; the active label is
   inked, the inactive muted. */
.switch {
  /* hardened against the global button styling (padding/min-height/font)
     which inflated the track into an egg */
  all: unset;
  box-sizing: border-box;
  position: relative; flex-shrink: 0;
  display: inline-block;
  width: 1.9rem; height: 1.05rem;
  border: 1px solid var(--rule); border-radius: 999px;
  background: var(--paper); cursor: pointer;
  vertical-align: middle;
}
.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.switch::after {
  content: ""; position: absolute; top: 1px; left: 2px;
  width: calc(1.05rem - 6px); height: calc(1.05rem - 6px);
  border-radius: 50%; background: var(--muted);
  transition: left 0.15s ease, background 0.15s ease;
}
.switch[aria-checked="true"] { border-color: var(--accent); }
.switch[aria-checked="true"]::after {
  left: calc(100% - 1.05rem + 4px);
  background: var(--accent);
}
.switch-side { color: var(--muted); }
.switch-side.on { color: var(--ink); }
.switch-wrap { display: inline-flex; gap: 0.4rem; align-items: center; }
.rail-body { display: none; flex-direction: column; min-height: 0; }
body.sidebar-open .rail-body { display: flex; }
.rail-log {
  overflow-y: auto; padding: 0.6rem 0; display: flex; flex-direction: column;
  gap: 0.8rem;
}
/* entries carry the margin-note rule, not bubbles */
.rail-msg { border-left: 2px solid var(--rule); padding-left: 0.6rem; }
.rail-user { border-left-color: var(--accent); }
.rail-p { margin: 0 0 0.45rem; line-height: 1.5; }
.rail-p:last-child { margin-bottom: 0; }
.rail-list { margin: 0 0 0.45rem; padding-left: 1.05rem; }
.rail-list li { margin: 0.15rem 0; }
.rail-sources {
  margin: 0.45rem 0 0; padding-left: 1.3rem;
  font-size: 0.78rem; color: var(--muted);
}
.rail-sources li { margin: 0.1rem 0; }
.rail-byline { margin: 0.35rem 0 0; opacity: 0.75; }
/* live reasoning: a small serif-italic block with real line height — one
   status line was too cramped and per-delta repaints scrolled past reading
   speed. Never confusable with the answer: muted, ruled, outside the log. */
.rail-thinkbox {
  font-family: var(--serif);
  font-style: italic;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.45;
  border-left: 2px solid var(--rule-soft);
  padding: 0.15rem 0 0.15rem 0.6rem;
  margin: 0.2rem 0;
  /* EXACTLY three lines (3 x 1.45 line-height) — a fractional cap sliced the
     third line in half. Bottom-anchored so overflow clips the OLD words at
     the top, never the newest at the bottom. */
  height: calc(3 * 1.45em);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  flex-shrink: 0;
}
.rail-note { margin: 0.2rem 0; text-align: center; opacity: 0.7; }
/* flex-shrink: 0 is load-bearing on both: under the rail's max-height the
   default shrink let these collapse and the form drew OVER wrapped status
   text. Only the log is allowed to give way (it scrolls). */
.rail-status { margin: 0; padding: 0.15rem 0; min-height: 1rem; flex-shrink: 0; }
.rail-form { padding: 0.3rem 0 0; display: flex; flex-direction: column; gap: 0.4rem; flex-shrink: 0; }
.rail-input {
  width: 100%; font: inherit; font-family: var(--serif); font-size: 0.85rem;
  border: 1px solid var(--rule); background: var(--paper); color: var(--ink);
  padding: 0.35rem; resize: vertical;
}
.rail-actions { display: flex; gap: 0.4rem; align-items: center; }
.rail-email-group { margin-left: auto; display: inline-flex; gap: 0.5rem; align-items: center; }

/* The margin can hold the column only when the viewport fits body + both
   margins: 2 x (22rem half-body + 2.5rem gap + 22.5rem column) = 94rem. Below
   that it becomes a bottom sheet rather than overlapping the content it is
   meant to annotate. */
@media (max-width: 94rem) {
  .rail {
    top: auto; bottom: 0; right: 0; left: 0; width: auto;
    max-height: 60vh; padding: 0.15rem 0.75rem 0.5rem;
    background: var(--surface); border-top: 1px solid var(--rule);
    /* the sheet must READ as a sheet: a lifted edge + grabber pill. The bare
       one-line "RESEARCH" bar was invisible in practice — reported as "the
       sidecar is nowhere" on a phone podcast page (2026-08-23), while DOM
       inspection showed it present and correctly placed. Discoverability is
       the failure, so the closed state gets the universal sheet affordance
       rather than a floating button over content. */
    box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.18);
  }
  .rail::before {
    content: "";
    display: block;
    width: 2.5rem; height: 0.28rem;
    margin: 0.25rem auto 0.15rem;
    border-radius: 999px;
    background: var(--muted, #888);
    opacity: 0.55;
  }
  /* the whole head is the handle on a sheet, not just the label */
  .rail-head { cursor: pointer; }
  .rail-log { max-height: 40vh; }
}

/* voice calls — banded, glyph-spined (voices.html). The record must read
   vertically as a column of marks; everything non-spine lives in the fold. */
.calls .call-row { list-style: none; }
.call-spine { display: flex; align-items: baseline; gap: .5rem; }
.call-glyph { flex: 0 0 1.1rem; text-align: center; font-weight: 600; }
.call-statement { flex: 1 1 auto; }
.call-when { flex: 0 0 auto; white-space: nowrap; }
.call-track { position: relative; height: 3px; margin: .15rem 0 .1rem 1.6rem;
  background: color-mix(in srgb, currentColor 8%, transparent); border-radius: 2px; }
.call-seg { position: absolute; top: 0; height: 3px; border-radius: 2px;
  background: currentColor; opacity: .45; min-width: 2px; }
.call-seg.up { opacity: .8; } .call-seg.down { opacity: .8; }
.call-tick { position: absolute; top: -2px; width: 1px; height: 7px; background: currentColor; opacity: .5; }
.channel-chips { display: flex; flex-wrap: wrap; gap: .35rem .9rem; margin: .3rem 0 0; }
.channel-chip { white-space: nowrap; }
/* A company logo is a mark, not a face: never crop it. cover-cropping squared
   wide logos into fragments ("VER"). contain + a quiet plate instead. */
.org-logo img { object-fit: contain !important; background: #fff; padding: 4px; }

/* statements topic map — data-carrying rows with shared-axis sparklines.
   Grid, not flex: topic labels align in one column (no two-line wrap chaos),
   claims ellipsize in the middle, sparklines right-align under one axis. */
.topic-map .call-fold.muted > summary { opacity: 0.55; } /* thesis layer: half-tone, never hidden */
.topic-map .topic-spine { display: grid; align-items: center; gap: 0 0.7em;
  grid-template-columns: minmax(9rem, 13rem) 1fr 6.5rem; padding: 0.22rem 0; }
.topic-map .topic-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topic-map .topic-claim { min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.topic-map svg.spark-tm { justify-self: end; color: var(--ink-muted, #888); }
.topic-map h4 { margin: 1.3rem 0 0.3rem; padding-top: 0.5rem;
  border-top: 1px solid var(--rule); font-variant: small-caps;
  letter-spacing: 0.06em; }
.topic-map h4:first-child { border-top: none; margin-top: 0.4rem; }
.topic-map .call-fold { margin-left: 0.9rem; } /* topics sit inside their family */
.spark-legend { text-align: right; margin: 0 0 0.2rem; }
@media (max-width: 40rem) { .topic-map .topic-spine { grid-template-columns: minmax(7rem, 1fr) 1fr; }
  .topic-map svg.spark-tm { display: none; } }

/* statements filter — grep, not chat */
input.topic-filter { width: 100%; max-width: 26rem; margin: 0.3rem 0 0.6rem;
  padding: 0.35rem 0.6rem; background: transparent; border: 1px solid var(--rule);
  color: inherit; font: inherit; }

/* focus layer — matched rows keep full ink, the rest stay VISIBLE but recede
   (a filter that removes rows hides what you are not looking at) */
.out-of-focus { opacity: 0.28; }
.focus-banner { display: flex; gap: 0.7em; align-items: baseline; margin: 0.2rem 0 0.6rem; }

/* steering controls in the rail: filter -> scope -> question, one column */
input.rail-filter { width: 100%; margin: 0 0 0.4rem; padding: 0.3rem 0.5rem;
  background: transparent; border: 1px solid var(--rule); color: inherit;
  font: inherit; font-size: 0.9em; }
.rail-prefs { display: flex; justify-content: flex-end; margin: 0 0 0.3rem; }

/* rail header: the title IS the panel control; the filter sits beside it and
   stays usable while the chat is collapsed */
button.rail-title { background: none; border: 0; padding: 0; cursor: pointer;
  /* the nav's exact type: alignRail pins this label to the nav's email span,
     and only equal font metrics make top-alignment equal baseline-alignment
     (inherited serif at 0.75rem sat visibly below the identity row) */
  font: inherit; font-family: var(--mono); font-size: 0.72rem;
  letter-spacing: 0.09em; color: inherit; opacity: 0.7; }
button.rail-title:hover { opacity: 1; }
/* the visible min/max cue on the panel's one control — same rotating
   disclosure marker the admin panel and context notes use, so "this
   toggles" reads at a glance instead of only "RESEARCH" as inert text. */
.rail-chevron { display: inline-block; margin-left: 0.3rem; transition: transform 0.15s ease; }
button.rail-title[aria-expanded="true"] .rail-chevron { transform: rotate(90deg); }
.rail-head .rail-head-actions { flex: 1 1 auto; display: flex; justify-content: flex-end; }
.rail-head input.rail-filter { margin: 0; max-width: 14rem; }

/* a table row that acts as a filter: it has to LOOK actionable before it is
   clicked, or the affordance exists only for whoever was told about it */
tr.row-filter { cursor: pointer; }
tr.row-filter:hover td { background: var(--rule); }
tr.row-filter:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }

/* the identity block is portrait + a text column, so what a voice IS can sit
   under its name in the space beside the portrait instead of below the fold */
.identity-head-text { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
.identity-head-title { display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap; }
.identity-head-text > p { margin: 0; }


/* ---- connection graph (Signals, left column) ---------------------------- */
/* One palette, defined once. Light and dark both go through these so a kind
   keeps its identity across themes. */
:root {
  --k-company: #4a6fa5;
  --k-security: #3f7d6a;
  --k-person: #8a6d3b;
  --k-filing: #7a6a8a;
  --k-prediction: #a2705a;
  --k-other: var(--rule);
}
/* Reuses .article-reader's grid so the picture sits in the same left margin the
   article and transcript readers use. Only the vertical rhythm differs: cards
   are a list, not prose. */
.graph-reader { margin-top: var(--space-sm); }
/* No graph -> no column. Collapsing to a single track (and dropping the
   negative pull) puts the cards back where they sit everywhere else, rather
   than leaving them indented past an empty margin. */
.graph-reader.no-graph {
  display: block;
  margin-left: 0;
}
.graph-reader.no-graph .reader-margin { display: none; }
/* Offset to the selected card by script; sticky so it follows once reached,
   rather than scrolling away from a card further down the list. */
.graph-reader .reader-margin { position: sticky; top: 1rem; transition: margin-top .18s ease; }
/* Narrow screens: the graph mounts under its selected card in a FIXED-height
   box — opening it must never reflow the list under the reader's finger
   (Tufte review 2026-08-23). Only ever shown ≤79rem (script-gated).
   The box is a flex column with a HARD overflow clip: .cgraph is an SVG
   sized width:100%/height:auto, which in a full-width phone container grows
   past any fixed box and spilled the picture + legend over the next card's
   text (user screenshot 2026-08-23). The SVG now shrinks to the space the
   title/legend/hint leave (viewBox letterboxes it), and nothing escapes. */
.insight-graph-inline {
  height: 20rem;
  margin: 0.4rem 0 0.9rem;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.insight-graph-inline .cgraph { flex: 1 1 0; min-height: 0; height: auto; }

/* A voice's profile puts the network beside the portrait, so the picture is the
   first thing read WITH the name rather than a footnote below the statements.
   Its own grid rather than .article-reader's negative pull: that trick exists to
   keep an article's prose column aligned with the page header, and there is no
   prose column here to hold still. Collapses to one track when the world model
   has nothing to draw, so the profile is never indented past an empty margin. */
/* The picture hangs in the LEFT PAGE MARGIN and is OUT OF FLOW.
   A grid put it beside the identity but a grid row is as tall as its tallest
   cell, so a 15rem-wide picture 20rem tall pushed everything after it — the
   channel list, the track record, the whole page — down by its own height,
   leaving a band of dead space beside the bio. Absolute positioning is what
   makes a margin note behave like a margin note: it sits next to the content
   without occupying any of the content's vertical space. */
.voice-head-grid { position: relative; }
.voice-head-grid .voice-graph {
  position: absolute;
  top: 0;
  /* Matched to the RIGHT-hand research rail: same 22.5rem width and same
     2.5rem gutter, so the page has two margins of equal weight instead of a
     wide one on the right and a cramped one on the left. 15rem squeezed a
     thirty-node network into a column narrower than the portrait beside it. */
  left: -25rem; /* 22.5rem track + 2.5rem gap */
  width: 22.5rem;
}
/* No picture -> no margin track, and the pull comes off with it, or the
   identity would hang off the left edge of the page. */
.voice-head-grid.no-graph .voice-graph { display: none; }
/* One column on a narrow screen, picture UNDER the identity: the name and face
   are what the page is about, and a 15rem graph above them would push them off
   a phone's first screen. */
/* Same 94rem threshold the right-hand rail uses, and for the same arithmetic:
   the margin can hold a column only when the viewport fits the body plus BOTH
   margins. At the old 60rem the picture hung off the left edge of every
   viewport between 60 and 94rem — invisible, and taking its clicks with it.
   Below the threshold it rejoins the flow UNDER the identity, because the name
   and face are what the page is about. */
@media (max-width: 94rem) {
  .voice-head-grid .voice-graph {
    position: static;
    width: auto;
    max-width: 22.5rem;
    margin-top: var(--space-sm);
  }
}

/* The universe picture is the shape of the WHOLE set. It sits in the same left
   page margin as the other two, so every graph on the site is in the same place
   and the table below keeps its full width and its alignment with the heading. */
/* Same margin, same out-of-flow reason: the table below must keep its position
   and its full width rather than being pushed down by the picture's height. The
   wrapper is a zero-height anchor, so it occupies nothing in the flow. */
.universe-graph-wrap { position: relative; height: 0; }
.universe-graph-wrap[hidden] { display: none; }
.universe-graph {
  position: absolute;
  top: 0;
  left: -25rem; /* matches .rail's 22.5rem + 2.5rem, see .voice-graph */
  width: 22.5rem;
}
@media (max-width: 94rem) {
  .universe-graph-wrap { height: auto; }
  .universe-graph {
    position: static;
    width: auto;
    max-width: 22.5rem;
    margin-bottom: var(--space-sm);
  }
}

.cgraph { width: 100%; height: auto; display: block; }
.cgraph-edge { stroke: var(--rule-soft); stroke-width: 1; }
/* Kind carries the colour. The world model types every node (Company, Security,
   Agent, Person, Filing, Prediction...), and drawing them all one grey threw
   that away — whether a bridge runs through people or through filings is most
   of what the picture is for. Muted on purpose: the ROLE (hinge/endpoint) still
   has to read first, so kinds are tints rather than a bright categorical set. */
.cgraph-node circle, .cgraph-node rect {
  fill: var(--k-other); stroke: var(--bg); stroke-width: 1.5; cursor: pointer;
}
.cgraph-node.k-company circle, .cgraph-node.k-company rect { fill: var(--k-company); }
/* GROUP colours (sector on the universe graph), assigned by a stable hash in
   cgraph.js rather than by name. A hand-kept name->colour map would need editing
   every time the SIC mapping produced a new tag, and until someone did, the new
   sector would fall to a default grey — which reads as a CATEGORY rather than as
   a gap. Ten slots, muted like the kind palette so the picture stays quiet, and
   distinguishable in order rather than by hue alone. */
.cgraph-node.g-0 circle { fill: #8fabd8; }
.cgraph-node.g-1 circle { fill: #79bda6; }
.cgraph-node.g-2 circle { fill: #c6a570; }
.cgraph-node.g-3 circle { fill: #b3a3c4; }
.cgraph-node.g-4 circle { fill: #d8a48c; }
.cgraph-node.g-5 circle { fill: #7fa8b8; }
.cgraph-node.g-6 circle { fill: #b8b378; }
.cgraph-node.g-7 circle { fill: #cf9aa8; }
.cgraph-node.g-8 circle { fill: #93b585; }
.cgraph-node.g-9 circle { fill: #a9a9bb; }
.cgraph-key.g-0 { background: #8fabd8; }
.cgraph-key.g-1 { background: #79bda6; }
.cgraph-key.g-2 { background: #c6a570; }
.cgraph-key.g-3 { background: #b3a3c4; }
.cgraph-key.g-4 { background: #d8a48c; }
.cgraph-key.g-5 { background: #7fa8b8; }
.cgraph-key.g-6 { background: #b8b378; }
.cgraph-key.g-7 { background: #cf9aa8; }
.cgraph-key.g-8 { background: #93b585; }
.cgraph-key.g-9 { background: #a9a9bb; }

.cgraph-node.k-security circle { fill: var(--k-security); }
.cgraph-node.k-agent circle, .cgraph-node.k-person circle { fill: var(--k-person); }
.cgraph-node.k-filing circle { fill: var(--k-filing); }
.cgraph-node.k-prediction circle { fill: var(--k-prediction); }
/* Three roles, and role NEVER touches the fill. Colour belongs to the kind and
   to the legend that explains it; an endpoint painted --accent was a colour that
   appeared in the picture and in no key, so the two most important nodes on
   screen were the two a reader could not look up. Size is out for the same
   reason: a bigger circle reads as "more of that kind", which is not the claim.
   The endpoints are marked by an OUTLINE and the hinge by its SHAPE, both of
   which are free of the colour channel. */
.cgraph-node.is-endpoint circle { stroke: var(--fg); stroke-width: 1.5; }
.cgraph-node.is-hinge rect { fill: var(--fg); stroke-width: 2; }
.cgraph-node:hover circle, .cgraph-node:focus circle,
.cgraph-node:hover rect, .cgraph-node:focus rect { fill: var(--accent); }
.cgraph-node:focus { outline: none; }
.cgraph-node:focus circle, .cgraph-node:focus rect {
  stroke: var(--accent); stroke-width: 2;
}
/* The detail line under the graph is not a caption, it is the ANSWER: click a
   node and this is what tells you who it is and how it connects
   ("Logan Green · agent · director of, proposed sale, acts on, traded"). It was
   inheriting `.denom`'s 0.85em on top of a rail that is already narrow, which
   compounded into something barely readable — the one line in the column a
   reader actually has to read was the smallest text on the page. Sized in rem
   so it cannot compound with whatever the container does. */
/* Ticker labels on the universe graph. Sized in viewBox units (the SVG scales
   with the fit), quiet enough that the dots stay the subject, and pointer-events
   off so a label can never swallow a click meant for its node. */
.cgraph-label {
  /* Set per-draw by the renderer from the fitted viewBox (see fitViewBox): the
     units are viewBox units, so a fixed size vanishes as soon as the layout
     spreads. The fallback matters only if the script has not run. */
  font-size: var(--cgraph-label-size, 11px);
  fill: var(--muted);
  pointer-events: none;
  font-family: var(--mono, ui-monospace, monospace);
  letter-spacing: 0.02em;
}

.cgraph-hint {
  margin: 0.5rem 0 0;
  line-height: 1.5;
  font-size: 0.92rem;
  color: var(--fg);
}
/* The name and kind lead it; the relation list stays quiet behind them. */
.cgraph-hint .cgraph-detail-name { font-weight: 600; }
.cgraph-hint .cgraph-detail-rel { color: var(--muted); }
/* Reads as a caption for the picture above it, so it needs a gap that separates
   it from the drawing rather than sitting on the last node. */
.cgraph-legend { margin: 0.9rem 0 0; line-height: 1.6; font-size: 0.82rem; }
.cgraph-key {
  display: inline-block; width: 0.55rem; height: 0.55rem; border-radius: 50%;
  background: var(--k-other); vertical-align: baseline;
}
.cgraph-key.k-company { background: var(--k-company); }
.cgraph-key.k-security { background: var(--k-security); }
.cgraph-key.k-agent, .cgraph-key.k-person { background: var(--k-person); }
.cgraph-key.k-filing { background: var(--k-filing); }
.cgraph-key.k-prediction { background: var(--k-prediction); }

/* Selecting a card swaps the picture; aria-current carries the state so it is
   not signalled by colour alone. */
.cgraph-pick { cursor: pointer; }
.cgraph-pick[aria-current="true"] {
  box-shadow: inset 2px 0 0 var(--accent);
  padding-left: 0.6rem;
}
.cgraph-pick:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }

/* LinkedIn section on the voice page (posts + engagement graph) */
#linkedin-section .li-post {
  padding: 0.2rem 0;
  margin: 0.6rem 0;
}
#linkedin-section .li-post p { margin: 0.2rem 0; }
.linkedin-engagement {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
  margin-top: 1rem;
}
.engagement-col { min-width: 12rem; }
ul.engagement { list-style: none; padding: 0; margin: 0.2rem 0; }
ul.engagement li { padding: 0.15rem 0; }
/* why an engager is on the page — stated inline, never implied */
.reason-chip {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0 0.4rem;
  font-size: 0.78em;
  color: var(--muted, #666);
  border: 1px solid var(--rule, #ddd);
  border-radius: 2px;
  white-space: nowrap;
}
.engagement-graph { margin-top: 0.6rem; max-width: 100%; }
.engagement-graph canvas { max-width: 100%; }
