/* Factor Engine — refined minimalism, token-based theming, bright default
   (owner preference 2026-07-11). */

:root {
  --font-display: 'Fraunces', ui-serif, Georgia, serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ---- Bright (default) ---- */
  --bg: #FAFAF8;
  --bg-alt: #F2F1ED;
  --ink: #1A1A1A;
  --ink-soft: #555;
  --ink-faint: #888;
  --rule: #E5E3DE;
  --accent: #2A5D61;      /* teal — positive */
  --accent-rgb: 42, 93, 97;
  --accent-contrast: #FFFFFF; /* text on accent-filled surfaces (selected chips,
                                 primary buttons) — white on the dark teal;
                                 dark theme overrides to near-black since its
                                 accent is light (~7:1 contrast both ways) */
  --warn: #B0523E;        /* coral — negative */
  --warn-rgb: 176, 82, 62;
  --heat-0: #F5F3EE;
  --heat-5: #9EB8A4;
  /* Categorical series palette. Re-ordered 2026-07-26 — the HUES ARE
     UNCHANGED, only their slot assignment. The previous order FAILED the
     dataviz validator's normal-vision floor in both themes (light: orange
     #EB6834 next to magenta #E87BA4 at ΔE 12.9, below the 15 floor; dark was
     worse, red #E66767 next to magenta #D55181 at ΔE 7.8 — two adjacent
     series most people cannot tell apart at all). Only ADJACENT pairs are
     gated, so a permutation was the whole fix: no hue was redesigned.
       validator, this order: light and dark BOTH "ALL CHECKS PASS"
       light  worst adjacent  normal ΔE 19.6 · CVD ΔE 9.1
       dark   worst adjacent  normal ΔE 19.3 · CVD ΔE 8.4
     Light keeps a contrast "relief required" note (aqua/yellow/magenta land
     at 2.1-2.7:1 on this near-white surface) — discharged by the legend and
     peer table both views ship, which is exactly the relief the check asks
     for. Don't reshuffle without re-running the validator; the order IS the
     colorblind-safety mechanism, and light/dark MUST keep the same hue in the
     same slot or toggling the theme repaints every series. */
  --series-1: #2A78D6;   /* blue */
  --series-2: #008300;   /* green */
  --series-3: #E87BA4;   /* magenta */
  --series-4: #EDA100;   /* yellow */
  --series-5: #1BAF7A;   /* aqua */
  --series-6: #EB6834;   /* orange */
  --series-7: #4A3AA7;   /* violet */
  --series-8: #E34948;   /* red */
}

:root[data-theme="dark"] {
  --bg: #15171B;
  --bg-alt: #1D2025;
  --ink: #F1EFE9;
  --ink-soft: #B4B0A6;
  --ink-faint: #7B7870;
  --rule: #2B2E33;
  --accent: #57B3A9;
  --accent-rgb: 87, 179, 169;
  --accent-contrast: #10221f;
  --warn: #E3806A;
  --warn-rgb: 227, 128, 106;
  --heat-0: #23262B;
  --heat-5: #3F8A82;
  /* Same eight hues stepped for the dark surface, in the SAME slot order as
     light (see the light block's note) so a theme toggle never repaints a
     series. All checks pass here including contrast — every step clears 3:1
     on #15171B. */
  --series-1: #3987E5;   /* blue */
  --series-2: #008300;   /* green */
  --series-3: #D55181;   /* magenta */
  --series-4: #C98500;   /* yellow */
  --series-5: #199E70;   /* aqua */
  --series-6: #D95926;   /* orange */
  --series-7: #9085E9;   /* violet */
  --series-8: #E66767;   /* red */
}

/* ---------- <select> popup legibility (2026-09-23, owner report) ----------
   Every select in this app sets `background: transparent` so the CLOSED
   control inherits the panel. The OPEN popup is painted by the OS, not by
   that rule: it takes the element's own background/color, so on the dark
   theme the unselected <option>s rendered as near-black text on the dark
   list and were unreadable until highlighted (screenshot: the stock page's
   "RIGHT AXIS" metric picker). Options must therefore carry EXPLICIT
   background AND color -- inheriting either one is what breaks. Both themes
   are set on purpose: light needs it too, since a transparent option over a
   white popup is fine only by accident of the default. */
select { color: var(--ink); }
select option,
select optgroup {
  background-color: var(--bg);
  color: var(--ink);
}
:root[data-theme="dark"] select option,
:root[data-theme="dark"] select optgroup {
  background-color: var(--bg-alt);
  color: var(--ink);
}
/* A disabled option (placeholder rows like "-- pick a metric --") stays
   dimmer but must still clear the surface it sits on. */
select option:disabled { color: var(--ink-faint); }

/* No prefers-color-scheme auto-switch: the product default is bright
   regardless of OS setting. The header toggle (persisted in localStorage,
   see theme.js) is the only way to switch to :root[data-theme="dark"]. */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  transition: background-color 0.2s ease, color 0.2s ease;
}

/* ---------- Topbar ---------- */
.topbar {
  display: flex;
  align-items: baseline;
  gap: 24px;
  padding: 20px 40px 16px;
  border-bottom: 1px solid var(--rule);
}
.topbar .spacer { flex: 1; }
.brand {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
}
.crumbs {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.crumbs a { color: var(--ink-soft); text-decoration: none; }
.crumbs a:hover { color: var(--ink); }
.crumbs .sep { margin: 0 8px; color: var(--ink-faint); }

.theme-toggle {
  align-self: center;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--ink);
  width: 32px;
  height: 32px;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.theme-toggle:hover { border-color: var(--ink-faint); }

/* ---------- Main ---------- */
main {
  max-width: 1400px;
  margin: 0 auto;
  padding: 40px;
}
/* Screener opts out of the prose cap: it's a wide numeric table (up to 69
   columns on the "Full screen" chip) that scrolls horizontally inside its own
   .data-table-wrap, so a maximised wide monitor should give it the full
   window rather than wasting a third of the screen on side margins. Every
   other view keeps the 1400px cap deliberately — long text lines are
   unreadable at 2560px. Padding is untouched; only the width ceiling lifts.
   Driven by data-view, set in app.js's route() on every navigation. */
main[data-view="screen"] { max-width: none; }

h1 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 34px;
  letter-spacing: -0.02em;
  margin: 0 0 8px;
}
h2 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 20px;
  letter-spacing: -0.01em;
  margin: 32px 0 16px;
}
h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 16px;
  letter-spacing: -0.01em;
  margin: 24px 0 12px;
}
h2 .hint, h3 .hint, .identity-fact-label .hint {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 400;
  color: var(--ink-faint);
  text-transform: none;
  letter-spacing: 0;
}
.subtitle {
  color: var(--ink-soft);
  font-size: 15px;
  margin-bottom: 24px;
}
.ticker {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-faint);
  font-weight: 400;
}

.view-links {
  display: flex;
  /* Wrap unconditionally, not only under the ~480px block: at 768px Pulse's
     four-link row (incl. a long "Compare {sub-industry} constituents" label)
     overran by 38px, because flex-wrap was scoped to phones only. Wrapping is
     inert at widths where the row already fits. */
  flex-wrap: wrap;
  gap: 8px 20px;   /* row-gap first: wrapped links need breathing room, not 20px */
  font-family: var(--font-mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 8px;
}
.view-links a { color: var(--ink-soft); text-decoration: none; }
.view-links a:hover { color: var(--accent); }

/* ---------- Region picker ---------- */
.region-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
}
.region-tile-card {
  display: flex;
  flex-direction: column;
}
.region-tile-strat {
  margin-top: 6px;
  align-self: flex-end;
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
  text-decoration: none;
}
.region-tile-strat:hover { color: var(--accent); }
.region-tile {
  padding: 20px 22px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  cursor: pointer;
  transition: background-color 0.18s ease, border-color 0.18s ease;
  text-decoration: none;
  color: inherit;
  display: block;
}
.region-tile:hover {
  background: var(--bg-alt);
  border-color: var(--ink-faint);
}
.region-tile[aria-disabled="true"] {
  opacity: 0.4;
  cursor: not-allowed;
}
.region-tile .region {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  margin-bottom: 4px;
}
.region-tile .label {
  font-size: 18px;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.region-tile .meta {
  margin-top: 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
}

/* ---------- Loading / empty states ---------- */
.state {
  padding: 60px 0;
  text-align: center;
  color: var(--ink-faint);
  font-family: var(--font-mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ---------- Banners (loader errors / schema warnings) ---------- */
.fe-banners {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: 420px;
}
.fe-banner {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border-radius: 3px;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
.fe-banner--error { background: var(--warn); color: #1A1A1A; }
.fe-banner--warn { background: var(--series-3, #D9B85C); color: #1A1A1A; }
.fe-banner__close {
  background: transparent;
  border: none;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  color: inherit;
  opacity: 0.7;
}
.fe-banner__close:hover { opacity: 1; }
.fe-banner__retry {
  flex: 0 0 auto;
  background: rgba(26, 26, 26, 0.12);
  border: 1px solid rgba(26, 26, 26, 0.3);
  border-radius: 3px;
  padding: 3px 9px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  color: inherit;
  cursor: pointer;
}
.fe-banner__retry:hover { background: rgba(26, 26, 26, 0.2); }

/* ---------- Chips (view mode / column-set / metric pickers) ---------- */
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}
.chip {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 6px 12px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
}
.chip:hover { border-color: var(--ink-faint); color: var(--ink); }
.chip.active { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); }

.btn {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 7px 14px;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.btn:hover { border-color: var(--ink-faint); background: var(--bg-alt); }

/* ---------- Screener toolbar ---------- */
.toolbar {
  display: flex;
  gap: 16px;
  align-items: center;
  padding: 16px 0;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 0;
  flex-wrap: wrap;
}
.toolbar input, .toolbar select {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 6px 10px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--ink);
  outline: none;
}
.toolbar input:focus, .toolbar select:focus { border-color: var(--ink); }
.toolbar .spacer { flex: 1; }
.toolbar .count {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ---------- Data table ---------- */
.data-table-wrap {
  overflow-x: auto;
  max-height: 70vh;
  overflow-y: auto;
  border-bottom: 1px solid var(--rule);
}
/* Landing factor-regime matrix (Now heatmap) — same overflow-x:auto pattern
   as .data-table-wrap, but a bare ECharts canvas has no natural min-content
   width the way a nowrap table cell does, so .chart-box needs an explicit
   floor (set per-breakpoint below) for the wrapper to ever actually scroll
   instead of just shrinking the canvas into unreadable cell labels (fleet
   scout sweep 2, item 5: 13 index columns collided at true 420px width). */
.regime-matrix-scroll { overflow-x: auto; }
table.data {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 12px;
  margin-top: 8px;
}
table.data thead th {
  text-align: right;
  padding: 10px 12px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10px;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--rule);
  background: var(--bg);
  position: sticky;
  top: 0;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  z-index: 1;
}
table.data thead th:first-child,
table.data thead th.text { text-align: left; }
table.data thead th:hover { color: var(--ink); }
table.data thead th .sort-ind {
  display: inline-block;
  margin-left: 4px;
  color: var(--ink-faint);
}
table.data thead th.sorted .sort-ind { color: var(--ink); }

/* ---------- Column header filter affordance ---------- */
.th-flex {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
table.data thead th.text .th-flex { justify-content: flex-start; }
.th-label { display: inline-flex; align-items: center; }
.th-filter-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  padding: 0;
  border: none;
  border-radius: 2px;
  background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
}
.th-filter-btn:hover { color: var(--ink); background: var(--bg-alt); }
.th-filter-btn.active { color: var(--accent); }

/* ---------- Excel-style per-column filter panel ---------- */
.filter-panel {
  position: fixed;
  z-index: 2000;
  min-width: 230px;
  max-width: 320px;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: 3px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  padding: 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink);
}
:root[data-theme="dark"] .filter-panel { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55); }
.filter-panel-search { margin-bottom: 8px; }
.filter-panel-search input,
.filter-panel-cond input,
.filter-panel-cond select {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 5px 8px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--ink);
  outline: none;
}
.filter-panel-search input:focus,
.filter-panel-cond input:focus,
.filter-panel-cond select:focus { border-color: var(--ink); }
.filter-panel-quick { display: flex; gap: 6px; margin-bottom: 8px; }
.filter-panel-list {
  max-height: 220px;
  overflow-y: auto;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding: 4px 2px;
}
.filter-panel-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 2px;
  cursor: pointer;
}
.filter-panel-row input[type="checkbox"] { accent-color: var(--accent); cursor: pointer; flex: 0 0 auto; }
.filter-panel-row .fp-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.filter-panel-count { color: var(--ink-faint); flex: 0 0 auto; }
.filter-panel-empty { color: var(--ink-faint); padding: 8px 2px; }
.filter-panel-cond { display: flex; gap: 6px; margin-bottom: 6px; }
.filter-panel-cond select { flex: 0 0 58px; }
.filter-panel-and {
  text-align: center;
  color: var(--ink-faint);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 6px;
}
.filter-panel-presets { display: flex; gap: 6px; margin-bottom: 8px; }
.filter-panel-blanks {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 8px 0 2px;
  cursor: pointer;
  color: var(--ink-soft);
}
.filter-panel-blanks input { accent-color: var(--accent); cursor: pointer; }
.filter-panel-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; }
.btn-mini {
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 4px 9px;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
}
.btn-mini:hover { border-color: var(--ink-faint); color: var(--ink); }
.btn-mini--primary { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); }
.btn-mini--primary:hover { opacity: 0.9; color: var(--accent-contrast); }

/* ---------- Active filter chips ---------- */
.filter-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  padding: 12px 0 0;
}
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 5px 6px 5px 10px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--bg-alt);
  color: var(--ink);
}
.filter-chip-x {
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--ink-faint);
  font-size: 13px;
  line-height: 1;
  padding: 0 2px;
}
.filter-chip-x:hover { color: var(--warn); }
.filter-clear-all { margin-left: 2px; }

table.data tbody td {
  padding: 9px 12px;
  border-bottom: 1px solid var(--rule);
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
table.data tbody td:first-child,
table.data tbody td.text {
  text-align: left;
  font-family: var(--font-display);
  font-size: 13px;
}
table.data tbody tr { cursor: pointer; }
/* Rows with no drill-in destination — e.g. the benchmark row in Pulse's factor
   table, which has no holdings page. Same cursor:default idiom as
   thead th.unsortable. The :hover band is deliberately kept: it's a row-tracking
   reading aid, not solely an interactivity cue. */
table.data tbody tr.row-inert { cursor: default; }
/* Strategies table: a preset whose AND-gate collapsed to too few holdings to
   backtest (empty stats{}, or the engine's own status field once shipped —
   see insufficientUniverseReason in strategies.js). Italic pairs with
   .unavailable-reason/.comp-note--warn's existing "this is a caveat, not a
   missing value" idiom. */
table.data tbody tr.row-muted td { font-style: italic; }
table.data tbody tr:hover td { background: var(--bg-alt); }
.num-pos { color: var(--accent); }
.num-neg { color: var(--warn); }
.num-na  { color: var(--ink-faint); }

/* Screen table: a value a PM would reasonably distrust before acting on it
   (PE<1x, DY>25%, TR 12m>500% — see SCREEN_OUTLIER_COLS in screen.js). The
   value itself is unchanged; a dashed underline + help cursor is deliberately
   quieter than a color/background change, which on a dense 87-column table
   would read as another data dimension rather than a caveat. */
.cell-outlier {
  text-decoration: underline dashed var(--warn);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: help;
}

/* ---------- Percentile chips (label-driven; must look distinct) ---------- */
.pct-chip {
  display: inline-block;
  font-variant-numeric: tabular-nums;
}
.pct-chip--rank {
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(var(--accent-rgb), 0.2);
  color: var(--accent);
  font-weight: 500;
}
.pct-chip--hist {
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  color: var(--ink);
}

/* ---------- Stock header ---------- */
.stock-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 20px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 8px;
}
.stat-row { display: flex; gap: 20px; }
.stat { text-align: right; }
.stat-label {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin-bottom: 4px;
}
.stat-value {
  font-family: var(--font-mono);
  font-size: 18px;
  font-variant-numeric: tabular-nums;
}

/* ---------- Stock hero band (owner-approved layout, 2026-07-22) ----------
   Two-column grid: left = stock-header + "What changed — visuals"; right
   (the corner) = an identity card (Factor profile radar + a few identity
   facts). minmax() on the side column keeps it from getting squeezed
   arbitrarily narrow on medium widths while the main column absorbs any
   extra space; collapses to one column (radar card falls below the header,
   above the visuals) under ~900px. No fixed heights on the row itself, so
   the radar box is never clipped regardless of how tall the left column's
   visuals section grows. */
.stock-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 320px);
  gap: 32px;
  align-items: start;
}
.stock-hero-main { min-width: 0; }
.stock-hero-side { min-width: 0; }
@media (max-width: 900px) {
  .stock-hero { grid-template-columns: 1fr; }
  .stock-hero-side { order: -1; }
}

/* pulse.js's index-level Factor profile radar (owner request, 2026-07-30)
   reuses .stock-hero/.stock-hero-main/.stock-hero-side wholesale rather than
   growing a parallel rule set — see pulse.js's own comment at the reuse
   site. But it must NOT inherit the "radar first" mobile hoist above: on
   the stock page the identity card genuinely leads (owner call, 2026-07-22
   — you should know whose stock you're looking at before its analysis).
   On the index page the radar is a supplementary card beside the return
   bridge/dumbbells/sparkline grid that ARE this panel's main content, so on
   a phone it should keep falling AFTER them in document order, not jump
   above. .pulse-wc-hero marks pulse's own instance of the grid so this
   override can never touch the stock page's selector above. */
@media (max-width: 900px) {
  .pulse-wc-hero .stock-hero-side { order: 0; }
}

.identity-card {
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 16px;
  background: var(--bg-alt);
}
.identity-card h2 { margin-top: 0; }
.identity-card .chart-box { height: 300px; }
.identity-facts {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
}
.identity-fact {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}
.identity-fact-label {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}
.identity-fact-value {
  font-family: var(--font-mono);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Standout traits (owner request, 2026-07-22) — two chip rows under the
   identity card's facts, built from the same oriented dimension values that
   feed the Factor profile radar. Reuses .chip/.num-pos/.num-neg wholesale;
   only the row layout + a non-interactive chip variant are new (these chips
   aren't buttons — no click handler — so .chip's pointer cursor/hover-invert
   are overridden). */
.standout-traits {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
}
/* Strategy membership (2026-07-28) — same rule-separated block treatment as
   .standout-traits above, since it's another annotation stacked in the
   identity card rather than a section of its own. Empty by default and only
   filled when the stock is actually held, so the :empty rule keeps the card
   from growing a stray divider on names held by nothing. */
.strategy-membership:not(:empty) {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
}
.strategy-membership h3 {
  margin: 0 0 8px;
}
.strategy-membership-group {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px;
  margin-bottom: 6px;
}
/* Fixed-width group label so the chips line up down the left edge across
   groups rather than starting at a different x per group name length. */
.strategy-membership-group__label {
  flex: 0 0 auto;
  min-width: 86px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
}
.strategy-membership .chip {
  font-size: 11px;
  padding: 2px 8px;
  text-decoration: none;
}
.standout-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.standout-row-label {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin-right: 2px;
}
.trait-chip {
  padding: 3px 9px;
  font-size: 10px;
  text-transform: none;
  letter-spacing: 0;
  cursor: default;
}
.trait-chip.num-pos { border-color: var(--accent); }
.trait-chip.num-neg { border-color: var(--warn); }
.trait-chip.num-pos:hover { border-color: var(--accent); color: var(--accent); }
.trait-chip.num-neg:hover { border-color: var(--warn); color: var(--warn); }

/* "vs sector" radar toggle (owner request, 2026-07-22) — reuses .chip-row/
   .chip/.chip--disabled wholesale (same disabled-chip idiom as landing.js's
   Δ6m horizon chip); only trims the row's default 16px bottom margin so the
   toggle sits snug between the "Factor profile" h2 and its hint line. */
.identity-card .radar-scope-row { margin-bottom: 8px; }

/* Rich/cheap valuation strip (owner request, 2026-07-22; extended to all
   nine FE_UTIL.fund fundamentals, grouped Valuation/Growth/Returns on
   capital) — a second .identity-card under the Factor profile card: one row
   per metric, a slim own-history (p5-p95) track with a dot at today's
   reading, and the 10y percentile as colored text (direction — low or high
   percentile is "green" — varies per metric, see RICH_CHEAP_LOW_GOOD in
   stock.js). DOM bars, not SVG/ECharts — colors are plain CSS custom
   properties so a theme toggle repaints for free, same idiom as .fe-spark's
   inline `var(--accent)` strokes. */
.richcheap-card { margin-top: 16px; }
/* Group subhead divider between Valuation/Growth/Returns-on-capital rows —
   a lighter idiom than table.data's .group-row (this card is a div-row list,
   not a table), matching .standout-row-label's typography. */
.richcheap-group-head {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin-top: 12px;
  padding-bottom: 4px;
}
.richcheap-group-head:first-child { margin-top: 0; }
.richcheap-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
}
.richcheap-row + .richcheap-row { border-top: 1px solid var(--rule); }
.richcheap-label {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-soft);
  flex: 0 0 80px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.richcheap-track-wrap { flex: 1 1 auto; min-width: 0; }
.richcheap-track {
  position: relative;
  height: 4px;
  border-radius: 2px;
  background: var(--rule);
}
.richcheap-dot {
  position: absolute;
  top: 50%;
  width: 8px;
  height: 8px;
  margin-left: -4px;
  border-radius: 50%;
  background: var(--accent);
  border: 1.5px solid var(--bg-alt);
  transform: translateY(-50%);
}
.richcheap-row--na .richcheap-track { opacity: 0.4; }
.richcheap-pct {
  font-family: var(--font-mono);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  flex: 0 0 38px;
  text-align: right;
}

/* Stock detail — "What changed?" thesis-delta scorecard (owner request,
   2026-07-19). Reuses .data-table-wrap/table.data/.hint-line/.num-pos/
   .num-neg wholesale; the one new bit is a bold subheader row dividing the
   Expectations/Growth/Profitability/Valuation/Market blocks — table.data has
   a `thead tr.group-row` (grouped header band, WAVE-3) but no tbody-level
   equivalent, so this is new, scoped to tbody to avoid any clash, and kept
   generic enough for other table.data bodies to reuse later. */
table.data tbody tr.group-row td {
  background: var(--bg-alt);
  font-weight: 600;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
  cursor: default;
}
table.data tbody tr.group-row:hover td { background: var(--bg-alt); }

/* Generic pinned summary row (e.g. pulse.js's "what changed" sub-sections:
   the Index row atop the sector table, the Benchmark row atop the factor
   table) — a subtle accent tint, distinct from an ordinary data row but not
   tied to the GLOBAL-specific styling below. */
tr.row-pinned td { background: rgba(var(--accent-rgb), 0.05); }
tr.row-pinned:hover td { background: rgba(var(--accent-rgb), 0.09); }

/* ---------- Charts ---------- */
.chart-box {
  width: 100%;
  height: 340px;
  margin-bottom: 8px;
}
.chart-box--tall { height: 520px; }
.chart-grid-2 {
  display: grid;
  /* minmax(0,…) on the two-column form too, not just the collapsed one below:
     between 900px and ~1100px each 1fr track was still inflating to its item's
     min-content width (measured 211px of page h-scroll on the landing page at
     1024px). Same implicit min-width:auto, just two tracks doing it. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 32px;
}
@media (max-width: 900px) {
  /* minmax(0,1fr), not 1fr: a 1fr track carries an implicit min-width:auto, so
     it inflates to its item's MIN-CONTENT width. A .data-table-wrap holding a
     wide table (Sub-sector ideas: long sub-industry names + 4 numeric columns)
     therefore grew its own box to 600px inside a 310px column, and its
     overflow-x:auto never engaged — the page scrolled sideways instead of the
     table. Measured at 390px: 250px of page-wide h-scroll, entirely from that
     one section, zero after this. Same class of bug as f90c42b's topbar fix. */
  .chart-grid-2 { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Compare / factors pickers ---------- */
.compare-pickers, .axis-pickers {
  display: flex;
  gap: 16px;
  align-items: center;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.compare-pickers input, .compare-pickers select, .axis-pickers select {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 7px 10px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--ink);
  min-width: 200px;
}
.axis-pickers label {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  display: flex;
  align-items: center;
  gap: 8px;
}
.axis-pickers select { min-width: 160px; }

/* ---------- Risk / ideas panel ---------- */
.risk-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  margin-bottom: 8px;
}
@media (max-width: 1100px) {
  .risk-grid { grid-template-columns: 1fr; }
}
.risk-card h2 { margin-top: 0; }
.risk-row {
  display: block;
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
}
.risk-row:hover .risk-row-name { color: var(--accent); }
.risk-row-name { font-size: 14px; margin-bottom: 4px; }
.risk-row-extras { display: flex; gap: 10px; flex-wrap: wrap; }
.risk-extra {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-soft);
}

/* ---------- Strategies view ---------- */
.strat-layout {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 32px;
  align-items: start;
}
@media (max-width: 900px) {
  .strat-layout { grid-template-columns: 1fr; }
}
.strat-rail {
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 16px;
  position: sticky;
  top: 16px;
}
.strat-rail-section { margin-bottom: 18px; }
.strat-rail-section:last-child { margin-bottom: 0; }
.strat-rail-title {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin-bottom: 8px;
}
.strat-check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  padding: 5px 0;
  cursor: pointer;
  color: var(--ink);
}
.strat-check input { accent-color: var(--accent); cursor: pointer; }
.strat-toolbar {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 4px;
}
.strat-toggle {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}
.strat-toggle input { accent-color: var(--accent); cursor: pointer; }
.tc-note {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--warn);
  padding: 4px 10px;
  border: 1px solid var(--rule);
  border-radius: 999px;
}

/* ============================================================
   WAVE-1 additions — cohorts / structure views + FX saved screens
   (agent B). Reuses .chip-row/.chip, .strat-layout/.strat-rail*,
   .data-table-wrap/table.data, .btn-mini(--primary) wherever the
   existing component already fit; only genuinely new bits below.
   ============================================================ */

/* Landing tile quicklinks row (Strategies / Cohorts / Structure) —
   .region-tile-strat keeps its font styling; this wrapper takes over
   the flex-row + bottom alignment so multiple links sit side by side. */
.region-tile-links {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  align-self: flex-end;
  margin-top: 6px;
}
.region-tile-links .region-tile-strat {
  margin-top: 0;
  align-self: auto;
}

/* Small caption label used ahead of the FX saved-screen buttons in the
   screener toolbar. */
.toolbar-label {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

/* ---------- Strategies — empty state ---------- */
.strategies-empty code {
  display: inline-block;
  margin-top: 12px;
  padding: 8px 14px;
  background: var(--bg-alt);
  border: 1px solid var(--rule);
  border-radius: 2px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink);
  text-transform: none;
  letter-spacing: 0;
}

/* ============================================================
   WAVE-2 additions (agents A + C) — strategies tab strip (Annual
   Returns / Valuation Drift / Return Drivers panels), the driver-entity
   <select>, and the stock-detail own-history sparkline. cohorts.js also
   reuses .strat-main for its rail+content layout, so keep it generic
   (not strategies-specific).
   ============================================================ */

/* .strat-layout's right-hand grid track (see "Strategies view" above) —
   min-width: 0 so its charts can shrink inside the fixed 220px rail
   track instead of overflowing the grid. */
.strat-main { min-width: 0; }

/* Underlined tab strip above the strategies panels. */
.strat-tabs {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 20px;
}
.strat-tab {
  font-family: var(--font-mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 10px 16px;
  margin-bottom: -1px;
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--ink-soft);
  cursor: pointer;
}
.strat-tab:hover { color: var(--ink); }
.strat-tab.active { color: var(--ink); border-bottom-color: var(--accent); }

/* Each tab's content area. Panels are toggled via the `hidden` attribute
   rather than a display class, so this rule only needs to supply spacing. */
.strat-panel { margin-top: 4px; }

/* Return Drivers entity picker — same look as .compare-pickers/.axis-pickers
   selects, reused here since strat-toolbar isn't wrapped in either. */
.strat-select {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 7px 10px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--ink);
  min-width: 200px;
}
.strat-select:focus { border-color: var(--ink); }

/* Stock detail — compact own-history sparkline beneath the metric-vs-sector
   chart. .stk-spark overrides .chart-box's default 340px height; the label
   above it explains the green/red shading convention. */
.stk-spark-block { margin-bottom: 24px; }
.stk-spark-label {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  margin-bottom: 6px;
}
.chart-box.stk-spark { height: 120px; margin-bottom: 0; }

/* Stock detail — "What changed — visuals" fundamentals sparkline grid (owner
   request, 2026-07-21). Reuses .chip-row/.chip (shared horizon toggle),
   .chart-box/.state (return bridge + factor dumbbells panels), .num-pos/
   .num-neg/.num-na and .fe-spark (FE_UTIL.sparklineSVG) wholesale — only the
   grid/tile chrome below is new. */
.spark-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-bottom: 8px;
}
@media (max-width: 760px) {
  .spark-grid { grid-template-columns: 1fr; }
}
.spark-cell {
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 10px 12px;
}
.spark-cell--empty { display: flex; align-items: center; justify-content: space-between; }
.spark-cell-label {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
  margin-bottom: 4px;
}
.spark-cell-value {
  font-family: var(--font-mono);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  margin-bottom: 4px;
}
.spark-cell-nospark {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  margin: 8px 0 0;
}

/* ============================================================
   WAVE-3 additions — screener Excel parity: 'Full screen' column set,
   row virtualization, frozen ID/Name panes, grouped header band, density
   modes, and percentile conditional-formatting shading. All scoped to
   #table-wrap (the screener's own .data-table-wrap) so cohorts.js's
   reuse of the same table.data/.data-table-wrap component elsewhere is
   untouched.
   ============================================================ */

/* ---------- Grouped header band (2nd row above column names) ---------- */
table.data thead tr.group-row th {
  height: 22px;
  padding: 3px 12px;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
  text-align: left;
  cursor: default;
  border-bottom: 1px solid var(--rule);
}
/* Column-name row sits below the group band; both rows are sticky (see
   generic `table.data thead th { position:sticky; top:0 }` above), so the
   column row needs its own top offset equal to the group row's height. */
table.data thead tr.col-row th { top: 23px; }

/* ---------- Frozen panes: ID + Name stay pinned while scrolling
   horizontally. Fixed pixel widths keep the sticky `left` offsets (and the
   table's auto column sizing) consistent between the header and body. ---- */
table.data th.col-frozen-1, table.data td.col-frozen-1 {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--bg);
  width: 110px;
  min-width: 110px;
  max-width: 110px;
}
table.data th.col-frozen-2, table.data td.col-frozen-2 {
  position: sticky;
  left: 110px;
  z-index: 2;
  background: var(--bg);
  width: 190px;
  min-width: 190px;
  max-width: 190px;
}
/* Header frozen cells (both the group band's merged corner cell and the
   column row's ID/Name cells) must sit above scrolled body content AND
   above plain (non-frozen) header cells at the top+left intersection. */
table.data thead th.col-frozen-1,
table.data thead th.col-frozen-2,
table.data thead th.group-frozen {
  z-index: 3;
}
table.data thead th.group-frozen {
  position: sticky;
  left: 0;
  background: var(--bg);
}

/* ---------- Density modes (Compact default, Comfortable alternative) ---- */
#table-wrap.density-compact table.data tbody td { padding: 4px 8px; }
#table-wrap.density-compact table.data tbody tr.data-row { height: 26px; }
#table-wrap.density-comfortable table.data tbody td { padding: 9px 12px; }
#table-wrap.density-comfortable table.data tbody tr.data-row { height: 34px; }

/* Subtle zebra striping keyed to each row's position in the full filtered
   result set (not DOM position), so it doesn't flicker as the virtualized
   window scrolls. Higher specificity than .col-frozen-*'s own background so
   frozen cells stay in sync with the stripe instead of looking cut out. */
#table-wrap tbody tr.row-alt td { background: var(--bg-alt); }

/* ---------- Percentile / rank conditional formatting ---------- */
/* Neutralize the shared .pct-chip--rank pill's own accent tint + colored
   text inside the screener only (cohorts.js reuses the same class on a
   different table — see .pct-chip--rank in the base stylesheet — and must
   keep its own look). The per-cell background ramp below (applied inline
   via FE_UTIL.rankShadeStyle) carries the color now; text stays on an ink
   token so it reads clearly against either tint. */
#table-wrap .pct-chip--rank { background: transparent; color: var(--ink); }
.cf-cell { text-align: right; }

/* ============================================================
   WAVE-4 additions — unified Compare series builder, stock.js metric-pair
   preset chips, and the new Pulse view. Reuses .chip-row/.chip, .btn,
   .compare-pickers/.axis-pickers, .data-table-wrap/table.data, .cf-cell and
   FE_UTIL.rankShadeStyle wherever the existing component already fit; only
   genuinely new bits below.
   ============================================================ */

/* ---------- Compare: series builder ---------- */
.compare-add-row {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.compare-add-row input {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 7px 10px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--ink);
  min-width: 220px;
  flex: 1 1 220px;
}
.index-median-toggle {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}
.index-median-toggle input { accent-color: var(--accent); cursor: pointer; }
.index-median-toggle.is-disabled { opacity: 0.45; cursor: default; }
.index-median-toggle.is-disabled input { cursor: default; }

.series-chip-row { margin-bottom: 6px; }
.series-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: default;
}
.series-chip:hover { border-color: var(--rule); color: var(--ink-soft); } /* not a mode toggle — undo .chip:hover */
.series-chip__kind {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.06em;
  opacity: 0.7;
}
.series-chip--sector .series-chip__kind { color: var(--accent); }
.series-chip--stock .series-chip__kind { color: var(--ink-faint); }
.series-chip--index-median .series-chip__kind { color: var(--warn); }
.series-chip__remove {
  border: none;
  background: transparent;
  color: inherit;
  opacity: 0.6;
  font-size: 13px;
  line-height: 1;
  padding: 0 0 0 2px;
  cursor: pointer;
}
.series-chip__remove:hover { opacity: 1; }

/* ---------- HTML chart legend (FE_UTIL.renderChartLegend, 2026-07-26) ----------
   Replaces ECharts' in-canvas type:'scroll' legend for xcompare.js/compare.js
   (up to 12 series — the scroll legend paginated behind arrows at every
   width). A wrapping row so every name is visible with no pager, ever.
   Reuses .chip's pill shape but overrides its uppercase/tracked-caps voice —
   these are company/sector NAMES, not short action words, and forcing
   "APPLE INC (SPX)" caps would fight the peer table right below it, which
   renders the same names in normal case. */
.xc-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  margin: 4px 0 16px;
}
.xc-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  min-width: 0; /* let a long GICS sector name shrink+wrap instead of forcing page-wide h-scroll */
  padding: 4px 10px 4px 8px;
  text-transform: none;
  letter-spacing: normal;
  text-align: left;
}
.xc-legend-item:hover { border-color: var(--ink-faint); }
.xc-legend-item--dim { opacity: 0.42; }
.xc-legend-item__swatch { flex: 0 0 auto; display: inline-flex; align-items: center; }
.xc-legend-item__label {
  min-width: 0;
  overflow-wrap: anywhere; /* never truncate the name — wrap it instead, item grows in height not width */
  color: var(--ink-soft);
}
.xc-legend-item__value {
  flex: 0 0 auto;
  margin-left: 2px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.xc-legend-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Phone: the one-item-per-row flex wrap is honest but disproportionate. Twelve
   series at 390px measured a 496px-tall legend against the 340px chart it
   describes — 1.5x the height of the thing it explains, so you scroll past the
   key to reach the chart. Two columns with tighter rows brings that to ~190px.
   Truncating the name is cheap HERE specifically: both views carry a full peer
   table immediately below the chart with every series' complete name and all
   its metrics, so the legend only has to do colour->name mapping, and the
   title attribute (set in util.js) keeps the full string on hover/long-press.
   The padding override is deliberate: .chip gets a 9px tap-target bump at this
   width (style.css's iPhone pass), which is right for a primary chip row but
   costs 12x here. ~26px is still a usable target for a secondary toggle, and
   is the better trade against half a screen of legend. */
@media (max-width: 600px) {
  .xc-legend {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 6px;
  }
  /* Descendant selector on purpose (0,2,0): .chip's 9px tap-target bump lives
     in the ~480px block LATER in this file, so a bare .xc-legend-item (0,1,0)
     loses on source order and the rows stayed 34px. */
  .xc-legend .xc-legend-item { padding: 5px 8px; gap: 5px; }
  .xc-legend-item__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow-wrap: normal;   /* undoes the wrap-don't-truncate rule above */
  }
  /* auto-margin lines the values up as their own column, so the legend reads
     as a ranking rather than twelve ragged labels. */
  .xc-legend-item__value { margin-left: auto; }
}

.hint-line {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  margin: -8px 0 16px;
}
/* Hint-lines carry action links now (sub-industry constituents -> compare /
   screener). Without this they fell through to the UA default and rendered
   #0000EE underlined — the only browser-blue links in the app. Same treatment
   as .view-links/.crumbs/.subtitle: quiet ink, no rule, accent on hover.
   Uppercase + tracked so they read as ACTIONS against the descriptive text
   beside them, matching the nav row's voice rather than inventing a third. */
.hint-line a {
  color: var(--ink-soft);
  text-decoration: none;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-left: 10px;
}
.hint-line a:hover { color: var(--accent); }
/* Insufficient-universe notice on a strategy's own detail page (fleet scout
   sweep 2, item 4) — same --warn token as .tile-chip--warn/.fe-banner--warn,
   so a PM sees the SAME "don't trust this" signal one click deeper as on the
   Strategies list's row-muted cell, instead of a page that just looks
   silently uncomputed. */
.hint-warn { color: var(--warn); font-style: italic; }

/* Compact stat strip — e.g. Structure view's terminal-growth index/dist
   summary between a toolbar and its table. Same voice as .hint-line, stacked
   as multiple lines instead of one. */
.stat-strip {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  line-height: 1.7;
  padding: 10px 0 14px;
}

/* ---------- Pulse view ---------- */
#pulse-sector-panel, #pulse-factor-panel { margin-bottom: 8px; }
/* :not(--tall): this ID rule outranks `.chart-box--tall { height: 520px }`, so it
   was silently shrinking the 16-row Factor leadership bar back to 260px — and at
   ~16px per row ECharts auto-hid every other strategy name, the exact failure
   --tall was added in pulse.js to prevent. The compact height still applies to
   the panel's other (non-tall) chart boxes. */
#pulse-factor-panel .chart-box:not(.chart-box--tall) { height: 260px; margin-bottom: 16px; }
#pulse-factor-panel .chart-box--tall { margin-bottom: 16px; }

/* ============================================================
   Terminal growth reference panel (Structure view, sectors.js v4
   `terminal` block). Pins the (0,'Index') reference row visually at
   the top of #tg-table regardless of the active sort column.
   ============================================================ */
table.data tr.tg-index-row td {
  background: var(--bg-alt);
  font-weight: 600;
  border-bottom: 1px solid var(--rule);
}
table.data tr.tg-index-row:hover td { background: var(--bg-alt); }

/* Growth-map scatter, sits between the stat strip and #tg-table. Slightly
   shorter than .chart-box--tall so the panel keeps table + chart both above
   the fold on a typical laptop viewport. */
.chart-box--growth-map { height: 420px; }

/* Click-a-dot -> scroll-and-flash the matching #tg-table row (data-group
   attribute set in structure.js). Runs once (~1.2s) then structure.js
   removes the class; the accent outline is drawn per-cell via inset
   box-shadow since table.data uses border-collapse (an outline on <tr>
   itself won't paint reliably across browsers on a collapsed table). */
@keyframes tg-row-flash {
  0%, 100% { box-shadow: none; background-color: transparent; }
  12%, 78% { box-shadow: inset 0 0 0 2px var(--accent); background-color: rgba(var(--accent-rgb), 0.16); }
}
table.data tr.tg-row-flash td { animation: tg-row-flash 1.2s ease-out; }

/* ============================================================
   WAVE-5 additions — Pulse as the single-index home, data-rich landing
   tiles, screener workbench trim (owner decisions, 2026-07-13). Reuses
   .region-tile-card/.region-tile, .num-pos/.num-neg, .hint, .state,
   .chip-row wherever the existing component already fit; only genuinely
   new bits below.
   ============================================================ */

/* ---------- Shared inline sparkline (landing tiles + Pulse header) ---------- */
.fe-spark { display: block; overflow: visible; }

/* ---------- Landing tile: data-rich body (spark + returns + sectors + chip) ---------- */
.tile-body { margin-top: 12px; }
.tile-spark { margin-bottom: 8px; }
.tile-line {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-soft);
  line-height: 1.6;
}
.tile-sectors { color: var(--ink-faint); }
.tile-chip {
  display: inline-block;
  margin-top: 8px;
  padding: 3px 9px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-decoration: none;
}
.tile-chip--accent { background: rgba(var(--accent-rgb), 0.16); color: var(--accent); }
.tile-chip--warn { background: rgba(var(--warn-rgb), 0.16); color: var(--warn); }

/* ---------- Pulse: index header (spark + trailing returns + meta line) ---------- */
.pulse-header {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 16px 0 20px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.phdr-spark-wrap { flex: 0 0 auto; }
.phdr-body { flex: 1 1 auto; min-width: 240px; }
.phdr-rets { display: flex; gap: 20px; flex-wrap: wrap; }
.phdr-ret {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-faint);
}
.phdr-meta {
  margin-top: 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
}

/* ---------- Pulse: Ideas triggers row (replaces the old screener movers strip) ---------- */
.ideas-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);  /* see .chart-grid-2 */
  gap: 24px;
  margin-bottom: 12px;
}
@media (max-width: 900px) {
  /* minmax(0,1fr) for the same reason as .chart-grid-2 above: a bare 1fr track
     inherits min-width:auto and inflates to its column's MIN-CONTENT width, so
     the idea cards' own flex rows (.risk-row-extras chips, .idea-card-chg) and
     any .data-table-wrap inside set the track width instead of the viewport.
     Measured at 390px: the track resolved to 501px on #/pulse and 471px on
     #/risk, dragging the whole page sideways by 151px and 121px. */
  .ideas-cols { grid-template-columns: minmax(0, 1fr); }
}
.ideas-col-title {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin-bottom: 8px;
}
.idea-card {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
}
.idea-card:hover .idea-card-name { color: var(--accent); }
.idea-card-name {
  font-size: 13px;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.idea-card-sector {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-faint);
  flex: 0 0 auto;
}
/* Preset-qualifier cards only (pulse.js presetMemberCard) — this line now
   carries the GICS-4 sub-industry ("Construction Machinery & Heavy
   Transportation Equipment") instead of the short GICS-1 label every OTHER
   .idea-card-sector user (pulse.js ideaCard, risk.js, sector.js nameCard)
   still shows. Un-shrinkable flex:0 0 auto was fine for a short label, but
   paired with .idea-card-chg's ~238px sentence here it pushed the row past
   the card's right edge — measured 100px of page-wide horizontal scroll on
   #/pulse/TPX at 1000px viewport (the 901-1199px band where .ideas-cols is
   still 2-column but narrower than desktop). Scoped to a modifier class
   rather than widened on .idea-card-sector itself: applying shrink+ellipsis
   there too silently truncated risk.js's short GICS-1 chips (e.g. "Health
   Care" down to ~38px, unreadable) which have no `title` fallback to
   recover the full text. presetMemberCard sets that title; the other
   builders don't need to. */
.idea-card-sector--sub {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.idea-card-chg {
  font-family: var(--font-mono);
  font-size: 11px;
  flex: 0 0 auto;
  white-space: nowrap;
}
.ideas-chip-row { display: flex; gap: 10px; margin-bottom: 24px; }

/* ============================================================
   Cross-market compare (#/xcompare). Reuses .chip-row/.chip, .btn/
   .btn-mini, .compare-add-row/.series-chip*/.hint-line (from the WAVE-4
   block above), .data-table-wrap/table.data and FE_UTIL.rankShadeStyle
   wherever the existing component already fit; only genuinely new bits
   below. One small index-code badge (.index-chip) is shared across the
   suggestion dropdown, series chips and the peer table header.
   ============================================================ */
.landing-view-links { margin: -8px 0 20px; }

.index-chip {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 1px 6px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--ink-soft);
}

/* Sector-mode add flow (#/xcompare): Index -> GICS level -> Group, 3
   labeled steps stacked vertically — replaces the old flat free-text
   sector search (see xcompare.js's renderSectorAddUI). */
.xc-sector-step { margin-bottom: 14px; }
.xc-sector-step:last-child { margin-bottom: 0; }
.xc-sector-step-label {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin-bottom: 8px;
}
#xc-sector-picker-row select {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 7px 10px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--ink);
  min-width: 220px;
}

.xc-suggestions {
  display: flex;
  flex-direction: column;
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--rule);
  border-radius: 2px;
  margin: -8px 0 12px;
  background: var(--bg-alt);
}
.xc-suggestion {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  padding: 7px 10px;
  border: none;
  border-bottom: 1px solid var(--rule);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 12px;
  cursor: pointer;
}
.xc-suggestion:last-child { border-bottom: none; }
.xc-suggestion:hover { background: var(--bg); color: var(--accent); }
.xc-suggestion-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xc-suggestion-id { flex: 0 0 auto; color: var(--ink-faint); font-size: 11px; }

.series-chip--pending { opacity: 0.6; }
.series-chip--failed { border-color: var(--warn); }
.series-chip__status {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
}
.series-chip__status--failed { color: var(--warn); }
.series-chip__asof {
  font-size: 9px;
  color: var(--ink-faint);
  white-space: nowrap;
}

.metric-ccy-badge {
  display: inline-block;
  margin-left: 6px;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--warn);
  border: 1px solid var(--warn);
  border-radius: 999px;
  padding: 1px 5px;
  vertical-align: middle;
}

.peer-table-wrap { max-height: none; }
/* Fixed column geometry (owner request, 2026-08-18). Default table layout
   sizes each column to its widest unbroken content, so one long name --
   "Nan Ya Printed Circuit Board C" -- stretched its column far past its
   neighbours and squeezed the rest, making a 7-stock peer set read as
   ragged columns of different widths. table-layout:fixed plus an explicit
   width per column makes every stock column identical regardless of name
   length, and the name simply wraps onto a second line (the owner's own
   suggestion) instead of dictating the grid.
   Widths, not percentages: with up to MAX_SERIES=12 stocks, percentage
   columns would compress to unreadable. Fixed px means the table overflows
   and .data-table-wrap (overflow-x:auto) scrolls it -- the same behaviour
   the screener's wide table already has. The metric-label column gets more
   room because its content ("Gross Profit / Assets") is the longest text
   in the table and is NOT allowed to wrap. */
table.peer-table { table-layout: fixed; width: auto; }
table.peer-table th.text, table.peer-table td.text { width: 190px; white-space: nowrap; }
table.peer-table thead th, table.peer-table tbody td { width: 116px; }
table.peer-table thead th { text-align: center; vertical-align: bottom; }
table.peer-table thead th.text { text-align: left; }
/* Wrap rather than ellipsis: the full name matters when comparing near-
   identical peers ("WUS Printed Circuit Co" vs "WUS Printed Circuit
   Kunshan"), and truncating would hide exactly the distinguishing word.
   break-word, NOT anywhere: `anywhere` lets the browser break inside a word
   even when a later break opportunity exists, which rendered Kinsus as
   "KINSUS INTERCONNEC / T TECHNOLOGY" -- a mid-word split that reads as a
   typo. `break-word` only breaks a token that cannot fit its line alone,
   so ordinary names wrap at spaces and only a genuinely over-long token
   gets cut. */
.peer-col-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.25;
}
.peer-col-link { color: inherit; text-decoration: none; }
.peer-col-link:hover { color: var(--accent); text-decoration: underline; }
.peer-col-meta { margin-top: 2px; }
.peer-col-asof { margin-top: 2px; font-family: var(--font-mono); font-size: 9px; color: var(--ink-faint); }
table.peer-table tbody td { text-align: center; }
table.peer-table tbody td.text { text-align: left; }

.xc-set-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: 12px;
}
.xc-set-name { flex: 1 1 auto; min-width: 0; }
.xc-set-meta { color: var(--ink-faint); font-size: 11px; flex: 0 0 auto; }
.xc-set-builtin-badge {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 1px 6px;
  margin-left: 6px;
}

/* ============================================================
   WAVE-6 — Landing v2: tile grid replaced by the Index Monitor table +
   Factor x Region matrix + Global Ideas strip (owner decision 2026-07-13,
   "tiles die, table wins"). Reuses table.data/.data-table-wrap, .chip-row/
   .chip, .num-pos/.num-neg/.num-na, .state, .hint-line, .idea-card/
   .idea-card-name/.idea-card-sector/.idea-card-chg/.ideas-cols/.ideas-col/
   .ideas-col-title, .fe-spark wherever the existing component already fit;
   only genuinely new bits below.
   ============================================================ */

/* ---------- Index Monitor / Matrix shared identity cell (1st column) ---- */
.index-cell-label { font-size: 13px; }
.index-cell-meta {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-faint);
  margin-top: 2px;
  white-space: nowrap;
}
.index-cell-sector { white-space: normal; }
.index-cell-refresh { font-family: var(--font-mono); font-size: 11px; color: var(--ink-soft); }

/* ---------- Index Monitor table ---------- */
.monitor-table-wrap { max-height: none; }
.monitor-spark { display: block; }
table.monitor-table thead th.unsortable { cursor: default; }
table.monitor-table thead th.unsortable:hover { color: var(--ink-soft); }

/* ---------- Fundamentals — what changed (2026-07-20) ---------------------
   Reuses table.data/.data-table-wrap/.monitor-table-wrap (row styling, GLOBAL
   pin, scroll behavior) and the screener's grouped-header band (.group-row/
   .group-th/.col-row — see screen.js) for the Valuation/Growth/Returns-on-
   capital column groups; no new table chrome needed. */
.fund-table-wrap { max-height: none; }

/* ---------- Shared "what changed" table (FE_UTIL.fund.renderWhatChangedTable,
   2026-07-21 unification round) — one renderer for the index/sector/factor
   "what changed" surfaces. Legend line spells out the Return attribution
   bar's fixed hue-per-driver mapping (P/E = accent, Earnings = warn, always
   — never opacity variants, see attribBarSVG). "No history" rows (a preset
   with no return data over the window) render dimmed instead of a
   live-looking fundamental next to an em-dash return. */
.fund-legend {
  display: flex;
  align-items: center;
  gap: 4px;
}
.fund-legend-swatch {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  margin-left: 10px;
}
.fund-legend-swatch:first-child { margin-left: 0; }
.fund-legend-swatch--accent { background: var(--accent); }
.fund-legend-swatch--warn { background: var(--warn); }
tr.fund-row-dim td { color: var(--ink-faint); font-style: italic; }
tr.fund-row-dim:hover td { background: var(--bg-alt); }

/* Selected-row highlight for a "what changed" table whose rows drive an
   adjacent visual panel instead of navigating away (pulse.js's sector panel
   click-to-select, 2026-07-21) — a persistent tint (not just :hover) so the
   panel's current selection stays legible after the pointer moves off.
   Also used by sector.js's industry scoreboard to mark the sub-industry a
   deep link arrived focused on. Selector deliberately NOT scoped to
   .monitor-row: it was, and the sector page's rows are plain table.data rows,
   so the class applied and tested green while painting nothing at all. */
tr.wc-vis-selected td { background: rgba(var(--accent-rgb), 0.10); }

/* ---------- Landing "What changed — across markets" small-multiples grid
   (owner request, 2026-07-21) — auto-fit ~5x2 for the usual 10 index tiles,
   collapsing to fewer columns on narrower viewports without a fixed
   breakpoint list. Cards are plain links (href carries the navigation, no
   click JS needed) styled to read as tiles, not table rows. */
.wc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  margin-bottom: 8px;
}
.wc-grid-card {
  display: block;
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 10px 12px;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.wc-grid-card:hover { border-color: var(--ink-faint); background: var(--bg-alt); }
.wc-grid-card-label {
  font-size: 12.5px;
  font-weight: 600;
  margin-bottom: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wc-grid-card-tr {
  font-family: var(--font-mono);
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  margin-bottom: 6px;
}
.wc-grid-card-bridge { margin-bottom: 6px; line-height: 0; }
.wc-grid-card-bridge--na {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-faint);
  line-height: normal;
}
.wc-grid-card-callout {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chip.chip--disabled { opacity: 0.4; cursor: not-allowed; }
.chip.chip--disabled:hover { color: inherit; border-color: var(--rule); }

/* ---------- Unavailable indices — dim, non-interactive strip ---------- */
.unavailable-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 12px 0 4px;
  color: var(--ink-faint);
}
.unavailable-title {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.unavailable-chip {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  padding: 3px 9px;
  border: 1px solid var(--rule);
  border-radius: 999px;
}
.unavailable-code { color: var(--ink-faint); }
/* Per-chip reason (retired / no data) — a shade fainter than the code so it
   reads as an annotation, not another identity field, and italic to match
   .comp-note--warn's "this is a caveat" idiom used elsewhere on the site. */
.unavailable-reason {
  color: var(--ink-faint);
  opacity: 0.75;
  font-style: italic;
}
.unavailable-reason::before { content: '\2014\a0'; } /* em dash + nbsp */

/* Pulse sector leadership table: coverage count beside a fundamentals
   median (fundCell in pulse.js) — small and faint so it reads as a
   footnote to the number in front of it, not a second data column. */
.fund-n {
  font-size: 9px;
  color: var(--ink-faint);
  vertical-align: top;
}

/* ---------- Factor x Region matrix ---------- */
/* Edge-fade scroll affordance: on narrow windows the matrix's rightmost
   columns (Stability, Benchmark) clip behind overflow-x scroll with no
   visual hint (macOS hides scrollbars until touched) — these scroll-linked
   gradient shadows (background-attachment local vs scroll) appear exactly
   when content is cut off on that side, in both themes via --bg/--rule. */
.matrix-table-wrap {
  max-height: none; margin-bottom: 4px;
  background:
    linear-gradient(90deg, var(--bg) 30%, transparent) left / 32px 100%,
    linear-gradient(270deg, var(--bg) 30%, transparent) right / 32px 100%,
    linear-gradient(90deg, var(--rule), transparent) left / 12px 100%,
    linear-gradient(270deg, var(--rule), transparent) right / 12px 100%;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll;
}
table.matrix-table { background: transparent; }
table.matrix-table thead th { cursor: default; text-align: center; }
table.matrix-table thead th:hover { color: var(--ink-soft); }
table.matrix-table thead th.text { text-align: left; }
table.matrix-table tbody td.matrix-cell { text-align: center; }
.matrix-cell--bench { border-left: 1px solid var(--rule); }
.matrix-cell--blank { font-family: var(--font-mono); }

/* ---------- Global Ideas strip — pooled cross-index idea-card variant --- */
.global-idea-card .idea-card-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.idea-card-code {
  font-weight: 600;
  color: var(--accent);
  margin-right: 2px;
}

/* ============================================================
   GLOBAL virtual universe (factor_engine.core.global_universe) — 9-index
   union/dedupe surfaced across the landing Index Monitor + matrix and the
   screener. Reuses table.data/.data-table-wrap, .hint-line, .chip-row/.chip,
   .unavailable-chip's pill styling wherever it already fit; only genuinely
   new bits below.
   ============================================================ */

/* ---------- Landing: GLOBAL pinned row — visually distinct, not just first --- */
tr.monitor-row--global,
tr.matrix-row--global {
  background: rgba(var(--accent-rgb), 0.05);
}
tr.monitor-row--global:hover td,
tr.matrix-row--global:hover td {
  background: rgba(var(--accent-rgb), 0.09);
}
.global-row-badge {
  font-family: var(--font-mono);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  border: 1px solid rgba(var(--accent-rgb), 0.4);
  border-radius: 999px;
  padding: 1px 6px;
  margin-left: 6px;
  vertical-align: middle;
}

/* ---------- Screener: HOME identity chip (GLOBAL screen.js only) -------- */
/* A stock's HOME column value in the GLOBAL screen — pill-styled like
   .unavailable-chip so it reads as "this row belongs to another market's
   page", not a normal text cell; row click still routes there (screen.js). */
.home-chip {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 1px 7px;
}

/* ============================================================
   Portfolio (#/portfolio). Reuses .chip-row/.chip, .btn/.btn-mini,
   .compare-add-row, .hint-line/.state, .data-table-wrap/table.data,
   .index-chip and .xc-set-row/.xc-set-name/.xc-set-meta/.xc-set-builtin-badge
   (xcompare's saved-sets idiom) wherever the existing component already
   fit; only genuinely new bits below.
   ============================================================ */
.pf-textarea {
  display: block;
  width: 100%;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
  padding: 10px 12px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--ink);
  resize: vertical;
  margin-bottom: 8px;
}
.pf-textarea:focus { border-color: var(--ink); outline: none; }

.pf-holdings-table tbody td:last-child,
.pf-risk-table tbody td:nth-child(3) { text-align: right; }

.pf-unresolved {
  border: 1px solid var(--warn);
  border-radius: 2px;
  padding: 10px 14px;
  margin: 4px 0 16px;
}
.pf-unresolved-title {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--warn);
  margin-bottom: 6px;
}
.pf-unresolved-item {
  padding: 6px 0;
  border-top: 1px solid var(--rule);
  font-size: 13px;
}
.pf-unresolved-item:first-child { border-top: none; }
.pf-unresolved-raw { font-family: var(--font-mono); color: var(--ink); }
.pf-unresolved-reason { color: var(--ink-faint); font-size: 11px; }
.pf-unresolved-suggestions {
  margin-top: 3px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-soft);
}

.pf-flag-chip {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--warn);
  border: 1px solid var(--warn);
  border-radius: 999px;
  padding: 2px 8px;
  margin: 0 4px 4px 0;
}
.pf-flag-chip--muted {
  color: var(--ink-faint);
  border-color: var(--rule);
  font-weight: 400;
}

/* ============================================================
   Strategy holdings drill-down (#/strategy/:IDX/:KEY) + drill-link
   affordances added to the Strategies stats table / rail and the Pulse
   factor-leadership table. Reuses .chip-row/.chip, .strat-layout/
   .strat-rail*, .chart-box(--tall), .data-table-wrap/table.data,
   .hint-line/.hint/.state, .tile-chip(--accent/--warn), .num-pos/.num-neg,
   .cf-cell and FE_UTIL.rankShadeStyle wherever the existing component
   already fit; only genuinely new bits below.
   ============================================================ */

/* ---------- Drill links: strategies.js rail + stats table, pulse.js table --- */
.strat-rail-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.strat-rail-row .strat-check { flex: 1 1 auto; min-width: 0; }
.strat-rail-link,
.strat-link-icon {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  text-decoration: none;
  white-space: nowrap;
  flex: 0 0 auto;
}
.strat-rail-link:hover,
.strat-link-icon:hover { color: var(--accent); }

/* Whole strategy name is the drill-in link (owner: icon-only target too
   small). Ink-colored for table legibility; hover reveals affordance. */
.strat-name-link {
  color: var(--ink);
  text-decoration: none;
}
.strat-name-link:hover { color: var(--accent); text-decoration: underline; }
.strat-name-link:hover .strat-link-icon { color: var(--accent); }

/* Sub-sector ideas tables (landing + pulse). These sit INSIDE the mover-chart
   ideas section, whose stock names are 12px mono chart labels — so their
   name/market cells read the same way (mono, ink), NOT in the serif display
   face the analytics tables use; side by side the two looked like different
   products (owner, 2026-07-24). Link styling comes from the app-wide
   table.data rule below. */
table.data.subsec tbody td:first-child,
table.data.subsec tbody td.text { font-family: var(--font-mono); font-size: 12px; }

/* Drill-in links inside ANY data table — the .strat-name-link idiom made
   app-wide (owner, 2026-07-24: the sector pages' GICS tree and constituent
   table rendered as walls of browser-default blue underline, drowning the
   bold/indent hierarchy that carries the structure; same disease previously
   fixed piecemeal on the sub-sector tables). Quiet ink, hover reveals the
   affordance — tables that want the pointer already advertise it row-wide. */
table.data td a { color: var(--ink); text-decoration: none; }
table.data td a:hover { color: var(--accent); text-decoration: underline; }

/* The GICS ladder in a node page's subtitle is a NAV PATH, not content —
   ink-soft like .crumbs/.view-links, so "Energy › Energy › Oil, Gas &
   Consumable Fuels" reads as a quiet trail (that doubling is real GICS:
   sector and industry group share the name) instead of a row of loud blue
   stutters. */
.subtitle a { color: var(--ink-soft); text-decoration: none; }
.subtitle a:hover { color: var(--accent); text-decoration: underline; }

/* ---------- Strategy holdings — header stat tiles ---------- */
.stgy-stats-row {
  display: flex;
  gap: 28px;
  flex-wrap: wrap;
  margin: 8px 0 4px;
}
.stgy-stat { display: flex; flex-direction: column; gap: 2px; }
.stgy-stat-label {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}
.stgy-stat-val {
  font-family: var(--font-mono);
  font-size: 18px;
  color: var(--ink);
}

/* ---------- Month snapshot (turnover timeline click target) ---------- */
.stgy-snapshot-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 16px;
  margin: 20px 0 10px;
}
.stgy-adddrop {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-bottom: 16px;
}
@media (max-width: 700px) {
  .stgy-adddrop { grid-template-columns: 1fr; }
}
.stgy-adddrop-title {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
  margin-bottom: 6px;
}

/* ============================================================
   WAVE-6 additions — Pulse horizon-chip row (shared factor + sector sort),
   filtered-improvers/stretched Ideas lists, and the stock-page H1
   click-to-search switcher (owner decisions, 2026-07-14). Reuses
   .chip-row/.chip, .hint-line, .ideas-cols/.ideas-col*/.idea-card*,
   .xc-suggestions/.xc-suggestion*, .index-chip, table.data thead th.sorted
   wherever the existing component already fit; only genuinely new bits
   below.
   ============================================================ */

/* ---------- Pulse: horizon-chip title line above the leadership bar ---------- */
.pulse-bar-title {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  margin: 4px 0 6px;
}

/* Active-horizon column highlight — factor + sector leadership tables.
   Deliberately subtle (a faint accent wash), and independent of `.sorted`
   (which can point at a different column after a manual header-click). */
table.data thead th.col-hz-active {
  background: rgba(var(--accent-rgb), 0.08);
}

/* ---------- Stock page: H1 click-to-search switcher ---------- */
.stock-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.stock-name-row h1 { cursor: pointer; }
.stock-switch-btn {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  margin-bottom: 8px; /* align with h1's own bottom margin */
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-faint);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.stock-switch-btn:hover { color: var(--accent); border-color: var(--accent); }
.stock-switch-box {
  position: relative;
  flex: 1 1 320px;
  min-width: 240px;
  margin-bottom: 8px;
}
.stock-switch-box input {
  width: 100%;
  font-family: var(--font-mono);
  font-size: 18px;
  padding: 6px 10px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--ink);
}
.stock-switch-box input:focus { border-color: var(--accent); }
.stock-switch-hint {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-faint);
  margin: 4px 0 0;
}
.stock-switch-box .xc-suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 20;
  margin-top: 4px;
}

/* ============================================================
   Global command line / jump palette (topbar, every page — search.js,
   "like a mini Bloomberg" per owner design addendum). Reuses
   .xc-suggestions/.xc-suggestion* (see the stock-switch-box override just
   above for the same "float it as an absolute dropdown" idiom); the topbar
   box itself, the idle "/" kbd hint, the stacked label/sub row layout, and
   the per-type color tags are new.
   ============================================================ */
.fe-search-box {
  position: relative;
  width: 420px;
  flex: 0 0 auto;
}
.fe-search-input {
  width: 100%;
  font-family: var(--font-mono);
  font-size: 13px;
  padding: 8px 30px 8px 12px; /* right-padded so the idle "/" chip has room */
  background: var(--bg-alt); /* terminal-panel tint — distinct from the plain topbar */
  border: 1px solid var(--rule);
  border-radius: 3px;
  color: var(--ink);
}
.fe-search-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(var(--accent-rgb), 0.18);
  outline: none;
}
.fe-search-input::placeholder { color: var(--ink-faint); }
/* Idle-state keyboard-shortcut affordance — visible only while the box is
   empty and unfocused (toggled via style.visibility in search.js, since
   there's no CSS selector for ":input is empty"). Disappears the instant
   you engage with it, same idiom as command-K launchers elsewhere. */
.fe-search-kbd {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1;
  padding: 2px 6px;
  border: 1px solid var(--rule);
  border-radius: 3px;
  color: var(--ink-faint);
  pointer-events: none;
}
.fe-search-box .xc-suggestions.fe-search-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 500; /* above ordinary page content; below .fe-banners (1000) / .filter-panel (2000) */
  margin-top: 4px;
  max-height: 400px;
  background: var(--bg-alt);
}
.fe-search-item { align-items: center; }
.fe-search-item.active { background: var(--bg); color: var(--accent); }
/* Bloomberg-style yellow-key category tag — fixed width so every row's
   label column starts at the same x position regardless of tag text length. */
.fe-search-kind {
  flex: 0 0 54px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 2px 0;
  border-radius: 2px;
  border: 1px solid currentColor;
}
/* Five entity types, five distinct existing tokens — no new colors, just
   assigned so the type reads at a glance across a mixed result list. */
.fe-search-kind--page { color: var(--ink-faint); }
.fe-search-kind--index { color: var(--accent); }
.fe-search-kind--sector { color: var(--ink-soft); }
.fe-search-kind--stock { color: var(--ink); }
.fe-search-kind--strategy { color: var(--warn); }
.fe-search-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}
.fe-search-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fe-search-sub {
  font-size: 11px;
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fe-search-loading { color: var(--ink-faint); cursor: default; }

@media (max-width: 1100px) {
  /* Shrinkable, not rigid. .fe-search-box is flex:0 0 auto at full size, so on a
     tight topbar (long GICS-4 breadcrumb + brand + toggle) it refused to give
     ground and shoved the theme toggle past the viewport — measured 57px of
     page-wide h-scroll at 768px on a sub-industry page. Same failure f90c42b
     fixed at phone width, recurring one breakpoint up because that pass only
     added a ~480px block. The crumbs need min-width:0 for the same reason: an
     unbreakable uppercase trail is otherwise a flex item that cannot shrink. */
  .fe-search-box { width: 280px; flex: 0 1 280px; min-width: 0; }
  .crumbs { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* ============================================================
   Mobile (iPhone-width) pass — 2026-07-24. Additive, scoped to one new
   ~480px breakpoint (below every existing 700-1100px grid collapse, which
   are already inert by this width). iPhone audit found the topbar's
   search box overflowing the viewport on literally every route (it never
   shrinks — flex: 0 0 auto — so it alone forced page-wide horizontal
   scroll everywhere, independent of any per-page content); the rest here
   are the other cross-page findings from that same pass.
   ============================================================ */
@media (max-width: 480px) {
  /* ---------- Topbar: drop the jump-search box to its own full-width row
     below brand/crumbs/toggle instead of holding it to a fixed width wider
     than the viewport. Wrap the bar, then pull the search box after
     everything else (order) with a 100% basis so it's forced onto line 2;
     brand/crumbs/spacer/toggle keep their existing order and alignment on
     line 1 (spacer still pushes the toggle to the right edge). */
  .topbar {
    flex-wrap: wrap;
    padding: 14px 16px 12px;
    row-gap: 10px;
  }
  .fe-search-box {
    order: 5;
    width: auto;
    flex: 1 1 100%;
  }

  /* ---------- view-links row: the wrap this pass added here now lives on the
     base .view-links rule instead — 768px hit the same squeeze, so scoping it
     to phones was too narrow. Kept as a pointer rather than a second copy;
     two rules setting the same property is how row-gap gets silently reset. */

  /* ---------- Chips: a modest tap-target bump (6px -> 9px vertical
     padding) — 11px text in 6px padding is under any comfortable tap
     target. Kept small so dense chip rows (screener column-set picker,
     strategies rail) don't blow past a phone's height budget. */
  .chip { padding: 9px 14px; }

  /* ---------- iOS auto-zooms any focused input/select under 16px font —
     every text-entry control site-wide, not just the search box. */
  .fe-search-input,
  .toolbar input, .toolbar select,
  .compare-pickers input, .compare-pickers select, .axis-pickers select,
  .compare-add-row input,
  .filter-panel-search input, .filter-panel-cond input, .filter-panel-cond select,
  .strat-select,
  #xc-sector-picker-row select,
  .pf-textarea {
    font-size: 16px;
  }

  /* ---------- Axis / compare pickers: each <label> is a flex row wrapping a
     <select> that carries min-width:160px (style.css:859) PLUS the 16px
     anti-zoom bump directly above — together they made the label 393px wide
     inside a 310px content column, pushing the page 43px sideways on
     #/factors. The row already wraps; the LABEL was what couldn't shrink. Give
     each picker its own full-width row and let the select shrink into it. */
  .compare-pickers label, .axis-pickers label { width: 100%; }
  .compare-pickers input, .compare-pickers select,
  .axis-pickers select { min-width: 0; flex: 1 1 auto; }

  /* ---------- Idea cards: .idea-card-chg is flex:0 0 auto + white-space:nowrap
     (style.css:1435), i.e. sized for one short value like "+9.8" — but Pulse's
     entry-trigger cards put a whole sentence in it ("entered 4m ago · PE 18.2 ·
     Comp 61", pulse.js:966), 238px that can neither shrink nor wrap. risk.js:177
     already notes this element is undersized for richer content. On a phone let
     the card wrap so the value takes its own line under the name. */
  .idea-card { flex-wrap: wrap; }
  .idea-card-chg { white-space: normal; }

  /* ---------- Factor-regime matrix (landing, "Now" heatmap): 13 index
     columns at ~48px/column reads fine at 1400px but collides into
     overlapping percentage labels once the canvas shrinks to a ~380px phone
     content column (fleet scout sweep 2, item 5). 640px (~49px/column,
     matching the >=900px desktop density) is the floor .regime-matrix-scroll
     scrolls to reach — proven via scrollWidth > clientWidth at a true 420px
     viewport, not a screenshot. */
  .regime-matrix-scroll .chart-box { min-width: 640px; }
}

/* ============================================================
   Composite breakdown (owner request, 2026-08-11) — "break the composite
   into major components (mtum, returns, growth, valuations) so I can see
   where the movers are".

   All of it is driven by FE_UTIL.comp (util.js), which is the single source
   of the maths AND of the markup these rules style — stock.js, pulse.js,
   landing.js, risk.js, sector.js and screen.js all call the same builders,
   so a colour or spacing change here lands everywhere at once.

   Part colours are --series-1..4 referenced INLINE by the SVG/HTML builders
   (same trick as .fe-attrib-bar/.fe-spark), not repeated here: a theme
   toggle then repaints for free and there is exactly one colour->part
   mapping in the codebase (COMPOSITE_PARTS).
   ============================================================ */

/* Legend + driver dots. 7px, not a text bullet: a bullet inherits the
   surrounding font metrics and sat visibly off-baseline next to 10px mono. */
.comp-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 2px;
  margin-right: 5px;
  vertical-align: baseline;
  flex: 0 0 auto;
}
.comp-legend {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 12px;
}
.comp-legend-item {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-faint);
  white-space: nowrap;
  cursor: help;
}

/* "What drove the move" chips. Deliberately NOT .chip: these are inline
   readouts inside a card row, not tappable filters, and borrowing .chip's
   border+padding made a 3-driver line taller than the idea-card it sits in. */
.comp-drivers {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px 6px;
  margin-top: 3px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-faint);
  line-height: 1.5;
}
.comp-driver { white-space: nowrap; cursor: help; }
.comp-driver-sep { color: var(--rule); }
.comp-driver--flat { font-style: italic; }
/* A driver line inside an <a class="idea-card"> must not turn blue with the
   rest of the link on hover — it is data, not affordance. */
.idea-card:hover .comp-drivers, .idea-card:hover .comp-driver { color: var(--ink-faint); }

/* Stacked contribution bar + diverging attribution bar. vertical-align
   middle keeps them centred in a table cell whose other content is text. */
.fe-comp-stack, .fe-comp-attrib { display: inline-block; vertical-align: middle; }

/* ---------- Stock page: the full breakdown card ----------
   Reuses .identity-card's surface (see the rich/cheap strip precedent) and
   table.data for the numbers; only the headline row and notes are new. */
.comp-card { margin-top: 16px; }
.comp-headline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-bottom: 10px;
}
.comp-score {
  font-family: var(--font-display);
  font-size: 26px;
  line-height: 1;
  color: var(--ink);
}
.comp-score-label {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}
.comp-stack-wrap { margin: 8px 0 6px; }
/* Stretched to the content width; the SVG carries preserveAspectRatio="none"
   so the artwork actually spans this box instead of painting at its intrinsic
   600px, centred (see compositeStackSVG's comment — that bug made the bar
   cover 45% of the axis its own end-labels claimed). */
.comp-stack-wrap .fe-comp-stack { width: 100%; height: 14px; display: block; }
/* The unfilled remainder of the track has to be VISIBLE for the bar to read as
   "56 of 100" rather than as a floating segment: --bg-alt sits ~1% off --bg on
   the light theme, which is invisible in practice. A dedicated rule on the
   full-width instance only — the 90px in-table version is read as a mix, not
   as a level, and a heavier track there would just add noise to 15 rows. */
.comp-stack-wrap .fe-comp-stack > rect:first-child { fill: var(--rule); opacity: 0.35; }
/* The stack is the ONE place a reader could mistake bar length for a
   0-100 axis, so the endpoints are labelled rather than implied. */
.comp-stack-scale {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--ink-faint);
  margin-top: 2px;
}
.comp-note {
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1.6;
  color: var(--ink-faint);
  margin-top: 8px;
}
.comp-note--warn { color: var(--warn); }
/* Total row: same weight-bearing rule as a table foot, without needing a
   <tfoot> in the string builders. */
table.data tr.comp-total-row td {
  border-top: 1px solid var(--ink-faint);
  font-weight: 600;
}
table.data tr.comp-cov-row td { color: var(--ink-faint); font-style: italic; }
/* Part name cell carries its colour dot; keep it from wrapping mid-name. */
table.data td.comp-part-name { white-space: nowrap; }

@media (max-width: 720px) {
  /* The 5-column breakdown table is the one place this feature can force a
     sideways page scroll; let it scroll inside its own wrap like every other
     wide table (.data-table-wrap already does the work). */
  .comp-score { font-size: 22px; }
  .comp-drivers { font-size: 9px; }
}

/* Driver line inside an .idea-card (pulse/risk/sector mover cards). The card
   is a single-line flex row of name | sector | change — a 4th child needs an
   explicit wrap or it squeezes the name to nothing instead of dropping to its
   own line. Scoped to a modifier so cards WITHOUT drivers keep the exact
   single-line geometry they have today (the same reason
   .idea-card-sector--sub exists rather than widening the base class). */
.idea-card--comp { flex-wrap: wrap; }
.idea-card--comp .comp-drivers { flex: 1 1 100%; margin-top: 2px; }

/* View-links leading icons (owner brief 2026-08-12): FE_ICONS.viewFor()
   prepends a wrapIcon() span (see fe-icons.js) inside the anchor. Left at
   default `inline`, that span's own `vertical-align` (tuned against normal
   sentence-case body/table text) sits wrong against .view-links' uppercase,
   letter-spaced mono text — the box reads a few px high or low depending on
   font metrics. inline-flex + align-items:center re-centers the icon against
   the label's own line box regardless of font, which a fixed vertical-align
   offset can't guarantee across every caller. `flex: 0 0 auto` keeps the
   icon at its wrapped 14x14 instead of stretching to fill the row.
   No `gap` here — wrapIcon() already ships an inline margin-right sized to
   match (5px at 14px), and inline `style=` always wins over a stylesheet
   rule, so a gap would stack on top of it rather than replace it. Links with
   no icon (unknown key, or fe-icons.js not loaded) get an empty leading
   string and are unaffected. */
.view-links a { display: inline-flex; align-items: center; }
.view-links a > span { flex: 0 0 auto; }

/* Anchor-as-button (2026-08-18). .btn/.btn-mini were written for <button>,
   which never underlines, so neither sets text-decoration — an <a> carrying
   those classes therefore rendered with the UA's default underline, visibly
   odd next to the real <button>s beside it in the same toolbar (spotted on
   xcompare's "Screen these N →"). Anchors are the right element for these:
   they navigate, so middle-click / open-in-new-tab must keep working. Scoped
   to a[class] usage only, so <button> styling is untouched. */
a.btn, a.btn-mini { text-decoration: none; display: inline-block; }

/* =====================================================================
   Cheatsheet project, stage V3 (2026-09-14) — Pulse's "Suggestions —
   research-backed" section (pulse.js's renderSuggestionsPanel). Deliberately
   its OWN block, separate from stage V1's .cs-* rules elsewhere in this
   file (a different section of the same research/cheatsheet effort) — do
   not merge the two; this one is scoped to the `.cs-sugg-` prefix only, so
   they can never collide or need to be told apart at a glance.
   ===================================================================== */

/* Bar-chart placeholder host for the sector-rotation group picks (stage V2
   fills this in). Reserves real vertical space up front so the layout
   doesn't jump once the chart is dropped in on a later build. Sized for the
   GICS_INDUSTRY_GROUP_NAME case (top-5 + bottom-5 = 10 category rows, owner
   decisions 2026-09-14 top / 2026-09-15 bottom); other levels render fewer
   rows into the same host. */
.cs-sugg-chart {
  height: 320px;
  margin: 10px 0 14px;
  border: 1px dashed var(--rule);
  border-radius: 3px;
}

/* Horizon chip: same visual language as every other .chip on this page
   (chip-row/.chip above), but inert here — it's a label pulled straight
   from the payload's own `horizon` string, not a click target, so it never
   gets `.active`/hover affordances of its own. */
.cs-sugg-chip { cursor: default; }
.cs-sugg-chip:hover { border-color: var(--rule); color: var(--ink-soft); }

/* Reuses table.data's sortable-header look as-is; nothing sugg-specific to
   override there. This selector exists only so a future tweak scoped to
   just this section's tables has somewhere to land without touching
   table.data globally. */
.cs-sugg-table { }

/* "Pending robustness check" notice — replaces the group table entirely
   when suggGroupsWarned() finds this index named in the payload's
   warnings[], per the owner's "withhold, don't just flag" instruction.
   Uses the --warn token (same family as .fe-banner--warn/.tile-chip--warn)
   for a payload-quality caveat, not an application error, laid out inline
   in the page flow rather than as a fixed corner toast. */
.cs-sugg-notice {
  padding: 12px 14px;
  margin: 8px 0 14px;
  border: 1px solid var(--warn);
  border-radius: 3px;
  background: var(--bg-alt);
  color: var(--ink-soft);
  font-size: 12px;
  line-height: 1.5;
}

/* =====================================================================
   Cheatsheet project, stage V1 (2026-09-14) — the #/research view ("What
   the signals are actually worth"): the verdict grid, legend, claims,
   evidence-ladder dossiers, overlap notice, backlog and provenance footer.
   New `.cs-*` namespace only; no existing selector touched. See stage V3's
   `.cs-sugg-*` block above (deliberately separate — different section of
   the same effort, never merged with this one).
   ===================================================================== */

.cs-header-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
  flex-wrap: wrap;
}
.cs-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  text-align: right;
  white-space: nowrap;
}

/* ---- The verdict grid: 7 family rows x 2 level columns ---- */
.cs-grid {
  margin: 20px 0 16px;
  border-top: 1px solid var(--rule);
}
.cs-grid-row,
.cs-grid-row--head {
  display: grid;
  /* minmax(0,…) on every track, not just 1fr shorthand — style.css's own
     .chart-grid-2 note above documents the min-content inflation bug this
     avoids (an unconstrained 1fr track still respects its content's
     min-width, which blew out a 1024px layout by 211px there). */
  grid-template-columns: 130px minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
  border-bottom: 1px solid var(--rule);
  padding: 10px 0;
}
.cs-grid-row--head {
  border-bottom: none;
  padding-bottom: 4px;
}
.cs-grid-rowhead {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  color: var(--ink);
  display: flex;
  align-items: center;
}
.cs-grid-colhead {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}
.cs-grid-cell { min-width: 0; }
/* The app's base table rule is `.data th, .data td { white-space: nowrap }` — right
   for numeric screens, wrong for the Research tab's PROSE tables: the backlog table
   measured 4,943px wide (1,161px per .text cell) and scrolled horizontally on every
   screen. Prose columns wrap; numeric columns keep the base rule. */
.cs-backlog td.text, .cs-backlog th.text,
.cs-detail-table td.text, .cs-detail-table th.text,
.cs-sources td.text, .cs-sources th.text,
.cs-universes td.text, .cs-universes th.text {
  white-space: normal;
  overflow-wrap: anywhere;
  min-width: 14em;
  max-width: 38em;
  vertical-align: top;
}
/* The eyebrow (level name) is redundant with the column header above 720px
   — it only earns its keep once the grid collapses to one column below. */
.cs-grid-cell .cs-tile__eyebrow { display: none; }

/* ---- The tile: the ONE cell-renderer's output, used by the grid, the
   legend (as dummy rows) and every dossier's <summary>. Verdict drives the
   surface; grade is a separate rail badge layered on top of it. ---- */
.cs-tile {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  border-radius: 4px;
  min-width: 0;
  text-decoration: none;
  color: var(--ink);
}
a.cs-tile { cursor: pointer; }
a.cs-tile:hover { filter: brightness(1.04); }

.cs-tile--neutral { background: var(--bg-alt); border: 1px solid var(--rule); }

.cs-tile--works { background: var(--accent); color: var(--accent-contrast); }
.cs-tile--works .cs-tile__eyebrow,
.cs-tile--works .cs-tile__meta,
.cs-tile--works .cs-tile__sources { color: var(--accent-contrast); opacity: 0.75; }

.cs-tile--weak { background: transparent; outline: 1px solid var(--accent); color: var(--ink); }

.cs-tile--nil { background: rgba(var(--warn-rgb), 0.06); outline: 1px solid var(--warn); color: var(--ink); }

/* not_measured — must look visibly different from nil at a glance: dashed
   border (not solid), no grade rail (the renderer simply never emits one,
   since a not_measured cell always has grade:null), a flat heat-0 fill, and
   a low-opacity hatch that survives greyscale/CVD rendering — a colour
   alone is not enough here per the dataviz-safety convention this app
   already follows for its categorical series. */
.cs-tile--not_measured {
  background-color: var(--heat-0);
  background-image: repeating-linear-gradient(
    45deg, rgba(128, 128, 128, 0.10) 0 6px, transparent 6px 14px
  );
  outline: 1px dashed var(--rule);
  color: var(--ink-faint);
}

.cs-tile--sm { padding: 6px 8px; gap: 3px; }

.cs-tile__eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}
.cs-tile__top { display: flex; align-items: center; gap: 6px; }
.cs-tile__headline { font-size: 13px; line-height: 1.4; }
.cs-tile--sm .cs-tile__headline { font-size: 12px; }
/* Grid tiles clamp to 2 lines — the full sentence lives in the dossier this
   tile links to. Without this the hero (7 rows x full-sentence headlines)
   runs well past the fold; the legend/dossier-summary uses of this same
   markup (.cs-tile--sm) are short strings already and are unaffected. */
.cs-grid-cell .cs-tile__headline {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.cs-tile__stat { font-family: var(--font-mono); font-size: 12px; }
.cs-tile__stat--untested { color: var(--ink-faint); font-style: italic; }
.cs-tile__t { font-family: var(--font-mono); font-size: 11px; color: var(--ink-soft); }
.cs-tile--works .cs-tile__t { color: var(--accent-contrast); opacity: 0.85; }
.cs-tile__meta { font-size: 11px; color: var(--ink-soft); }
.cs-tile__sources { font-family: var(--font-mono); font-size: 10px; color: var(--ink-faint); }

/* Verdict chip — small pill echoing the tile's own surface colour so the
   legend's standalone chip and the grid's in-tile chip never disagree. */
.cs-chip {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 2px 7px;
  border-radius: 999px;
}
.cs-chip--works { background: rgba(255, 255, 255, 0.22); color: inherit; }
.cs-chip--weak { border: 1px solid var(--accent); color: var(--accent); }
.cs-chip--nil { border: 1px solid var(--warn); color: var(--warn); }
.cs-chip--not_measured { border: 1px dashed var(--ink-faint); color: var(--ink-faint); }

/* Grade rail badge — A/B/C, layered beside the verdict chip, never instead
   of it (a cell's verdict and its evidence grade are different axes). */
.cs-rail {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 3px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
}
.cs-rail--a { background: var(--accent); color: var(--accent-contrast); }
.cs-tile--works .cs-rail--a { background: rgba(255, 255, 255, 0.28); color: inherit; }
.cs-rail--b { border: 1px solid var(--ink-soft); color: var(--ink-soft); }
.cs-rail--c { border: 1px solid var(--ink-faint); color: var(--ink-faint); }

/* ---- Legend: renders the same tile/chip/rail markup as the grid, via
   dummy cell objects, so the two vocabularies cannot drift apart. ---- */
.cs-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 20px 40px;
  margin: 4px 0 12px;
  padding: 12px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
/* Column, not a wrapped row: the verdict/grade descriptions are full
   sentences (one carries a PLAN.json cross-reference), not short labels —
   a wrapped row of max-width tiles reads as a wall of clipped text. */
.cs-legend-group { display: flex; flex-direction: column; gap: 10px; flex: 1 1 320px; min-width: 260px; }
.cs-legend-item { display: flex; align-items: flex-start; gap: 10px; }
.cs-legend-item .cs-tile { flex: 0 0 auto; }
.cs-legend-desc { font-size: 11px; color: var(--ink-soft); line-height: 1.45; }
.cs-rules { color: var(--ink-soft); font-size: 12px; font-style: italic; line-height: 1.6; margin-bottom: 20px; }

/* ---- The three headline claims ---- */
.cs-claims { display: flex; flex-direction: column; gap: 14px; margin-bottom: 24px; }
.cs-claim { display: flex; gap: 14px; align-items: flex-start; }
.cs-claim__num {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--accent);
  flex: 0 0 auto;
  line-height: 1;
}
.cs-claim__text { font-family: var(--font-display); font-size: 16px; line-height: 1.45; }
.cs-claim__stat { font-family: var(--font-mono); font-size: 12px; color: var(--ink-soft); margin-top: 4px; }
.cs-claim__jump { font-size: 12px; margin-top: 4px; display: inline-block; }

/* ---- Evidence ladder: 14 collapsible dossiers, one per cell ---- */
.cs-ladder { display: flex; flex-direction: column; gap: 8px; margin-bottom: 24px; }
.cs-dossier {
  border: 1px solid var(--rule);
  border-radius: 4px;
  overflow: hidden;
}
.cs-dossier__summary {
  cursor: pointer;
  padding: 8px;
  list-style: none;
}
.cs-dossier__summary::-webkit-details-marker { display: none; }
.cs-dossier__summary .cs-tile { pointer-events: none; }
.cs-dossier__body { padding: 4px 16px 16px; border-top: 1px solid var(--rule); }
.cs-dossier__block { margin: 14px 0; }
.cs-dossier__block h4 {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin: 0 0 6px;
}
.cs-dossier__message { font-family: var(--font-display); font-size: 14px; line-height: 1.5; }
.cs-dossier__table-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; }

/* Chart placeholder — stage V2 fills this in. Reserves space so the layout
   doesn't jump once a chart lands. */
.cs-chart {
  height: 260px;
  margin: 8px 0;
  border: 1px dashed var(--rule);
  border-radius: 3px;
}

.cs-list { margin: 0; padding-left: 18px; font-size: 12px; line-height: 1.6; color: var(--ink-soft);
  overflow-wrap: anywhere;   /* long source paths must wrap, not clip, inside .cs-dossier{overflow:hidden} */
}
.cs-list--mono { font-family: var(--font-mono); font-size: 11px; }

/* ---- Notices: the overlap-correction rule, local-only footer, and any
   per-cell staleness flag. Not a footnote — deliberately visible, never
   using --warn as a background alone (contrast) or --series-3 (categorical
   slot, not a severity step — see the palette note at the top of this
   file). ---- */
.cs-notice {
  padding: 14px 16px;
  margin: 16px 0;
  border: 1px solid var(--rule);
  border-radius: 4px;
  background: var(--bg-alt);
  font-size: 13px;
  line-height: 1.55;
  overflow-wrap: anywhere;   /* file paths and code tokens in the notice must wrap: a 350px token in a 286px box was the 7px page overflow at 400px */
}
.cs-notice code { white-space: normal; overflow-wrap: anywhere; }
.cs-notice h3 { margin: 0 0 8px; font-family: var(--font-display); font-size: 16px; }
.cs-notice--overlap { border-color: var(--accent); }
.cs-notice--warn {
  border: 1px solid var(--warn);
  background: rgba(var(--warn-rgb), 0.06);
  color: var(--ink);
  font-size: 12px;
  padding: 8px 12px;
  margin: 0 0 12px;
}

.cs-empty { display: flex; flex-direction: column; gap: 8px; align-items: center; }
/* .state uppercases + letter-spaces everything inside it (style.css:271) —
   fine for its usual short status line, wrong for a copyable shell command
   or a sentence of prose, both of which .cs-empty nests inside it. */
.cs-empty code, .cs-empty .hint-line { text-transform: none; letter-spacing: normal; }

.cs-csv-btn { font-size: 11px; padding: 3px 10px; }

@media (max-width: 720px) {
  .cs-grid-row,
  .cs-grid-row--head {
    grid-template-columns: 1fr;
    gap: 6px;
  }
  .cs-grid-row--head { display: none; } /* column labels move to each tile's own eyebrow instead */
  .cs-grid-cell .cs-tile__eyebrow { display: block; }
  .cs-grid-rowhead {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ink-faint);
    padding-top: 4px;
  }
  .cs-header-row { flex-direction: column; }
  .cs-meta { text-align: left; }
}

/* =====================================================================
   Consensus-revision cards (2026-09-15) - the "Consensus revision" panel
   on the sector/industry-group dossier (#/sector/{IDX}/...) and on the
   stock drill-in (#/stock/{IDX}/{id}), SPX and RAY only, fed by
   data/research/revisions_{IDX}.js. That payload is Bloomberg-derived
   per-name data: gitignored in the data repo, regenerated locally by
   research/revisions/build_entity_payload.py and present only on the
   office checkout, so on any other machine the cards are absent and none
   of this block applies.

   Only two selectors are new. Everything else on those panels reuses what
   already exists - .chart-box for the chart host, .data-table-wrap +
   table.data for the members/factors tables, .pct-chip--rank for a
   percentile cell, .hint-line for the provenance line - so the cards read
   as the same furniture as the panels above and below them.
   ===================================================================== */

/* "Suggested" / "In top decile" marker: this name is in the index's own
   published top-decile revision list (research/revisions/
   current_suggestions.json, the same list Pulse's Suggestions panel
   shows). Outline rather than fill - it sits inline after a ticker link
   inside a dense table row, where a filled pill would out-shout the link
   it annotates. --accent is the app's positive-signal token. */
.rv-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 5px;
  border: 1px solid var(--accent);
  border-radius: 2px;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1.5;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
  vertical-align: middle;
}

/* Card-title flag: this card's own payload (Bloomberg-derived, its own
   build cadence) is behind the index's own latest month — same idiom as
   .rv-badge, --warn instead of --accent since this is a caution, not a
   positive marker. */
.rv-stale {
  display: inline-block;
  padding: 1px 5px;
  border: 1px solid var(--warn);
  border-radius: 2px;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1.5;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--warn);
  white-space: nowrap;
  vertical-align: middle;
  cursor: help;
}

/* Chart heights. Both charts carry a wrapping legend plus two value axes,
   so they need more room than .chart-box's default; the stock one carries
   up to five series and gets a little more again. Scoped modifiers rather
   than inline style= so a height tweak is a stylesheet edit, the same way
   .chart-box--tall / .chart-box--growth-map already work. */
.chart-box--rev { height: 300px; }
.chart-box--rev-stock { height: 320px; }

/* Provenance line under a card's table. .hint-line carries margin-top:-8px
   so it can tuck under a heading, which is wrong immediately after a
   .data-table-wrap - it overlapped the table's last row. Reset it here
   rather than weaken .hint-line, which every other caller relies on. */
.hint-line.rv-prov { margin-top: 12px; }