/* Component styles for the citation index. Every colour, radius, spacing
   step and type size below is a token from tokens.css (ported verbatim from
   fo-analytics-v1) so this product reads as part of the same bundle.

   Deliberately NEVER used here: the gain/loss colour tokens tokens.css
   defines for the sibling products. A citation index has no gain or loss to
   colour; red or green on a stock, video or chip would read as a trading
   verdict. citations/tests.py scans this file to enforce that.

   --- The market-terminal pass (2026-09-04) ---------------------------------

   This file now carries a ticker strip, symbol badges, a mention-volume
   strip, a channel tile board and a status gauge board, so the product reads
   as a data terminal rather than a form with boxes under it. The rule above
   did not loosen to allow that -- it got tighter, and the constraints are
   written next to each component rather than assumed:

     * ONE accent colour does all the work. No component's colour varies with
       the value it is showing. A busy day on the volume strip is a taller
       teal bar, never a differently-coloured one; a symbol on the ticker is
       the same teal at 12 mentions as at 1.
     * No arrows, no triangles, no +/- anywhere near a symbol. The one glyph
       in the product that points is the play triangle on a timestamp chip,
       which points at a video.
     * Nothing is ordered by a number this product computed about a stock.
       The ticker is alphabetical, the tile board is alphabetical, the volume
       strip is chronological. See views.ticker_symbols() for why that is a
       correctness property and not a styling preference.
     * The only amber/red in the product lives in .cap-gauge on the status
       page, describes this project's own daily API spend, and is scoped by
       selector so it cannot be reused elsewhere. See that section.
     * No candlestick, price line, sparkline-of-price or axis imagery exists
       anywhere here, decorative or otherwise. */

/* ---------- app-only tokens ----------
   tokens.css is a verbatim port and its own header says nothing in it is
   edited, so the handful of custom properties this app needs and the
   platform does not have are declared here instead of upstream.

   --cap-warn / --cap-crit are the status page's daily-cap gauge colours.
   They are NOT a port of the sibling products' gain/loss pair -- this file
   is forbidden from even naming those two, and citations/tests.py enforces
   that by scanning for the literal strings, which is why they are described
   rather than spelled here. Nor are they interchangeable with them: these
   two say how close today's ingestion is to an API's own daily ceiling, an
   operational budget this project spends, and they are referenced by exactly
   one selector block in this file (.cap-gauge, far below). citations/tests.py
   asserts both that they are never used outside it and that no citations
   template mentions them at all.

   --grid-line is the ledger texture (see body). */
:root {
  --cap-warn: #E0A63C;
  --cap-crit: #E4705C;
  --grid-line: rgba(231, 234, 240, .035);
  --ticker-duration: 120s;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --cap-warn: #8A5A00;
    --cap-crit: #9E3527;
    --grid-line: rgba(24, 34, 49, .05);
  }
}
:root[data-theme="light"] {
  --cap-warn: #8A5A00;
  --cap-crit: #9E3527;
  --grid-line: rgba(24, 34, 49, .05);
}

/* ---------- base ---------- */
* { box-sizing: border-box; }
html { color-scheme: dark light; }
body {
  margin: 0;
  font-family: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: var(--fs-md);
  line-height: 1.55;
  color: var(--text);
  -webkit-font-smoothing: antialiased;

  /* Ledger texture: two hairline gradients on a 64px grid at ~3-5% alpha
     over the normal page ground. CSS only -- no image asset, nothing to
     download, and it degrades to a flat --bg wherever gradients are not
     painted. Kept under the contrast floor of every text tone in the token
     set on purpose: it should be felt at a glance and be invisible while
     reading a line, which is why the alpha lives in a token that is tuned
     per theme (the light palette needs a touch more to register at all). */
  background-color: var(--bg);
  background-image:
    linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size: 64px 64px;
  background-attachment: fixed;
}
h1, h2, h3 { font-family: "Archivo", system-ui, sans-serif; letter-spacing: -.01em; margin: 0 0 var(--s3); }
h1 { font-size: var(--fs-2xl); line-height: 1.15; }
h1 .help { font-family: "IBM Plex Sans", system-ui, sans-serif; font-weight: 400; font-size: var(--fs-md); letter-spacing: 0; }
h2 { font-size: var(--fs-lg); }
h3 { font-size: var(--fs-md); }
p { margin: 0 0 var(--s4); }
a { color: var(--accent); text-decoration: none; }
a:hover, a:focus-visible { color: var(--accent-strong); text-decoration: underline; }
code { font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: .9em; background: var(--surface); padding: 0 .3em; border-radius: var(--r); }
/* Every figure in the product -- mention counts, timestamps, quota numbers,
   dates -- renders in the token set's mono face with tabular figures, so
   columns of numbers line up the way a data terminal's do instead of
   shifting with proportional digit widths. `.num` is the single hook the
   templates apply; the rest of these are components that are figures by
   nature and should never need the class spelled out. */
.num, .chip-ts, .ticker-count, .sym-badge, .board-figure, .tile-meta dd,
.volume-axis, .cap-pct {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.help { font-size: var(--fs-sm); color: var(--text-muted); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (prefers-reduced-motion: no-preference) {
  a, .btn, .chip, .chip-check, .mode-tab, .video-card, .combobox-list li, .tile, .ticker-item, .volume-bar {
    transition: background-color .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease, transform .15s ease;
  }
}

/* ---------- header / nav ---------- */
.topbar {
  display: flex; align-items: center; gap: var(--s5); flex-wrap: wrap;
  padding: var(--s3) var(--s5);
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-1);
}
.brand { display: flex; align-items: center; gap: var(--s2); color: var(--text); }
.brand:hover { text-decoration: none; color: var(--text); }
.brand-mark {
  width: 28px; height: 28px; border-radius: var(--r-lg); flex: none;
  background: linear-gradient(155deg, var(--accent), var(--accent-strong));
  color: var(--on-accent); font-family: "Archivo", sans-serif; font-weight: 800; font-size: var(--fs-sm);
  display: flex; align-items: center; justify-content: center;
}
.brand-word { font-family: "Archivo", sans-serif; font-weight: 700; font-size: var(--fs-md); }
.nav { display: flex; gap: var(--s1); flex-wrap: wrap; }
.nav a {
  padding: var(--s2) var(--s3); border-radius: var(--r-lg);
  color: var(--text-muted); font-weight: 500; font-size: var(--fs-sm);
}
.nav a:hover, .nav a:focus-visible { color: var(--text); background: var(--accent-soft); text-decoration: none; }
.nav a.active, .nav a[aria-current="page"] { color: var(--text); background: var(--accent-soft); box-shadow: inset 0 -2px 0 var(--accent); }
.topbar-spacer { flex: 1; }
.auth-nav { display: flex; gap: var(--s3); align-items: center; font-size: var(--fs-sm); }
.user-chip {
  width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--border); font-size: var(--fs-xs); font-weight: 600; color: var(--text-muted);
}
form.inline-logout { display: inline; }

main { max-width: 42rem; margin: 0 auto; padding: var(--s6) var(--s5) var(--s7); }
main.wide { max-width: 64rem; }

/* ---------- buttons ---------- */
.btn {
  font: inherit; font-weight: 600; font-size: var(--fs-sm);
  padding: var(--s2) var(--s4); cursor: pointer;
  border-radius: var(--r-lg); border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--text);
}
.btn:hover, .btn:focus-visible { border-color: var(--accent); background: var(--accent-soft); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn-primary:hover, .btn-primary:focus-visible { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--on-accent); }
.btn[aria-busy="true"], .btn:disabled { opacity: .7; cursor: progress; }
.link-btn { background: none; border: none; padding: 0; font: inherit; font-size: var(--fs-sm); color: var(--accent); text-decoration: underline; cursor: pointer; }
.link-btn:hover { color: var(--accent-strong); }

/* ---------- framing (disclaimer) ---------- */
.framing {
  border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--r-lg);
  background: var(--surface); padding: var(--s3) var(--s4); margin: 0 0 var(--s5);
  font-size: var(--fs-sm); line-height: 1.55; color: var(--text-muted);
}
.framing strong { color: var(--text); }
.framing ul { margin: var(--s2) 0 0; padding-left: 1.1rem; }
.framing li { margin-bottom: var(--s1); }
.framing li:last-child { margin-bottom: 0; }

/* ---------- forms ---------- */
.auth-card, .search-form {
  border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface);
  padding: var(--s5); box-shadow: var(--shadow-1);
}
.search-form { display: flex; flex-wrap: wrap; gap: var(--s4) var(--s5); align-items: flex-end; margin-bottom: var(--s5); }
.field-row { display: flex; flex-direction: column; gap: var(--s1); margin-bottom: var(--s4); min-width: 0; }
.search-form .field-row { margin-bottom: 0; min-width: 12rem; }
.search-form .field-row.grow { flex: 1 1 18rem; }
/* The channel picker is a full-width row of its own, never a column.
   .channel-picker already asked for min-width:100%, but at one class of
   specificity it lost to the `.search-form .field-row` rule above, so the
   picker wrapped into a narrow column and left a tall empty gutter next to
   it. Matching that rule's specificity is the fix. */
.search-form .channel-picker { min-width: 100%; flex-basis: 100%; }
.field-row label, legend { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); font-weight: 600; }
.field-row input, .field-row select, .combobox input {
  font: inherit; padding: var(--s2) var(--s3); width: 100%;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--r-lg);
}
.field-row input:focus, .combobox input:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }
.radio-row { display: flex; gap: var(--s4); flex-wrap: wrap; }
.radio-row label { display: flex; align-items: center; gap: var(--s1); font-size: var(--fs-sm); text-transform: none; letter-spacing: 0; color: var(--text); font-weight: 400; cursor: pointer; }
.date-row { display: flex; gap: var(--s2); }
.errorlist { margin: 0 0 var(--s4); padding-left: 1.1rem; color: var(--text); font-size: var(--fs-sm); }
.cooldown-note, .messages li { border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); padding: var(--s3) var(--s4); margin-bottom: var(--s4); font-size: var(--fs-sm); }
.messages { list-style: none; padding: 0; margin: 0 0 var(--s4); }
.auth-alt { font-size: var(--fs-sm); margin-top: var(--s4); color: var(--text-muted); }

/* ---------- mode tabs ---------- */
.mode-tabs { display: flex; gap: var(--s1); margin-bottom: var(--s4); border-bottom: 1px solid var(--border); }
.mode-tab {
  font: inherit; font-weight: 600; font-size: var(--fs-sm); padding: var(--s2) var(--s4);
  border: 1px solid transparent; border-bottom: none; border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: transparent; color: var(--text-muted); cursor: pointer; margin-bottom: -1px;
}
.mode-tab:hover { color: var(--text); background: var(--accent-soft); }
.mode-tab[aria-selected="true"] { color: var(--text); background: var(--surface); border-color: var(--border); box-shadow: inset 0 2px 0 var(--accent); }

/* ---------- combobox (custom autocomplete) ---------- */
.combobox { position: relative; }
.combobox-list {
  position: absolute; z-index: 20; left: 0; right: 0; top: calc(100% + 4px);
  margin: 0; padding: var(--s1); list-style: none; max-height: 18rem; overflow-y: auto;
  background: var(--bg-elevated); border: 1px solid var(--border-strong); border-radius: var(--r-lg); box-shadow: var(--shadow-2);
}
.combobox-list li { padding: var(--s2) var(--s3); border-radius: var(--r); cursor: pointer; display: flex; gap: var(--s3); align-items: baseline; }
.combobox-list li .sym { font-family: "IBM Plex Mono", ui-monospace, monospace; font-weight: 600; min-width: 7ch; }
.combobox-list li .name { color: var(--text-muted); font-size: var(--fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combobox-list li[aria-selected="true"], .combobox-list li:hover { background: var(--accent-soft); color: var(--text); }
.combobox-list mark { background: none; color: var(--accent-strong); font-weight: 700; }
.combobox-empty { padding: var(--s2) var(--s3); color: var(--text-muted); font-size: var(--fs-sm); }

/* ---------- channel picker ---------- */
.channel-picker { border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--s3) var(--s4); min-width: 100%; background: var(--bg); }
.channel-picker legend { padding: 0 var(--s1); }
.channel-picker legend .help { text-transform: none; letter-spacing: 0; font-weight: 400; margin-left: var(--s2); }
.picker-global { font-size: var(--fs-sm); margin-bottom: var(--s2); display: flex; gap: var(--s2); align-items: center; }
.cat-group { margin: var(--s1) 0; border-top: 1px solid var(--border); padding-top: var(--s1); }
.cat-group summary { cursor: pointer; display: flex; gap: var(--s3); align-items: baseline; font-size: var(--fs-sm); padding: var(--s1) 0; list-style: none; }
.cat-group summary::-webkit-details-marker { display: none; }
.cat-group summary::before { content: "▸"; color: var(--text-muted); font-size: var(--fs-xs); }
.cat-group[open] summary::before { content: "▾"; }
.cat-group summary strong { color: var(--text); }
.group-toggles { margin-left: auto; display: flex; gap: var(--s2); align-items: center; }
.chip-checks { display: flex; flex-wrap: wrap; gap: var(--s2); padding: var(--s2) 0 var(--s1); }
.chip-check {
  display: inline-flex; align-items: center; gap: var(--s1);
  border: 1px solid var(--border-strong); border-radius: 999px; padding: .2rem .7rem;
  font-size: var(--fs-sm); cursor: pointer; background: var(--surface); color: var(--text-muted);
}
.chip-check:hover { border-color: var(--accent); color: var(--text); }
.chip-check:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); color: var(--text); font-weight: 600; }
.chip-check input { margin: 0; accent-color: var(--accent); }

/* ---------- results ---------- */
.result-summary { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: var(--s5); }
.video-card {
  border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface);
  padding: var(--s4) var(--s5); margin-bottom: var(--s4); box-shadow: var(--shadow-1);
}
.video-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow-2); }
.video-card h3 { margin: 0 0 var(--s1); font-size: var(--fs-md); }
.video-card h3 a { color: var(--text); }
.video-card h3 a:hover { color: var(--accent-strong); }
.video-card .meta { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: var(--s3); }
.chips { display: flex; flex-wrap: wrap; gap: var(--s2); }
.chip {
  display: inline-flex; align-items: center; gap: var(--s1);
  border: 1px solid var(--border-strong); border-radius: 999px; padding: .25rem .75rem;
  font-size: var(--fs-sm); color: var(--text); background: var(--bg);
}
.chip:hover, .chip:focus-visible { border-color: var(--accent); background: var(--accent-soft); color: var(--text); text-decoration: none; transform: translateY(-1px); }
.chip-ts .chip-icon { color: var(--accent); font-size: .7em; }
.chip .count { color: var(--text-muted); font-size: var(--fs-xs); }
.hits { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--s3); }
.hit { display: flex; gap: var(--s3); align-items: flex-start; }
.hit .chip { flex: none; }
.snippet { margin: 0; padding: var(--s1) var(--s3); border-left: 2px solid var(--accent); color: var(--text); font-size: var(--fs-sm); line-height: 1.6; min-width: 0; }
.snippet cite { display: block; font-style: normal; font-size: var(--fs-xs); color: var(--text-muted); margin-top: var(--s1); }
.snippet mark { background: var(--accent-soft); color: var(--text); font-weight: 700; padding: 0 .1em; border-radius: var(--r); }
.empty-state {
  border: 1px dashed var(--border-strong); border-radius: var(--r-lg); background: var(--surface);
  padding: var(--s6) var(--s5); text-align: center;
}
.empty-state .empty-title { font-family: "Archivo", sans-serif; font-weight: 600; font-size: var(--fs-md); margin: 0 0 var(--s1); color: var(--text); }
.empty-state .empty-hint { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; }

/* ---------- tables (coverage / status) ---------- */
.coverage-group { margin-top: var(--s5); }
.coverage-group h2 { font-size: var(--fs-md); margin: 0 0 var(--s2); display: flex; gap: var(--s2); align-items: baseline; }
.coverage-table, .status-table { border-collapse: collapse; width: 100%; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
.coverage-table th, .coverage-table td, .status-table th, .status-table td { text-align: left; padding: var(--s2) var(--s4); border-bottom: 1px solid var(--border); }
.coverage-table tr:last-child td, .status-table tr:last-child td { border-bottom: none; }
.coverage-table th, .status-table th { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); background: var(--bg); }
.coverage-table tbody tr:hover, .status-table tbody tr:hover { background: var(--accent-soft); }
.status-over { color: var(--text); font-weight: 700; text-decoration: underline; }
.scroller { overflow-x: auto; }

/* ---------- symbol badge ----------
   The one way a stock symbol is drawn anywhere in this product: mono,
   bordered, faintly tinted -- how a terminal frames a ticker symbol. It
   carries NO state. There is no modifier that colours it by a count, a
   direction, or anything else, and there is deliberately no room in the
   design for one: the badge says "this is a symbol", and any number about
   that symbol lives outside it as its own labelled figure. */
.sym-badge {
  display: inline-block;
  padding: .05rem .4rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--accent-soft);
  color: var(--text);
  font-weight: 600;
  font-size: .92em;
  letter-spacing: .04em;
  line-height: 1.5;
  white-space: nowrap;
}
.sym-badge-lg {
  font-size: .82em;
  padding: .1rem .5rem;
  letter-spacing: .06em;
  vertical-align: .06em;
}
.chip:hover .sym-badge, .chip:focus-visible .sym-badge { border-color: var(--accent); }

/* ---------- ticker strip ----------
   Chrome above the search form carrying the symbols actually cited in the
   last few days and the literal citation count behind each.

   The marquee is decoration; the DATA's neutrality is enforced in
   views.ticker_symbols() (alphabetical, never count-ordered) and in the
   template, and this file adds nothing that could reintroduce a signal:
   every item is the same colour at the same weight regardless of its count,
   there is no arrow or triangle in the strip, and no rule here reads the
   count at all. */
.ticker {
  display: flex; align-items: center; gap: var(--s3);
  margin: 0 0 var(--s5);
  padding: var(--s2) var(--s3);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--bg-deep);
  overflow: hidden;
}
.ticker-label, .ticker-note {
  flex: none;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .08em;
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
}
.ticker-label { padding-right: var(--s3); border-right: 1px solid var(--border); }
.ticker-note { padding-left: var(--s3); border-left: 1px solid var(--border); text-transform: none; letter-spacing: .02em; font-weight: 400; }
.ticker-viewport { flex: 1 1 auto; min-width: 0; overflow: hidden; }
.ticker-track { display: flex; width: max-content; }
.ticker-run { display: flex; align-items: center; gap: var(--s5); list-style: none; margin: 0; padding: 0 var(--s5) 0 0; }
.ticker-item { display: inline-flex; align-items: baseline; gap: var(--s2); white-space: nowrap; }
.ticker-count { font-size: var(--fs-sm); font-weight: 600; color: var(--accent); }
.ticker-unit { font-size: var(--fs-xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; }

@media (prefers-reduced-motion: no-preference) {
  /* Two identical runs sit in the track, so translating by exactly -50%
     lands the second run where the first started and the restart is
     seamless. Slow on purpose: this is ambient chrome, not something a
     reader should feel obliged to keep up with. */
  .ticker-track { animation: ticker-scroll var(--ticker-duration) linear infinite; }
  .ticker:hover .ticker-track, .ticker:focus-within .ticker-track { animation-play-state: paused; }
}
@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  /* No animation at all -- the strip becomes a plain scrollable row, which
     is why the viewport rather than the track owns the overflow. */
  .ticker-viewport { overflow-x: auto; }
}

/* ---------- neutral mention-volume strip ----------
   One bar per calendar day; height is that day's share of the busiest day.

   Everything here is chosen to keep it legible as a COUNT histogram and to
   keep it from drifting into looking like a price chart: bars are separated
   rather than joined, there is no connecting line and no area fill, the
   baseline is the bottom of the box (not a zero line values can sit below),
   and every bar is the identical accent colour -- height is the only channel
   carrying information. A day with nothing on it is drawn as a hairline in
   the border tone, so an empty day reads as "nothing was said" rather than
   disappearing into the gap between two bars. */
.volume {
  margin: 0 0 var(--s5);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
}
.volume-caption {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s3);
  margin-bottom: var(--s3);
}
.volume-title {
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .08em;
  font-weight: 600; color: var(--text-muted);
}
.volume-bars { display: flex; align-items: flex-end; gap: 2px; height: 46px; }
.volume-bar {
  flex: 1 1 0; min-width: 2px;
  height: var(--bar); min-height: 2px;
  background: var(--accent); opacity: .8;
  border-radius: 1px 1px 0 0;
}
.volume-bar:hover { opacity: 1; }
/* A day with nothing on it is a DOT, not a full-width hairline. With 2px
   gaps, thirty adjacent full-width hairlines merge into what reads as a
   continuous baseline -- and a continuous line under a chart is exactly the
   price-chart cue this strip must not borrow. A centred 2px dot per empty
   day reads as a discrete tick: "nothing was said on this day", still
   visible, impossible to mistake for a plotted line. */
.volume-bar.is-empty { height: 2px; background: none; opacity: 1; position: relative; }
.volume-bar.is-empty::after {
  content: ""; position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  width: 2px; height: 2px; border-radius: 50%; background: var(--border-strong);
}
.volume-bar.is-empty:hover::after { background: var(--text-muted); }
.volume-axis {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3);
  margin-top: var(--s2); padding-top: var(--s2);
  border-top: 1px solid var(--border);
  font-size: var(--fs-xs); color: var(--text-muted);
}
.volume-axis .help { font-size: var(--fs-xs); }

/* ---------- coverage tile board ---------- */
.tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: var(--s3); }
.tile {
  position: relative;
  display: flex; flex-direction: column; gap: var(--s2);
  padding: var(--s4);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--shadow-1);
}
.tile:hover { border-color: var(--accent); box-shadow: var(--shadow-2); transform: translateY(-1px); }
.tile-name { margin: 0; font-size: var(--fs-md); line-height: 1.3; padding-right: 3.6rem; }
.tile-cat { margin: 0; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); }
.tile-meta { margin: auto 0 0; padding-top: var(--s2); border-top: 1px solid var(--border); }
.tile-meta dt { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); }
.tile-meta dd { margin: 0; font-size: var(--fs-sm); }
.tile-badge {
  position: absolute; top: var(--s3); right: var(--s3);
  padding: .05rem .45rem;
  border: 1px solid var(--accent); border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-strong);
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .07em; font-weight: 600;
}

/* ---------- status board ---------- */
.board { display: grid; gap: var(--s3); margin-bottom: var(--s5); }
.board-row {
  padding: var(--s4) var(--s5);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--shadow-1);
}
.board-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s2) var(--s4); }
.board-name { margin: 0; font-size: var(--fs-md); font-weight: 600; }
.board-name .help { text-transform: none; letter-spacing: 0; }
.board-figure { margin: 0; font-size: var(--fs-xl); line-height: 1.1; white-space: nowrap; }
.board-used { font-weight: 600; color: var(--text); }
.board-sep, .board-cap { color: var(--text-muted); font-size: var(--fs-lg); }
.board-sep { padding: 0 .15em; }
.board-note { margin: var(--s2) 0 0; }

/* THE ONE PLACE --cap-warn / --cap-crit ARE USED.
   A daily-cap gauge for this project's own API spend: how much of today's
   YouTube quota / TranscriptAPI budget ingestion has burned. Amber and red
   here mean "close to being throttled today", an operational fact about a
   pipeline, and this block is the only thing in the product allowed to say
   it in colour. Nothing in these selectors can ever match a symbol, a
   citation, a chip or a channel -- .cap-gauge exists on the status page
   alone -- and citations/tests.py asserts that both the tokens and the class
   stay confined here. */
.cap-gauge {
  height: 10px; margin-top: var(--s3);
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg); overflow: hidden;
}
.cap-fill { display: block; height: 100%; width: var(--fill); background: var(--accent); border-radius: 999px; }
.cap-gauge[data-level="warn"] .cap-fill { background: var(--cap-warn); }
.cap-gauge[data-level="crit"] .cap-fill,
.cap-gauge[data-level="over"] .cap-fill { background: var(--cap-crit); }
.cap-gauge[data-level="warn"] { border-color: var(--cap-warn); }
.cap-gauge[data-level="crit"],
.cap-gauge[data-level="over"] { border-color: var(--cap-crit); }
@media (prefers-reduced-motion: no-preference) {
  .cap-fill { transition: width var(--tween-ms) ease-out; }
}

.board-ambient {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s3);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
  border-radius: var(--r-lg); background: var(--bg-deep);
  font-size: var(--fs-sm);
}
.board-ambient-label {
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .08em;
  font-weight: 600; color: var(--text-muted);
}
.board-ambient .help { flex: 1 1 20rem; }

/* ---------- search scanning state ----------
   Replaces the old "Searching…" button label with a line that says what the
   request is actually doing: sweeping the transcripts of a known number of
   channels. The number is the real count of channels the submitted form
   will search (static/js/ui.js reads it off the form), not decoration, and
   the sweep is an indeterminate progress indicator -- it deliberately does
   not pretend to know a percentage it cannot know. */
.scanning {
  display: flex; align-items: center; gap: var(--s3);
  margin-top: var(--s4); padding: var(--s2) var(--s3);
  width: 100%;
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--bg-deep);
  font-size: var(--fs-sm); color: var(--text-muted);
}
/* The line is appended INTO the search form, which is a wrapping flex
   container -- without an explicit full-width basis it becomes a
   content-sized flex item wedged beside the submit button, and the sweep
   track (flex: 1 1 auto) collapses to nothing. It is a row of its own. */
.search-form .scanning { flex: 1 0 100%; margin-top: 0; }
.scanning-text { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .08em; font-weight: 600; white-space: nowrap; }
.scanning-text .num { text-transform: none; letter-spacing: 0; color: var(--accent); }
.scanning-track { flex: 1 1 auto; height: 3px; border-radius: 999px; background: var(--border); overflow: hidden; }
.scanning-sweep { display: block; height: 100%; width: 32%; border-radius: 999px; background: var(--accent); }
@media (prefers-reduced-motion: no-preference) {
  .scanning-sweep { animation: scanning-sweep 1.1s ease-in-out infinite; }
}
@keyframes scanning-sweep {
  from { transform: translateX(-110%); }
  to { transform: translateX(320%); }
}

/* ---------- mode tab panel transition ---------- */
.mode-panel { will-change: opacity, transform; }
@media (prefers-reduced-motion: no-preference) {
  .mode-panel.is-entering { animation: panel-enter var(--tween-ms) ease-out both; }
}
@keyframes panel-enter {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ---------- responsive ---------- */
@media (max-width: 960px) {
  main, main.wide { padding: var(--s5) var(--s4) var(--s6); }
  h1 { font-size: var(--fs-xl); }
  .tile-grid { grid-template-columns: repeat(auto-fill, minmax(12.5rem, 1fr)); }
}
@media (max-width: 600px) {
  .topbar { gap: var(--s3); padding: var(--s3) var(--s4); }
  .brand-word { display: none; }
  .nav a { padding: var(--s1) var(--s2); }
  main, main.wide { padding: var(--s4) var(--s3) var(--s6); }
  h1 { font-size: var(--fs-lg); }
  .search-form { padding: var(--s4); gap: var(--s3); }
  .search-form .field-row { min-width: 100%; }
  .date-row { flex-direction: column; }
  .video-card { padding: var(--s3) var(--s4); }
  .hit { flex-direction: column; gap: var(--s1); }
  .empty-state { padding: var(--s5) var(--s4); }
  .group-toggles { margin-left: 0; }
  /* The ticker's two end labels are the first thing to go on a narrow
     screen -- the strip itself is the content, and the A-Z note is a
     description of an ordering the reader can see. The leading label stays
     because without it the row of symbols has no stated meaning. */
  .ticker-note { display: none; }
  .ticker-run { gap: var(--s4); }
  .board-row { padding: var(--s3) var(--s4); }
  .board-figure { font-size: var(--fs-lg); }
  .board-sep, .board-cap { font-size: var(--fs-md); }
  .tile-grid { grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); }
  .volume { padding: var(--s3); }
  .volume-bars { height: 36px; }
  .volume-axis { flex-wrap: wrap; }
  /* Two dates on one line, the explanatory note wrapping under them --
     losing the note would leave the two bare dates looking like an axis
     range, which is exactly the reading the note exists to prevent. */
  .volume-axis .help { order: 3; flex: 1 0 100%; }
  .scanning { flex-wrap: wrap; }
  .scanning-track { flex-basis: 100%; }
}

/* ---------------------------------------------------------------------------
   AI Research Digest (citations/templates/citations/research.html).
   Same surfaces, tokens and chips as the results page. The interpretation
   framing keeps the accent rule; the opinion framing (Feature B, flag-gated)
   uses the stronger border so it cannot be mistaken for the digest.
   No new colour carries a judgement about a stock: source chips are the
   same .chip-ts as a citation chip, only wider because they name the
   channel and title beside the timestamp.
   --------------------------------------------------------------------------- */
.research-link { margin: 0 0 var(--s4); font-size: var(--fs-sm); }
.research-link a { font-weight: 600; }

.research-forms { display: grid; gap: var(--s4); grid-template-columns: 1fr; margin-bottom: var(--s5); }
@media (min-width: 900px) { .research-forms { grid-template-columns: 1fr 1fr; } }
.research-forms .search-form { margin: 0; }
.research-presets { margin-bottom: var(--s5); }
.research-h2 { margin: var(--s5) 0 var(--s3); font-size: var(--fs-lg); }
.research-h2 .help { font-size: var(--fs-sm); color: var(--text-muted); }
.research-sources { display: grid; gap: var(--s3); }
.video-card-compact { padding: var(--s3) var(--s4); }
.video-card-compact h3 { font-size: var(--fs-md); margin: 0 0 var(--s1); }

.framing-ai { border-left-color: var(--accent-strong); }
.framing-ai > strong { display: block; margin-bottom: var(--s2); color: var(--text); }
.framing-opinion {
  border: 2px solid var(--border-strong); border-left: 6px solid var(--text);
  background: var(--surface-alt); color: var(--text);
}
.framing-opinion > strong { display: block; margin-bottom: var(--s2); font-size: var(--fs-md); }
.framing-opinion p { margin: 0; }

.digest {
  border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface);
  padding: var(--s4) var(--s5); margin-bottom: var(--s4); box-shadow: var(--shadow-1);
}
.digest-settings { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); margin: 0 0 var(--s2); font-size: var(--fs-sm); color: var(--text-muted); }
.digest-kv { white-space: nowrap; }
.digest-k { text-transform: uppercase; letter-spacing: .06em; font-size: var(--fs-xs); color: var(--text-faint); margin-right: var(--s1); }
.digest-v { color: var(--text); }
.digest-transparency { margin: 0 0 var(--s4); font-size: var(--fs-sm); color: var(--text-muted); border-bottom: 1px solid var(--border); padding-bottom: var(--s3); }
.digest-section { margin-bottom: var(--s4); }
.digest-heading { margin: 0 0 var(--s2); font-size: var(--fs-md); }
.digest-points { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }
.digest-point { border-left: 2px solid var(--border-strong); padding-left: var(--s3); }
.digest-text { margin: 0 0 var(--s2); line-height: 1.55; }
.digest-sources { gap: var(--s1); }
.chip-src { max-width: 100%; }
.chip-src-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 22ch; }
.chip-src-sep { color: var(--text-faint); }
.digest-caveats { margin: var(--s3) 0 0; font-size: var(--fs-sm); color: var(--text-muted); border-top: 1px solid var(--border); padding-top: var(--s3); }
.digest-empty-section { margin: 0; }
.digest-withheld { border-style: solid; }

.flag-form { margin: 0 0 var(--s5); font-size: var(--fs-sm); }
.flag-form label { display: block; margin-bottom: var(--s1); font-weight: 600; }
.flag-row { display: flex; gap: var(--s2); flex-wrap: wrap; }
.flag-row input[type="text"] { flex: 1 1 24ch; min-width: 0; }
.research-generate { margin: 0 0 var(--s5); }
.research-generate form { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }
.research-h2-opinion { border-top: 2px solid var(--border-strong); padding-top: var(--s5); }
@media (max-width: 600px) {
  .chip-src { flex-wrap: wrap; }
  .chip-src-title { max-width: 100%; white-space: normal; }
}
.research-presets .radio-row label { white-space: nowrap; }
