/* ==========================================================================
   Global Prime Tools — dashboard styles.

   Brand tokens are lifted from globalprime.com's own Webflow stylesheet, so the
   dashboard matches the site rather than approximating it:

     --colour-primary--500   #ade512   the lime
     --colour-secondary--100 #bebebe   secondary ink
     --colour-secondary--200 #2c2c2c   borders
     --colour-secondary--300 #1b1b1b   elevated surface
     --colour-secondary--400 #141414   surface
     --colour-secondary--500 #0f0f0f   page
     --font--headings/body   Delight
     radii                   .25 / .5 / .75 / 1 / 1.25em

   DARK ONLY. Global Prime's brand has no light theme — every secondary step is a
   dark neutral — and the lime measures 12.77:1 on the brand page colour against
   1.50:1 on white, so a light variant could not use the brand accent at all
   without substituting a different colour. There is no toggle and no
   prefers-color-scheme branch: one theme, matching the site.

   The lime is brand chrome only (logo, active chip, focus ring). Gains and
   losses keep the reserved status green/red, always paired with a ▲/▼ glyph and
   a signed number. Lime and the status green measure ΔE 21.6 apart under protan
   simulation, so brand accent and "up" can share a screen without being
   confused.
   ========================================================================== */

/* Type treatment follows globalprime.com's own rules, not an approximation:
     h1–h4      font-weight 500, letter-spacing 0, text-wrap balance
     p          font-weight 400, colour --colour-secondary--100 (#bebebe)
     .caption   lime, uppercase, letter-spacing 3px, weight 500, .85em
   The site uses .caption as the eyebrow above a section, which is exactly what
   the panel headings here are — so they take the lime caption treatment rather
   than a muted grey invention.

   Note the site sets letter-spacing 0 on headings. Tightening display text is a
   common default that would visibly diverge from the brand, so it isn't done.

   Delight ships two weights only — 400 and 500. Nothing here asks for more, so
   the browser never has to synthesise a faux-bold (which smears a CFF face).
   Hierarchy comes from size, case, colour and letter-spacing instead — the same
   approach the brand site takes. */
@font-face {
  font-family: Delight;
  src: url("fonts/delight-regular.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: Delight;
  src: url("fonts/delight-medium.woff") format("woff");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: dark;

  /* Brand */
  --brand-lime: #ade512;
  --accent: #ade512;
  --accent-ink: #0f0f0f; /* text on a lime fill — 12.8:1 */

  /* Surfaces (brand secondary scale) */
  --page: #0f0f0f;
  --surface-1: #141414;
  --surface-2: #1b1b1b;

  /* Ink */
  --text-primary: #ffffff;
  --text-secondary: #bebebe;
  --text-muted: #8a8a8a;

  /* Lines */
  --gridline: #1f1f1f;
  --baseline: #2c2c2c;
  --border: #2c2c2c;

  /* Delta + status tokens (reserved; never reused as brand or series colour) */
  --delta-up: #4ec44e;
  --delta-down: #ef5f5f;

  /* Categorical series slots, for Prime AI's charts. Fixed order, assigned by
     series index, never cycled — the order IS the colourblind-safety mechanism, so
     resequencing these invalidates the checks below. Validated against the chart
     surface #1b1b1b: lightness band PASS, chroma floor PASS, worst adjacent CVD
     ΔE 8.4 (protan, target ≥8) PASS, worst adjacent normal-vision ΔE 19.7
     (floor ≥15) PASS, contrast all ≥3:1 PASS. The source palette's green and red
     slots are deliberately absent: they share hue families with the delta tokens
     above, and a series that reads as "up" when it only means "the third
     instrument" is worse than being one hue short. See public/chart.js. */
  /* Intel status ramp. The lime stays on "Accelerating" — the strongest reading —
     and the rest step away from it in hue AND lightness, both descending with the
     theme's momentum, so the order is carried twice and neighbours stay apart.

     They are the lime's tonal siblings, not its lightness twins, and the difference
     matters: the lime is L 0.852 at C 0.213, which is 91% of the most chroma sRGB
     can produce at its hue. Holding that lightness across other hues forces chroma
     down to 0.07–0.11 — pastels, the opposite tone. Holding its *share of the
     available gamut* is what keeps the family: each of these sits near its own hue's
     cusp, so each is as vivid as its hue can be, exactly as the lime is.

     Measured on the card surface #1b1b1b: contrast 5.0–11.8:1 (marks need 3:1),
     adjacent steps ΔE 10–22, and every one at least as far from the gain/loss tokens
     as the lime already is (ΔE 14) — nothing here can be misread as up or down.
     "Fading" is deliberately drained to grey rather than red: red borders above a
     table where red means "down" is the exact misreading this board used to invite. */
  --intel-accelerating: #ade512; /* lime — brand, unchanged */
  --intel-building: #09f1c3; /* spring teal */
  --intel-emerging: #09cfd4; /* cyan */
  --intel-elevated: #06ace7; /* azure */
  --intel-active: #6c90ff; /* indigo — also the fallback for an unrecognised status */
  --intel-shifting: #ad66ff; /* violet */
  --intel-fading: #8f8f8f; /* drained */

  --series-1: #3987e5; /* blue */
  --series-2: #d95926; /* orange */
  --series-3: #199e70; /* aqua */
  --series-4: #c98500; /* yellow */
  --series-5: #9085e9; /* violet */
  --series-6: #d55181; /* magenta */
  --status-good: #4ec44e;
  --status-warning: #fab219;
  --status-serious: #ec835a;
  --status-critical: #ef5f5f;
  --status-neutral: #8a8a8a;

  /* Brand radii */
  --radius-s: 0.5em;
  --radius-m: 0.75em;
  --radius-l: 1em;
  --radius: 0.75em;

  --shadow: none;

  --font: Delight, "Helvetica Neue", Arial, sans-serif;
  /* Delight has proportional figures only (no `tnum`; its "1" is 40% narrower
     than its "0"), which makes a 59-row price table jitter and defeats column
     comparison. Numbers therefore use the system UI stack, whose faces all
     carry tabular figures. Brand type everywhere it reads as type; aligned
     digits everywhere they read as data. */
  --font-num: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* The UA's [hidden] rule is weaker than any class that sets `display`, so a
   styled element with the hidden attribute renders anyway — which shipped as an
   empty box under the Momentum heading. Make the attribute authoritative. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  background: var(--page);
  color: var(--text-primary);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  font-synthesis: none;
  text-wrap: pretty;
}

h1,
h2,
h3 {
  text-wrap: balance;
}

.wrap {
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 20px;
}

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--surface-1);
  color: var(--text-primary);
  padding: 10px 16px;
  z-index: 20;
  border-radius: 0 0 var(--radius-s) 0;
}
.skip:focus {
  left: 0;
}

:where(a, button, input):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-s);
}

/* Delight carries no geometric symbols — ▲ ▼ ● ↑ ↓ are all absent from its
   cmap, so they fall to whatever the fallback stack provides (on some systems
   ◑ renders as "›"). The direction arrows are load-bearing: they are what stops
   gain/loss depending on colour alone, so they must not be left to chance.
   Anything carrying a symbol therefore opts into the system stack, which has
   full coverage. */
.sym,
.badge-icon,
.col-title [aria-hidden],
.sortable::after {
  font-family: var(--font-num);
}

/* ── Masthead ─────────────────────────────────────────────────────────── */

.masthead {
  border-bottom: 1px solid var(--border);
  background: var(--surface-1);
  position: sticky;
  top: 0;
  z-index: 10;
}

.masthead-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-top: 15px;
  padding-bottom: 15px;
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
}

/* The published brand asset, unmodified — lime spiral, white wordmark. It only
   ever sits on a dark surface, so nothing needs recolouring. */
.brand-logo {
  height: 26px;
  width: auto;
  flex-shrink: 0;
}

.brand-divider {
  width: 1px;
  height: 26px;
  background: var(--border);
  flex-shrink: 0;
}

.masthead h1 {
  margin: 0;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
  white-space: nowrap;
}

.brand-sub {
  margin: 0;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--text-muted);
  white-space: nowrap;
}

/* ── Site nav ─────────────────────────────────────────────────────────── */

/* Two pages, two URLs. The current one is marked with aria-current and takes the
   lime underline — brand chrome, which is what a nav indicator is. */
.sitenav {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-right: auto;
  margin-left: 8px;
}

.sitenav-link {
  font-size: 13.5px;
  color: var(--text-secondary);
  text-decoration: none;
  padding: 6px 12px;
  border-radius: var(--radius-s);
  border: 1px solid transparent;
  white-space: nowrap;
}
.sitenav-link:hover {
  color: var(--text-primary);
  border-color: var(--border);
}
.sitenav-link[aria-current="page"] {
  color: var(--text-primary);
  font-weight: 500;
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* The burger. Hidden here and shown at the mobile breakpoint — two links need no
   menu when there's room for them. */
.nav-toggle {
  display: none;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  color: var(--text-secondary);
  width: 34px;
  height: 34px;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
}
.nav-toggle:hover {
  border-color: var(--accent);
  color: var(--text-primary);
}
.nav-toggle svg {
  width: 16px;
  height: 16px;
}
.nav-toggle[aria-expanded="true"] {
  border-color: var(--accent);
  color: var(--text-primary);
}

/* The connector button's label has two lengths; the short one only appears on
   mobile. See the comment on the button in prime-ai.html for why neither span is
   aria-hidden. */
.mcp-btn-short {
  display: none;
}

.masthead-meta {
  display: flex;
  align-items: center;
  gap: 14px;
}

.stamp {
  margin: 0;
  font-size: 12.5px;
  color: var(--text-muted);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
}

.ghost-btn {
  font: inherit;
  font-size: 13px;
  color: var(--text-secondary);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  padding: 6px 12px;
  cursor: pointer;
}
.ghost-btn:hover {
  border-color: var(--accent);
  color: var(--text-primary);
}
.ghost-btn svg {
  width: 13px;
  height: 13px;
  vertical-align: -2px;
  margin-right: 5px;
}

/* ── Panels ───────────────────────────────────────────────────────────── */

.panel {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-l);
  padding: 24px;
  margin: 20px 0;
  box-shadow: var(--shadow);
}

.panel-head {
  margin-bottom: 18px;
}

.panel-head-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

.share-btn {
  flex-shrink: 0;
  white-space: nowrap;
}
.share-btn[disabled] {
  opacity: 0.6;
  cursor: default;
}

/* globalprime.com's `.caption`: lime, uppercase, 3px tracking, weight 500. */
.panel h2 {
  margin: 0;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--brand-lime);
}

.panel-note {
  margin: 6px 0 0;
  font-size: 13px;
  color: var(--text-muted);
}

/* Wraps, because the number of lenses is not fixed: a module can register a fourth
   (see registerLens in app.js), and on a 390px screen four chips in a row pushed the
   Share button beside them clean off the page. `.chips` already wraps for the same
   reason — a control group whose membership can grow has to. */
.lens-toggle {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 10px;
}

/* And the column holding them has to be allowed to shrink: a flex item defaults to
   min-content, which for a row of chips is the whole row, wrap or no wrap. */
.panel-head-row > div {
  min-width: 0;
}

/* Shown when a large share of the universe has no 24h move — normal outside
   market hours, and otherwise indistinguishable from a broken pipeline. */
.closed-note {
  display: inline-block;
  margin-top: 8px;
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  background: var(--surface-2);
}

/* ── Hero / pulse ─────────────────────────────────────────────────────── */

.hero {
  margin-top: 24px;
}

.hero-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.hero-text {
  margin: 0 0 22px;
  font-size: 25px;
  line-height: 1.3;
  font-weight: 500;
  letter-spacing: 0;
  max-width: 70ch;
}

.skeleton-text {
  color: var(--text-muted);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 6px 13px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-primary);
  white-space: nowrap;
}
.badge-icon {
  font-size: 9px;
  line-height: 1;
}
.badge-on .badge-icon {
  color: var(--status-good);
}
.badge-off .badge-icon {
  color: var(--status-critical);
}
.badge-mixed .badge-icon {
  color: var(--status-warning);
}

.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px;
  margin: 0;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  overflow: hidden;
}

.stat {
  background: var(--surface-1);
  padding: 14px 17px;
}

.stat dt {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: 4px;
}

/* The word under a stat's number — a banded reading of the figure above it, never a
   substitute for it. `tone` is a second signal beside the wording, not instead of it. */
.stat-band {
  margin: 3px 0 0;
  font-size: 11.5px;
  color: var(--text-muted);
}
.stat-band[data-tone="warn"] {
  color: var(--accent);
}

.stat dd {
  margin: 0;
  font-size: 25px;
  font-weight: 500;
  letter-spacing: -0.02em;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
}

/* ── Health strip ─────────────────────────────────────────────────────── */

/* Sits below the footer: a diagnostic, read when something looks wrong rather
   than announced on arrival. Muted to match — the accent stripe marks it without
   competing with the market data above it. */
.health {
  border: 1px solid var(--border);
  border-left: 3px solid var(--status-serious);
  background: var(--surface-1);
  border-radius: var(--radius-s);
  padding: 13px 17px;
  margin: 4px 0 24px;
  font-size: 12.5px;
  color: var(--text-muted);
}
.health strong {
  color: var(--text-primary);
  font-weight: 500;
}
.health ul {
  margin: 7px 0 0;
  padding-left: 18px;
}

/* ── Momentum ─────────────────────────────────────────────────────────── */

/* Desktop: chart 50%, each column 25% — the proportions of the TraderX Pulse
   momentum widget. Below 900px it collapses to the app's mobile arrangement:
   chart on top, the two columns side by side underneath. */
.momentum-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 26px;
  /* stretch, so the chart fills the widget's height rather than floating in
     dead space beside two much taller lists. */
  align-items: stretch;
}

@media (max-width: 900px) {
  .momentum-grid {
    grid-template-columns: 1fr 1fr;
  }
  /* Chart spans both columns and leads, exactly as in the app. */
  .momentum-chart {
    grid-column: 1 / -1;
  }
}

.momentum-chart {
  margin: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.chart-legend {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 10px;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* A 2px bar, matching the stroke weight of the line it identifies. */
.legend-swatch {
  width: 14px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}

.chart-scale {
  margin-left: auto;
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: 0;
  font-size: 11.5px;
}

.plot {
  position: relative;
  width: 100%;
  /* Grows to whatever height the row gives it; the floor keeps the chart
     readable when the mover lists are short. */
  flex: 1;
  min-height: 190px;
}

.plot svg {
  display: block;
  width: 100%;
  height: auto;
  /* Clipped, not visible: the end labels sit inside the viewBox's right padding,
     and anything that did escape would land in the Upside column beside it. */
  overflow: hidden;
}

/* Widened hit area so a 2px line is reachable without pixel-hunting. */
.plot .hit {
  stroke: transparent;
  stroke-width: 12;
  fill: none;
  cursor: pointer;
}
.plot .series {
  transition: opacity 0.12s ease;
}
.plot.is-hovering .series {
  opacity: 0.28;
}
.plot.is-hovering .series.is-active {
  opacity: 1;
}

.plot-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 150px;
  padding: 0 12px;
  text-align: center;
  font-size: 12.5px;
  color: var(--text-muted);
  border: 1px dashed var(--border);
  border-radius: var(--radius-s);
}

.end-label {
  font-family: var(--font-num);
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
}

.col-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 11.5px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.delta-up {
  color: var(--delta-up);
}
.delta-none {
  /* No direction to signal, so neither the up/down colour nor an arrow applies.
     Muted rather than red: an absent reading is not a fall. */
  color: var(--text-muted);
}
.delta-down {
  color: var(--delta-down);
}
.delta-flat {
  color: var(--text-muted);
}

.mover-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.mover {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 4px 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--gridline);
}
.mover:last-child {
  border-bottom: 0;
}

.mover-name {
  min-width: 0;
}
.mover-ticker {
  font-weight: 500;
  font-size: 13.5px;
}
.mover-full {
  display: block;
  font-size: 12px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mover-change,
.mover-price {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.mover-change {
  font-weight: 500;
  font-size: 13.5px;
}
/* Price sits under the change so a 25%-wide column never has to compress the
   instrument name to fit three things on one line. */
.mover-price {
  grid-column: 2;
  font-size: 11.5px;
  color: var(--text-muted);
}

/* ── Intel ────────────────────────────────────────────────────────────── */

/* 215px lets all five themes sit on one row at full width — a 4+1 wrap leaves an
   orphan card that reads as a sixth, missing theme. */
.intel-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
  gap: 14px;
}

/* A colour per status, from the ramp in :root — the lime kept for "Accelerating"
   and the rest stepping away from it in hue and lightness together.

   The original version used one lime for every card, on the grounds that the status
   word is already on screen so colour encoded nothing new. That reasoning still
   holds, and it's why the colour goes on the *marks* — the top border and the pill's
   rule and dot — while the label itself stays in a text token. At 10.5px uppercase,
   coloured type would be the one part of this that has to clear 4.5:1, and it buys
   nothing the mark beside it doesn't already say.

   The other half of that original note is still binding: no red, and no second green.
   A red border above a table where red means "down" is a real misreading, so "Fading"
   goes grey instead. */
.intel-card {
  --intel-accent: var(--intel-active); /* the generator's own default status */
  border: 1px solid var(--border);
  border-top: 3px solid var(--intel-accent);
  border-radius: var(--radius-s);
  padding: 15px 17px 17px;
  background: var(--surface-2);
}

.intel-card[data-status="accelerating"] {
  --intel-accent: var(--intel-accelerating);
}
.intel-card[data-status="building"] {
  --intel-accent: var(--intel-building);
}
.intel-card[data-status="emerging"] {
  --intel-accent: var(--intel-emerging);
}
.intel-card[data-status="elevated"] {
  --intel-accent: var(--intel-elevated);
}
.intel-card[data-status="active"] {
  --intel-accent: var(--intel-active);
}
.intel-card[data-status="shifting"] {
  --intel-accent: var(--intel-shifting);
}
.intel-card[data-status="fading"] {
  --intel-accent: var(--intel-fading);
}

.intel-card h3 {
  margin: 0 0 9px;
  font-size: 15.5px;
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1.4;
}

/* The pill picks up the same colour as a rule and a dot; the word stays in ink. */
.intel-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 10.5px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
  border: 1px solid var(--intel-accent);
  border-radius: 999px;
  padding: 3px 10px;
  margin-bottom: 10px;
}

/* 6px dot — the mark that carries the colour next to type that doesn't. Drawn with a
   pseudo-element rather than a bullet character because Delight has no geometric
   glyphs (see docs/BRAND.md), so "●" would fall through to whatever the fallback
   stack happens to provide. */
.intel-status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--intel-accent);
  flex-shrink: 0;
}

.intel-card p {
  margin: 0 0 11px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-secondary);
}

.intel-markets {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.market-tag {
  font-size: 11.5px;
  color: var(--text-muted);
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  padding: 3px 8px;
}

/* ── Controls ─────────────────────────────────────────────────────────── */

.controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 15px;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}

.chip {
  font: inherit;
  font-size: 12.5px;
  color: var(--text-secondary);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 14px;
  cursor: pointer;
}
.chip:hover {
  border-color: var(--accent);
  color: var(--text-primary);
}
/* aria-selected covers the dialog's tablist; aria-pressed the momentum and filter
   toggles. Same affordance, same treatment. */
.chip[aria-pressed="true"],
.chip[aria-selected="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 500;
}

/* The instrument search, and the same control wherever a module needs one. */
#search,
.text-filter {
  font: inherit;
  font-size: 13px;
  padding: 7px 13px;
  min-width: 190px;
  color: var(--text-primary);
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
}
#search::placeholder,
.text-filter::placeholder {
  color: var(--text-muted);
}

/* ── Table ────────────────────────────────────────────────────────────── */

/* Wide content scrolls inside its own container so the page body never
   scrolls horizontally. */
/* Sideways scrolling only when the columns need it, so that on every other width the
   wrapper is not a scrollport and the sticky header below can actually pin. See
   sticky-table.js — this was declared sticky for a long time without ever sticking. */
.table-scroll {
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
}
.table-scroll.is-scrollable {
  overflow-x: auto;
}

.pulse-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
  min-width: 900px;
}

.pulse-table thead th {
  text-align: left;
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  padding: 11px 14px;
  border-bottom: 1px solid var(--baseline);
  background: var(--surface-1);
  position: sticky;
  /* Below the masthead, which is sticky itself. Measured, not assumed — it wraps to
     two rows on a narrow screen. */
  top: var(--sticky-top, 0px);
  z-index: 2;
  white-space: nowrap;
}

.pulse-table th.num,
.pulse-table td.num {
  text-align: right;
}
.pulse-table td.num {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
}

.sortable {
  cursor: pointer;
  user-select: none;
}
.sortable:hover {
  color: var(--text-primary);
}
.sortable[aria-sort]::after {
  content: " ↓";
  color: var(--accent);
}
.sortable[aria-sort="ascending"]::after {
  content: " ↑";
}

.pulse-table tbody tr {
  border-bottom: 1px solid var(--gridline);
}
.pulse-table tbody tr:last-child {
  border-bottom: 0;
}
.pulse-table tbody tr:hover {
  background: var(--surface-2);
}

.pulse-table td {
  padding: 11px 14px;
  vertical-align: middle;
}

.inst-ticker {
  font-weight: 500;
  white-space: nowrap;
}
.inst-name {
  display: block;
  font-size: 12px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 210px;
}

.class-tag {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  padding: 3px 8px;
  white-space: nowrap;
}

.narrative-col {
  min-width: 260px;
}
.narrative-cell {
  color: var(--text-secondary);
  font-size: 13px;
  max-width: 380px;
}

.spark {
  display: block;
  width: 92px;
  height: 26px;
  overflow: visible;
}
.spark-none {
  font-size: 11.5px;
  color: var(--text-muted);
}

.empty-note {
  margin: 16px 0 0;
  font-size: 13px;
  color: var(--text-muted);
  text-align: center;
}

.row-skeleton td {
  color: var(--text-muted);
  text-align: center;
  padding: 26px;
}

/* ── Tooltip ──────────────────────────────────────────────────────────── */

.tooltip {
  position: fixed;
  z-index: 30;
  pointer-events: none;
  opacity: 0;
  transform: translateY(3px);
  transition: opacity 0.1s ease, transform 0.1s ease;
  background: var(--surface-2);
  color: var(--text-primary);
  border: 1px solid var(--baseline);
  border-radius: var(--radius-s);
  padding: 10px 13px;
  font-size: 12.5px;
  line-height: 1.5;
  box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.5);
  max-width: 280px;
}
.tooltip[data-show="true"] {
  opacity: 1;
  transform: translateY(0);
}
.tooltip dl {
  display: grid;
  grid-template-columns: auto auto;
  gap: 2px 14px;
  margin: 0;
}
.tooltip dt {
  color: var(--text-muted);
}
.tooltip dd {
  margin: 0;
  text-align: right;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
}
.tooltip .tip-title {
  font-weight: 500;
  margin: 0 0 6px;
}

/* ══ Prime AI ═══════════════════════════════════════════════════════════════

   The chat page. One column, narrower than the research grid: a transcript is
   prose, and prose at 1240px is unreadable regardless of how much room the
   window has.

   The thread scrolls inside itself rather than growing the page, so the
   composer never leaves the viewport — the alternative (page-length transcript,
   input at the bottom) means scrolling back down to type after every answer.
   ══════════════════════════════════════════════════════════════════════════ */

.prime-ai-main {
  max-width: 940px;
}

.prime-ai-panel {
  display: flex;
  flex-direction: column;
}

.thread {
  display: flex;
  flex-direction: column;
  gap: 20px;
  height: min(58vh, 620px);
  overflow-y: auto;
  /* Keeps a wheel over the transcript from scrolling the page once it bottoms out. */
  overscroll-behavior: contain;
  padding: 4px 2px 8px;
  border-bottom: 1px solid var(--border);
}

.msg {
  max-width: 100%;
}

/* The reader's own words get the bubble; the answer gets the full column, because
   it may carry a chart and a chart in a bubble is a chart in a box in a box. */
.msg-user {
  align-self: flex-end;
  max-width: 82%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  padding: 12px 15px;
}

.msg-who {
  margin: 0 0 5px;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

.msg-body p {
  margin: 0 0 10px;
  color: var(--text-secondary);
  max-width: 78ch;
}
.msg-body p:last-child {
  margin-bottom: 0;
}
.msg-body strong {
  color: var(--text-primary);
  font-weight: 500;
}
.msg-body ul,
.msg-body ol {
  margin: 0 0 10px;
  padding-left: 20px;
  color: var(--text-secondary);
  max-width: 78ch;
}
.msg-body li {
  margin-bottom: 4px;
}
.msg-body code {
  font-family: var(--font-num);
  font-size: 0.92em;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
}

.msg-intro .msg-body p {
  color: var(--text-secondary);
}

.msg-error .msg-body p,
.msg-error p {
  color: var(--status-critical);
  margin: 0 0 8px;
}
.msg-hint {
  font-size: 12.5px;
  color: var(--text-muted) !important;
}
.msg-hint a,
.footer a {
  color: var(--text-secondary);
}

.msg-chart-error {
  font-size: 12.5px;
  color: var(--text-muted);
}

/* Waiting state. The dots animate; the reduced-motion rule at the foot of this
   file stops them for anyone who asked it to. */
.msg-thinking {
  margin: 0;
  font-size: 13px;
  color: var(--text-muted);
}
.dots {
  display: inline-flex;
  gap: 3px;
  margin-right: 6px;
  vertical-align: 1px;
}
.dots i {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  animation: dot 1.1s ease-in-out infinite;
}
.dots i:nth-child(2) {
  animation-delay: 0.15s;
}
.dots i:nth-child(3) {
  animation-delay: 0.3s;
}
@keyframes dot {
  0%,
  100% {
    opacity: 0.25;
  }
  50% {
    opacity: 1;
  }
}

/* Which tools answered. Collapsed by default: it's provenance, not the answer —
   but it is the thing that makes "read live from the feed" checkable rather than
   a claim. */
.msg-tools {
  margin-top: 12px;
  font-size: 12.5px;
  color: var(--text-muted);
}
.msg-tools summary {
  cursor: pointer;
  color: var(--text-muted);
}
.msg-tools summary:hover {
  color: var(--text-secondary);
}
.msg-tools ul {
  margin: 8px 0 4px;
  padding-left: 18px;
}
.msg-tools code {
  font-family: var(--font-num);
  font-size: 11.5px;
  color: var(--text-secondary);
}
.msg-tools-foot {
  margin: 0;
  font-size: 11.5px;
  color: var(--text-muted);
}

/* Starter questions. They scroll horizontally on a phone rather than stacking
   into six rows above the input. */
.starters {
  display: flex;
  gap: 8px;
  padding: 14px 0 12px;
  overflow-x: auto;
  scrollbar-width: thin;
}
.starters .chip {
  white-space: nowrap;
  flex-shrink: 0;
}

.composer {
  display: flex;
  align-items: flex-end;
  gap: 10px;
}

#question {
  flex: 1;
  font: inherit;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  padding: 11px 14px;
  resize: none;
  overflow-y: auto;
  max-height: 160px;
}
#question::placeholder {
  color: var(--text-muted);
}
#question:disabled {
  opacity: 0.6;
}

.send-btn {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  background: var(--accent);
  color: var(--accent-ink);
  border: 1px solid var(--accent);
  border-radius: var(--radius-m);
  cursor: pointer;
}
.send-btn svg {
  width: 16px;
  height: 16px;
}
.send-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.composer-note {
  margin: 9px 0 0;
  font-size: 11.5px;
  color: var(--text-muted);
}

/* ══ Prime AI charts ════════════════════════════════════════════════════════

   Rendered by public/chart.js. Marks are thin, grid and axes recessive, and
   every value is also available in the table view — see the module header for
   the constraints these styles serve.
   ══════════════════════════════════════════════════════════════════════════ */

.gp-chart {
  margin: 16px 0 4px;
  padding: 16px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
}

.chart-head {
  margin-bottom: 10px;
}
.chart-title {
  margin: 0;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0;
}
.chart-subtitle {
  margin: 3px 0 0;
  font-size: 12.5px;
  color: var(--text-muted);
}

/* A legend for two or more series, always — identity never rests on colour
   matching alone. One series has none: the title names it. */
.chart-keys {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin: 0 0 10px;
  padding: 0;
  list-style: none;
  font-size: 12px;
  color: var(--text-secondary);
}
.chart-keys li {
  display: flex;
  align-items: center;
  gap: 6px;
}
/* 2px, matching the stroke weight of the line it identifies. */
.chart-key-swatch {
  width: 14px;
  height: 2px;
  border-radius: 1px;
  flex-shrink: 0;
}

.chart-axis-label {
  margin: 0 0 6px;
  font-size: 11.5px;
  color: var(--text-muted);
}

/* Height includes the x-axis band, so the axis labels are never cut off and the
   card never grows a nested scrollbar. */
.chart-plot {
  width: 100%;
  height: 250px;
}
.chart-plot svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.gp-chart-bar .chart-plot svg {
  height: auto;
}

/* Text wears text tokens, never the series colour: a light categorical hue is
   illegible as type on this surface. Identity comes from the mark beside it. */
.chart-tick,
.chart-cat-label,
.chart-value-label,
.chart-end-label {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  fill: var(--text-muted);
  font-size: 10.5px;
}
.chart-cat-label,
.chart-value-label {
  fill: var(--text-secondary);
  font-size: 11.5px;
}
.chart-end-label {
  fill: var(--text-secondary);
  font-family: var(--font);
  font-variant-numeric: normal;
  font-size: 11px;
}

.chart-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  margin-top: 10px;
}
.chart-source,
.chart-note {
  margin: 0;
  font-size: 11.5px;
  color: var(--text-muted);
}
.chart-toggle {
  font: inherit;
  font-size: 11.5px;
  color: var(--text-muted);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border);
  padding: 0 0 1px;
  cursor: pointer;
  margin-left: auto;
}
.chart-toggle:hover {
  color: var(--text-secondary);
  border-color: var(--accent);
}

/* The table view: a parallel rendering of the same numbers, which is what lets the
   chart itself stay sparse (labels on the ends only). */
.chart-table-wrap {
  margin-top: 12px;
  max-height: 260px;
  overflow: auto;
}
.chart-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.chart-table caption {
  text-align: left;
  font-size: 11.5px;
  color: var(--text-muted);
  padding-bottom: 6px;
}
.chart-table th,
.chart-table td {
  text-align: left;
  padding: 5px 10px 5px 0;
  border-bottom: 1px solid var(--gridline);
  white-space: nowrap;
}
.chart-table thead th {
  position: sticky;
  top: 0;
  background: var(--surface-2);
  color: var(--text-muted);
  font-weight: 400;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.chart-table tbody th {
  color: var(--text-muted);
  font-weight: 400;
}
.chart-table .num {
  text-align: right;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.tip-swatch {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  margin-right: 6px;
  vertical-align: 0;
}

/* ══ "Add GP MCP To Your AI" dialog ════════════════════════════════════════

   A native <dialog>: Esc, focus trapping, the backdrop and inert on the rest of
   the page all come for free and all come correct.
   ══════════════════════════════════════════════════════════════════════════ */

.mcp-btn span {
  white-space: nowrap;
}

.mcp-dialog {
  width: min(640px, calc(100vw - 32px));
  max-height: min(86vh, 860px);
  overflow-y: auto;
  background: var(--surface-1);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-l);
  padding: 24px;
  font-family: var(--font);
  font-size: 14px;
}
.mcp-dialog::backdrop {
  background: rgba(0, 0, 0, 0.6);
}

.mcp-dialog-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
.mcp-dialog h2 {
  margin: 0;
  font-size: 19px;
  font-weight: 500;
  letter-spacing: 0;
}
.mcp-close {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  color: var(--text-secondary);
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
}
.mcp-close:hover {
  border-color: var(--accent);
  color: var(--text-primary);
}
.mcp-close svg {
  width: 14px;
  height: 14px;
}

.mcp-intro {
  margin: 12px 0 18px;
  font-size: 13.5px;
  color: var(--text-secondary);
}

.mcp-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

.mcp-steps {
  margin: 0 0 14px;
  padding-left: 20px;
  font-size: 13.5px;
  color: var(--text-secondary);
}
.mcp-steps li {
  margin-bottom: 6px;
}
.mcp-steps strong {
  color: var(--text-primary);
  font-weight: 500;
}
.mcp-steps code,
.mcp-value {
  font-family: var(--font-num);
  font-size: 12.5px;
}

.mcp-field {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  padding: 10px 12px;
}
.mcp-field-block {
  align-items: flex-start;
}
.mcp-value {
  flex: 1;
  min-width: 0;
  margin: 0;
  color: var(--text-primary);
  overflow-x: auto;
  white-space: pre;
}

.copy-btn {
  flex-shrink: 0;
  font: inherit;
  font-size: 12px;
  color: var(--accent-ink);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius-s);
  padding: 4px 12px;
  cursor: pointer;
  font-weight: 500;
}

.mcp-note {
  margin: 10px 0 0;
  font-size: 12.5px;
  color: var(--text-muted);
}

.mcp-asks {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.mcp-asks h3 {
  margin: 0 0 8px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--accent);
  font-weight: 500;
}
.mcp-asks ul {
  margin: 0;
  padding-left: 20px;
  font-size: 13px;
  color: var(--text-secondary);
}
.mcp-asks li {
  margin-bottom: 4px;
}

.mcp-foot {
  margin: 18px 0 0;
  font-size: 12px;
  color: var(--text-muted);
}

/* ── Footer ───────────────────────────────────────────────────────────── */

.footer {
  margin: 32px 0 44px;
  padding-top: 22px;
  border-top: 1px solid var(--border);
  font-size: 12.5px;
  color: var(--text-muted);
}
.footer p {
  margin: 0 0 8px;
  max-width: 90ch;
}
.footer strong {
  color: var(--text-secondary);
  font-weight: 500;
}

/* ── Advanced mode ────────────────────────────────────────────────────────
   The staging surface: the way in, the banner that says where you are, and the
   badge on a panel that isn't finished. See public/advanced-gate.js for what the
   gate is and is not. */

/* Deliberately the quietest thing in the footer — muted, and underlined only on
   hover. It is a door for the team, not a call to action for a client. */
.advanced-link {
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 1px dotted var(--baseline);
}
.advanced-link:hover {
  color: var(--text-secondary);
  border-bottom-color: var(--text-muted);
}

/* Above the masthead and impossible to miss, because the whole risk of a staging
   copy is a screenshot of it being taken for the product. */
.stage-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  padding: 10px 24px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--accent);
  font-size: 12.5px;
  color: var(--text-secondary);
}
.stage-banner p {
  margin: 0;
  max-width: 90ch;
}
.stage-banner strong {
  color: var(--accent);
  font-weight: 500;
}

/* Only ever rendered behind the gate: on the public site a promoted module is just
   part of the product, and a badge about our release process would be noise. */
.stage-badge {
  align-self: flex-start;
  flex-shrink: 0;
  font-family: var(--font-num);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--baseline);
  color: var(--text-muted);
}
.stage-badge[data-stage="advanced"] {
  border-color: var(--status-warning);
  color: var(--status-warning);
}
.stage-badge[data-stage="live"] {
  border-color: var(--delta-up);
  color: var(--delta-up);
}

/* Nothing else is needed for its placement: a module panel's head uses the existing
   .panel-head-row, the same layout the momentum panel uses for its Share button, so
   the badge sits opposite the title exactly as that control does. */

/* ── The password dialog ──────────────────────────────────────────────────
   Shares .mcp-dialog's box so the two dialogs on the site are the same object. */

.gate-dialog {
  width: min(440px, calc(100vw - 32px));
}
.gate-form {
  margin-top: 4px;
}
.gate-label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--text-muted);
  margin-bottom: 8px;
}
.gate-input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-num);
  font-size: 13px;
  color: var(--text-primary);
  background: transparent;
  border: 0;
  outline: none;
  padding: 2px 0;
}
/* The field's border is the focus indicator, because the input itself has none —
   an outline inside the .mcp-field box would draw a second rectangle. */
.mcp-field:focus-within {
  border-color: var(--accent);
}
.gate-error {
  margin: 10px 0 0;
  min-height: 1.2em;
  font-size: 12.5px;
  color: var(--delta-down);
}

/* The plot area before its data arrives.
   The frame around it is already the real one — same title, axis label and foot, built
   by the same code — so when the marks land the card doesn't move. This is the only
   part that changes, which is what stops each widget popping the page around as it
   loads. Deliberately faint: it reserves space and says "not yet", and should not read
   as content. The reduced-motion rule at the foot of this file stops the pulse. */
.chart-skeleton {
  height: 100%;
  min-height: 120px;
  border-radius: var(--radius-s);
  background: linear-gradient(
    90deg,
    var(--surface-2) 0%,
    var(--border) 40%,
    var(--surface-2) 80%
  );
  background-size: 300% 100%;
  animation: chart-skeleton-sweep 1.6s ease-in-out infinite;
  opacity: 0.5;
}
@keyframes chart-skeleton-sweep {
  from {
    background-position: 150% 0;
  }
  to {
    background-position: -50% 0;
  }
}
/* The loading foot carries a hidden stand-in for the "Show values" button rather than
   a guessed min-height — see chartSkeleton. Nothing to declare here. */

/* ── Section pages ────────────────────────────────────────────────────────
   Generated from index.html, so they inherit every rule above; all that is needed
   is the rhythm between the panels the registry mounts. */

#section-panels > .panel + .panel {
  margin-top: 20px;
}
.module-charts {
  display: grid;
  gap: 24px;
}
/* A grid item's default `min-width: auto` is its min-content width, so a wide table
   inside one pushes the grid — and therefore the page — wider than the viewport
   instead of scrolling inside its own scroller. */
.module-charts > * {
  min-width: 0;
}
/* A module's own table sits under its charts, not beside them: at panel width a
   two-column arrangement squeezes the chart into uselessness. */
.module-body > .chart-table-wrap {
  margin-top: 4px;
}

/* ── The cost table ───────────────────────────────────────────────────────
   A whole tradable universe in one sortable table.

   It grows to its content: one page scroll, however many instruments load, the
   same as the instrument table on the Research page (`.table-scroll`). A capped
   scroller would keep the card a fixed size, but it buys that with a scrollbox
   inside a scrolling page — and a reader who scrolls past the card never sees the
   rows the cap hid.

   The comparison the bar charts used to carry is drawn inside the cells — a bar
   anchored to the left of the cell, scaled to the widest value on screen. That is
   one row per instrument instead of one chart row plus one table row. */

.cost-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 12px;
}
.cost-controls .chips {
  flex: 1 1 auto;
}
/* A labelled control in a module's toolbar — the correlation picker, and whatever
   else needs one. The label sits above so the control keeps its full width on a
   phone rather than being squeezed beside its own name. */
.picker-label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}
.picker-label .text-filter {
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-primary);
}

.cost-count {
  font-size: 12px;
  color: var(--text-muted);
  white-space: nowrap;
  margin: 0;
}

/* No height: the page carries the length. And no overflow until the columns
   genuinely need it — see measureChrome() in friction.js for why that is what lets
   the header pin. */
.cost-table-wrap {
  border: 1px solid var(--gridline);
  border-radius: var(--radius-s);
}
.cost-table-wrap.is-scrollable {
  overflow-x: auto;
}
.cost-caption {
  font-size: 11.5px;
  color: var(--text-muted);
  margin: 0 0 6px;
}
/* Below the masthead, which is sticky itself. The offset is measured, not assumed —
   the masthead wraps to two rows on a narrow screen. */
.cost-table thead th {
  top: var(--sticky-top, 0px);
}
.cost-table {
  font-size: 12.5px;
}
.cost-table th,
.cost-table td {
  padding: 7px 12px;
}
/* The instrument name stays put when the columns scroll sideways — on a phone the
   numeric columns are wider than the screen, and a row of figures with no name on it
   is a row you cannot read. It needs its own background to scroll under, and that
   background has to follow the row's hover or the name detaches from its row. */
.cost-table tbody th,
.cost-table thead th:first-child {
  position: sticky;
  left: 0;
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cost-table tbody th {
  color: var(--text-secondary);
  background: var(--surface-1);
  z-index: 1;
}
/* The corner cell is sticky in both axes, so it has to sit above the row it pins
   beside and above the header row it pins under. */
.cost-table thead th {
  z-index: 2;
}
.cost-table thead th:first-child {
  z-index: 3;
}
.cost-table tbody tr:hover th,
.cost-table tbody tr:hover td {
  background: var(--surface-2);
}

/* The header cell keeps the sticky background and the sort arrow; the button inside
   it is what the keyboard reaches. */
.cost-table thead th button {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.cost-table thead th.num button {
  width: 100%;
  text-align: right;
}
.cost-table thead th button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.cost-table td.has-bar {
  position: relative;
}
/* Anchored left, with the number right-aligned over it: a short bar is then a stub
   against a shared left edge rather than a sliver hiding in the right-hand padding,
   and lengths compare the way they do on the chart above. It also gives the bars the
   same zero to grow out of that every chart on this site has. */
.cost-table .cell-bar {
  position: absolute;
  left: 0;
  top: 5px;
  bottom: 5px;
  background: var(--accent);
  opacity: 0.2;
  border-radius: 2px;
  transform-origin: 0 50%;
  pointer-events: none;
}
.cost-table .cell-value {
  position: relative;
}

/* An absent ratio, named. Never a blank cell and never a zero. */
.cost-table .cell-flag {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
  padding: 2px 6px;
  white-space: nowrap;
}

@media (max-width: 620px) {
  /* The ticker identifies the row; the full name is the title attribute and the
     search box. Half a phone's width is too much to spend on "Instrument number 41". */
  .cost-table .row-name {
    display: none;
  }
  .cost-table th,
  .cost-table td {
    padding: 7px 9px;
  }
}

.cost-table .cost-empty {
  padding: 18px 12px;
  color: var(--text-muted);
  text-align: center;
}

.cell-ghost {
  display: block;
  height: 9px;
}
.cell-ghost,
.chart-key-ghost {
  border-radius: 3px;
  background: linear-gradient(
    90deg,
    var(--surface-2) 0%,
    var(--border) 40%,
    var(--surface-2) 80%
  );
  background-size: 300% 100%;
  animation: chart-skeleton-sweep 1.6s ease-in-out infinite;
  opacity: 0.5;
}
@media (prefers-reduced-motion: reduce) {
  .cell-ghost,
  .chart-key-ghost {
    animation: none;
  }
}

@media (max-width: 620px) {
  .hero-text {
    font-size: 19px;
  }
  .panel {
    padding: 18px 16px;
  }
  .stat dd {
    font-size: 21px;
  }
  .brand-sub,
  .brand-divider {
    display: none;
  }

  /* Below this width the masthead is down to ~350px and losing the argument with
     the logo, so the nav collapses behind the burger and drops out of the flow.
     The panel hangs off .masthead, which is `position: sticky` and therefore
     already a positioning context. */
  /* Pinned to the top-right corner rather than left in the flex flow, where the
     stamp or the connector button pushes it onto a second row and it lands in a
     different place on each page. Taken out of flow and anchored to the content
     column — `top`/`right` match .masthead-inner's own padding, so it lines up with
     the logo opposite it — with .masthead-meta reserving the space it vacated. */
  .masthead-inner {
    position: relative;
  }
  .nav-toggle {
    display: grid;
    position: absolute;
    top: 15px;
    right: 20px;
    z-index: 1;
  }
  .mcp-btn-full {
    display: none;
  }
  .mcp-btn-short {
    display: inline;
  }

  .sitenav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin: 0;
    padding: 6px;
    background: var(--surface-1);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.6);
  }
  .sitenav[data-open="true"] {
    display: flex;
  }
  /* Full-width rows with a 44px tap target — a 13px inline link is a thumb-sized
     miss on a phone. */
  .sitenav-link {
    padding: 12px 14px;
    min-height: 44px;
    display: flex;
    align-items: center;
    font-size: 15px;
    border-radius: var(--radius-s);
  }
  .sitenav-link[aria-current="page"] {
    /* The desktop underline reads as a stray rule on a stacked row; a lime bar down
       the leading edge is the same signal in a shape that suits the layout. */
    box-shadow: inset 2px 0 0 var(--accent);
    background: var(--surface-2);
  }

  .masthead-meta {
    gap: 8px;
    /* Room for the pinned burger, so nothing sharing its row slides underneath. */
    padding-right: 44px;
  }
  /* On the Research page the stamp is the freshness of the data — it stays. On the
     the Prime AI page it names the MCP endpoint, which is context, not information the
     reader needs on a 390px masthead. */
  .page-prime-ai .stamp {
    display: none;
  }
  .thread {
    height: 62vh;
  }
  .msg-user {
    max-width: 92%;
  }
  .chart-plot {
    height: 220px;
  }
  .mcp-dialog {
    padding: 18px 16px;
  }
  /* The banner keeps the content column's gutter rather than the masthead's. */
  .stage-banner {
    padding: 10px 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    animation: none !important;
  }
}
