/* === fonts === */
/* === Self-hosted typefaces ===
   Inter and JetBrains Mono were loaded from fonts.googleapis.com, which meant
   every visitor's IP and User-Agent went to Google before the page painted.
   That is the one piece of this stack that quietly contacted a third party —
   Ollama is asserted to hold no external sockets, speech runs on our own GPU,
   the indicator reader is local and read-only — and it was doing it on the
   front page.

   It also caused a visible bug. Only the MARKETING pages carried the link, so
   athrive.ai rendered the wordmark in Inter while /chat rendered it in
   system-ui. The two lockups could never match no matter how much CSS they
   shared, because they were different typefaces.

   Variable fonts, one file per family per subset, latin and latin-ext only:
   the site is English and shipping Cyrillic, Greek and Vietnamese would have
   tripled the bytes for glyphs nothing here renders. */

/* Inter — latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 900;
  /* swap: text is readable in the fallback immediately and reflows once the
     real face arrives. block would show nothing for up to 3s; optional would
     often never use the font we just shipped. */
  font-display: swap;
  src: url('/assets/fonts/inter.a28eb6d3cc.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Inter — latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 900;
  /* swap: text is readable in the fallback immediately and reflows once the
     real face arrives. block would show nothing for up to 3s; optional would
     often never use the font we just shipped. */
  font-display: swap;
  src: url('/assets/fonts/inter.c940764593.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* JetBrains Mono — latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 500;
  /* swap: text is readable in the fallback immediately and reflows once the
     real face arrives. block would show nothing for up to 3s; optional would
     often never use the font we just shipped. */
  font-display: swap;
  src: url('/assets/fonts/jetbrainsmono.9c38cb2d0d.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* JetBrains Mono — latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 500;
  /* swap: text is readable in the fallback immediately and reflows once the
     real face arrives. block would show nothing for up to 3s; optional would
     often never use the font we just shipped. */
  font-display: swap;
  src: url('/assets/fonts/jetbrainsmono.2c32b9b3ee.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* === tokens === */
/* Light is the default and the brand: warm cream and charcoal, editorial.
   Dark is a genuine second palette rather than an inverted first one — the
   cream becomes a warm near-black rather than pure #000, so the page keeps the
   same temperature and does not read as a different product at night. */
:root {
  color-scheme: light;

  /* Brand: Warm Cream + Charcoal — editorial, premium */
  /* SOFT LIGHT. --surface was #FFFFFF, and on a dashboard the cards cover most
     of the screen — so the surface the eye actually spent its time on was pure
     white at L* 100, not the warm cream page behind it. No surface is pure
     white now; the card stays lighter than the page so the depth cue is
     unchanged, it just stops glaring. Text contrast falls 17.5:1 -> 16.4:1,
     still far above the 7:1 AAA bar. */
  --paper: #F6F2E9;          /* warm cream, dimmed from #FBF9F4 */
  --paper-rgb: 246, 242, 233;
  --ink: #0E1422;            /* deep navy charcoal */
  --ink-rgb: 14, 20, 34;
  --ink-2: #1F2937;
  /* Measured against the DARKEST light ground, not the page.
     #6B7280 was 4.59:1 on --paper and passed, but --muted is used throughout
     the sidebar, which sits on --surface-2 (#F4F0E6) — and there it was
     4.25:1, under AA. Group headers, search snippets and the empty state were
     all failing on the one surface nobody thought to check them against.
     #646B77 clears 4.5 on all three light grounds: 5.10 paper, 5.37 surface,
     4.72 surface-2. */
  --muted: #646B77;
  --body: #4B5563;           /* body copy on paper — 7.3:1 on --paper */
  --line: #E5DFD1;           /* warm hairline, re-darkened to stay visible on the dimmer paper */
  --line-strong: #CFC7B4;   /* also the sidebar divider — 1.60:1 on --paper */
  --surface: #FCFAF5;        /* cards — was #FFFFFF */
  --surface-2: #EDE8DB;      /* recessed */

  /* Accents */
  --accent: #1F3A5F;         /* deep blue */
  --accent-rgb: 31, 58, 95;
  --accent-2: #B7956F;       /* warm bronze */
  --accent-2-rgb: 183, 149, 111;
  --accent-2-ink: #7A5F3E;   /* bronze darkened for text on light — 4.6:1 */

  /* Logo gradient stops. Read by the inline SVG sprite (symbol-mark.html) so
     the mark follows the theme instead of being a fixed image that only suits
     one of them. The crossbar is punched in --paper, not listed here. */
  --logo-1: #1F3A5F;
  --logo-2: #0E1422;
  --logo-3: #B7956F;

  /* Direction. Up and down are the only semantic colours in the product and
     they were hardcoded in dashboard.css, so dark mode never reached them:
     #1B6B3A measured 2.52:1 and #9B1C1C 2.02:1 on #1F1F1F — present in the
     markup, invisible on screen.
     Chosen for MATCHED contrast, 6.25 and 6.21 here, so neither direction
     shouts louder than the other. Colour is never the only signal: every
     direction also carries a glyph or a sign, because red/green is exactly the
     pair ~8% of men cannot separate. */
  --up: #146B3A;
  --down: #B3261E;

  /* Error surfaces. Same story as direction: every error box on the site was
     three literal hexes in a component, so in dark mode a failure painted a
     near-white #FDF2F2 panel across a #1F1F1F page. */
  --danger: #B3261E;
  --danger-bg: #FDF2F2;
  --danger-line: #F5C2C2;

  /* Attention — "this is the loud one", distinct from danger ("this failed").
     It was already being used five times as `var(--warning, #a87317)` against
     a token that was never defined, so every one of those resolved to the
     literal fallback — including in dark mode, where #a87317 measures 2.6:1 on
     #1F1F1F and the market-relevant badge was effectively invisible. Defining
     it is not a retune of an existing value: nothing was there. */
  --warning: #A05E00;        /* 4.79:1 on --paper, 4.62:1 on --surface-2 */
  --warning-rgb: 160, 94, 0;

  /* Typography */
  --font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Geometry */
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --shadow-lg: 0 30px 80px -20px rgba(14, 20, 34, 0.18);

  --nav-h: 76px;

  /* Fluid metrics — scale continuously with the viewport rather than
     snapping at a single breakpoint. clamp() floors them on phones and
     ceilings them so a 4K monitor doesn't get absurd whitespace. */
  --container: 1280px;
  --gutter: clamp(20px, 4vw, 40px);
  --section-y: clamp(56px, 7vw, 96px);
  --stack-lg: clamp(28px, 4vw, 56px);
  --stack-md: clamp(20px, 2.6vw, 36px);

  /* --- data surfaces -------------------------------------------------------
     Spacing only. There is deliberately no font-size token here: density on a
     data page comes from padding, gaps and hairlines, never from shrinking the
     type, and a size token would make the wrong fix the easy one. */
  --data-row-y: 8px;         /* vertical padding inside a data cell */
  --data-row-x: 14px;        /* horizontal padding inside a data cell */
  --data-gap: 14px;          /* between items in a metric strip / control row */
  --data-stack: 8px;         /* between the bands stacked above a table */
  --pane-max: 70vh;          /* how tall a scrolling data pane may grow */
}

/* Wide monitors: let the measure grow instead of stranding 600px of margin
   on either side. Content stays readable because the inner grids are fluid. */
@media (min-width: 1700px) {
  :root { --container: 1440px; }
}
@media (min-width: 2200px) {
  :root { --container: 1600px; }
}

/* Short landscape viewports (laptops at 1366x768, phones on their side)
   — trim the vertical rhythm so a section still fits on one screen. */
@media (max-height: 720px) and (min-width: 900px) {
  :root { --section-y: clamp(48px, 6vh, 72px); }
}

@media (max-width: 900px) {
  :root { --nav-h: 64px; }
}


/* --- dark --- */
/* Applied two ways on purpose:
     @media  — follows the operating system for anyone who never touches the
               toggle, which is most people
     [data-theme] — the explicit choice, written to localStorage and stamped on
               <html> before first paint by the inline script in each page head
   The attribute rules come last so a deliberate choice always beats the system
   preference, in BOTH directions: someone on a dark OS who picks light must
   get light. */
/* Dark is driven ONLY by [data-theme] on <html>, never by a bare
   @media (prefers-color-scheme: dark).
   This stylesheet is global. A media block here would repaint the marketing
   pages too, and their sections carry their own literal colours — the result
   is a half-dark home page nobody asked for. Instead the app pages run the
   inline boot script (components/theme-boot.html), which resolves the stored
   choice or the system preference and stamps the attribute before first
   paint. System preference is still honoured; it is just honoured where we
   control the outcome. */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* VS Code "Dark Modern". Note the direction: the side bar (#181818) is
     DARKER than the editor (#1F1F1F), which is the reverse of most dark
     themes and the reason the rail reads as chrome rather than as another
     panel. --surface-2 is our side bar, so it takes the darker value. */
  --paper: #1F1F1F;          /* editor background */
  --paper-rgb: 31, 31, 31;
  --ink: #CCCCCC;            /* editor foreground */
  --ink-rgb: 204, 204, 204;
  --ink-2: #BBBBBB;
  --muted: #9D9D9D;          /* description text — 6.08:1 on --paper */
  --body: #CCCCCC;           /* 10.26:1 on --paper */
  --line: #2B2B2B;
  /* Also the sidebar divider, which on a shared ground is the only thing
     separating the rail from the thread — #3C3C3C reads at 1.49:1 there,
     which is the low end of perceptible. Nudged up so the edge is definite
     without becoming a drawn line. */
  --line-strong: #454545;
  --surface: #2A2A2A;        /* inputs, hover states — raised above the page */
  --surface-2: #181818;      /* side bar / activity bar */

  /* VS Code's link blue on dark. The brand #1F3A5F is 2.1:1 here and
     unreadable; #4DAAFC carries at 6.65:1 and is the same hue family.
     All five foreground tokens measured AA or better against both the editor
     ground and the darker side bar — see the ratios beside each. */
  --accent: #4DAAFC;
  --accent-rgb: 77, 170, 252;
  --accent-2: #D6B98F;
  --accent-2-rgb: 214, 185, 143;
  --accent-2-ink: #E0C9A6;

  --shadow-lg: 0 30px 80px -20px rgba(0, 0, 0, 0.7);

  /* The dark variant of the mark, DERIVED rather than hand-picked.
     Hue and saturation are carried over unchanged from the light artwork and
     only lightness is mirrored around 50%. That keeps it recognisably the
     same logo: same hues, same tonal rhythm, inverted.

       #1F3A5F  H215 S51 L25  ->  #A0BBE0  L75   8.38:1 on #1F1F1F
       #0E1422  H222 S42 L 9  ->  #DDE3F1  L91  12.82:1
       #B7956F  H 32 S33 L58  ->  #906E48  L42   3.54:1  (clears 3:1 non-text)

     The first attempt was picked by eye and drifted: the middle stop became
     #E6E6E6, a neutral grey 222 degrees off the brand's blue, so the dark mark
     had no blue in its centre at all and read as a different logo rather than
     the same one at night.

     Reusing the light artwork unchanged is not an option, and not as a matter
     of taste — two of its three stops are DARKER than the dark ground:
     #1F3A5F measures 1.44:1 and #0E1422 measures 1.12:1 on #1F1F1F. The mark
     would lose everything except its bronze tip. */
  /* Matched again on the dark ground: 7.26:1 and 7.26:1. The light values are
     far too dark to survive here — that is the whole reason these are tokens
     now rather than two literals in a component. */
  --up: #34C46B;
  --down: #FF8A8A;

  /* The panel has to read as a panel — #2A1A1A was only 1.01:1 from the page,
     an error box you could not see the edges of. #412826 is 1.22:1 against the
     page and still holds the text at 5.95:1. */
  --danger: #FF8A8A;
  --danger-bg: #412826;
  --danger-line: #5C3835;

  /* The dark half of the attention colour the components had been asking for.
     Warm amber rather than the light palette's brown, for the same reason --up
     and --down change value here: the light one is 2.6:1 on this ground. */
  --warning: #E0A458;        /* 8.28:1 on --paper, 6.94:1 on --surface */
  --warning-rgb: 224, 164, 88;

  --logo-1: #A0BBE0;
  --logo-2: #DDE3F1;
  --logo-3: #906E48;
}

:root[data-theme="light"] { color-scheme: light; }

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

/* `hidden` must win over any component's display value. Without this, a
   `display: inline-flex` class (e.g. .exec-auto) silently un-hides the element. */
[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;
  /* Keep anchor targets clear of the sticky nav. Honoured by Chrome 69+,
     Safari 14.1+, Firefox 68+; main.js offsets manually for the rest. */
  scroll-padding-top: calc(var(--nav-h) + 16px);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-synthesis: none;
  font-optical-sizing: auto;
  font-feature-settings: "cv02", "cv03", "cv04", "cv11";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
}

/* Financial figures should not jump horizontally as values refresh. */
.mono, [class*="-number"], [class*="-value"] { font-variant-numeric: tabular-nums; }

/* Safety net against sideways scroll on phones. `clip` rather than `hidden`
   on purpose: `hidden` makes the root a scroll container, which historically
   breaks `position: sticky` on the nav in WebKit. `clip` never does, and
   browsers without it simply go without the net — the real overflow sources
   are fixed at source below. */
@supports (overflow: clip) {
  html { overflow-x: clip; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

a { color: inherit; text-decoration: none; }
img, svg, video { display: block; max-width: 100%; height: auto; }

input, select, textarea, button { font: inherit; color: inherit; }
input, select, textarea { font-size: 16px; } /* < 16px triggers iOS zoom-on-focus */
button { background: none; border: 0; margin: 0; }

h1, h2, h3, h4, p { overflow-wrap: break-word; word-wrap: break-word; }

/* ---------------------------------------------------------------------------
   Focus. Keyboard users get a visible ring everywhere; :focus-visible keeps it
   off mouse clicks. Browsers without :focus-visible fall back to :focus, so the
   ring is never missing — only occasionally over-eager.
   --------------------------------------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}
@supports not selector(:focus-visible) {
  a:focus, button:focus, [tabindex]:focus {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }
}
.btn-primary:focus-visible { outline-color: var(--accent-2); }
.dark :focus-visible { outline-color: var(--accent-2); }
/* Programmatic focus on a scroll target must not draw a ring around a section */
section:focus, header:focus, main:focus, [tabindex="-1"]:focus { outline: none; }

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

.skip-link {
  position: absolute;
  left: 12px;
  top: -100px;
  z-index: 100;
  padding: 12px 18px;
  background: var(--ink);
  color: var(--paper);
  border-radius: var(--radius-md);
  font-size: 14px;
  font-weight: 500;
  transition: top .15s ease;
}
.skip-link:focus { top: 12px; }

.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter);
}
@supports (padding: max(0px)) {
  .container {
    padding-left: max(var(--gutter), env(safe-area-inset-left));
    padding-right: max(var(--gutter), env(safe-area-inset-right));
  }
}

/* ---------------------------------------------------------------------------
   Gradient text.
   `color: transparent` on its own renders the headline INVISIBLE anywhere
   background-clip:text is missing (old WebKit views, Opera Mini, IE, forced
   colours). So: paint a real colour first, and only knock the fill out inside
   an @supports guard — using -webkit-text-fill-color, which no browser honours
   without also supporting the clip.

   The gradient itself is passed in per element as --grad and is applied ONLY
   inside the guard. Two reasons it lives here rather than on the element:

   1. `background: linear-gradient(...)` is a SHORTHAND, so it resets
      background-clip back to border-box. Section CSS is concatenated after
      this file (tokens → base → components → sections, see scripts/build.js),
      so any such shorthand silently un-clipped the text and painted the
      gradient across the whole box with a transparent fill on top — i.e. a
      solid gradient slab where the headline should be.
   2. Applying it under the guard means the no-support fallback gets no
      background at all, just real painted text, instead of dark text sitting
      on a full-bleed gradient block.

   Set --grad on the element; never set `background`/`background-image` on a
   .grad-text* element directly.
   --------------------------------------------------------------------------- */
.grad-text        { color: var(--ink); }
.grad-text-accent { color: var(--accent); }
.grad-text-light  { color: #fff; }

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .grad-text, .grad-text-accent, .grad-text-light {
    /* Fallback is a flat currentColor "gradient", not `none`: an element that
       gets a .grad-text class but forgets --grad then paints as ordinary
       coloured text instead of vanishing under the transparent fill. */
    background-image: var(--grad, linear-gradient(currentColor, currentColor));
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
  }
}

/* Windows High Contrast / forced-colors strips backgrounds — restore the fill
   or the headline vanishes again. */
@media (forced-colors: active) {
  .grad-text, .grad-text-accent, .grad-text-light {
    background: none !important;
    -webkit-text-fill-color: currentColor;
    color: CanvasText;
  }
}

/* Scroll-reveal: sections fade up as they enter the viewport. The hidden state
   is applied by main.js only after the observer is confirmed working, so a
   no-JS or failed-JS load never leaves the page blank. */
.reveal {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity .55s cubic-bezier(.2,.6,.2,1), transform .55s cubic-bezier(.2,.6,.2,1);
  will-change: opacity, transform;
}
.reveal.is-revealed {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  will-change: auto; /* release the compositor layer once the animation is done */
}
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
}

.mono { font-family: var(--font-mono); }

.eyebrow {
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 12px;
}
.eyebrow::before {
  content: "";
  width: 28px;
  height: 1px;
  flex: none;
  background: linear-gradient(to right, var(--accent-2), rgba(var(--accent-2-rgb), 0));
}

/* Section accents — subtle warm tint variation between bands */
.tint-cream { background: linear-gradient(180deg, var(--paper) 0%, var(--surface-2) 100%); }
.tint-bronze {
  background:
    radial-gradient(800px 400px at 90% 0%, rgba(var(--accent-2-rgb), 0.08), transparent 60%),
    var(--paper);
}
.tint-blue {
  background:
    radial-gradient(900px 500px at 10% 100%, rgba(var(--accent-rgb), 0.06), transparent 60%),
    var(--paper);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 18px;
  border-radius: var(--radius-md);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.2;
  transition: transform .15s ease, background-color .15s ease, border-color .15s ease, box-shadow .2s ease, color .15s ease;
  cursor: pointer;
  border: 1px solid transparent;
  font-family: inherit;
  letter-spacing: 0.005em;
  text-align: center;
}
.btn:active { transform: translateY(1px); }
.btn-primary {
  background: var(--ink);
  color: var(--paper);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.08) inset,
    0 1px 2px rgba(var(--ink-rgb), 0.18),
    0 6px 18px -10px rgba(var(--ink-rgb), 0.45);
}
.btn-primary:hover {
  background: #000;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.1) inset,
    0 2px 4px rgba(var(--ink-rgb), 0.22),
    0 12px 28px -12px rgba(var(--ink-rgb), 0.55);
  transform: translateY(-1px);
}
.btn-accent {
  background: var(--accent);
  color: #fff;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.1) inset,
    0 6px 18px -10px rgba(var(--accent-rgb), 0.55);
}
.btn-accent:hover {
  background: #18304d;
  transform: translateY(-1px);
  box-shadow: 0 12px 28px -12px rgba(var(--accent-rgb), 0.65);
}
.btn-ghost { color: var(--ink); }
.btn-ghost:hover { background: rgba(var(--ink-rgb), 0.05); }
.btn-outline {
  border-color: var(--line-strong);
  color: var(--ink);
  background: rgba(255, 255, 255, 0.4);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.btn-outline:hover {
  border-color: var(--ink);
  background: rgba(255, 255, 255, 0.7);
}
/* Touch devices have no hover; give taps their own feedback since
   -webkit-tap-highlight-color is switched off above. */
@media (hover: none) {
  .btn:active { opacity: 0.85; }
  a:active { opacity: 0.7; }
}

/* Marketing bands only. `section` is a BARE element selector, so it also hit
   every app screen that uses <section> semantically — .intel-workspace, the
   .panel blocks on the dashboard and signals, all four fundamentals panels —
   handing each of them 96px of top padding at a desktop width.

   That was the ~130px void above "Market cockpit" that survived shrinking
   every heading, removing the nav panel and dropping position:sticky: none of
   those were ever the cause. An app section is a grouping element, not a
   page band, and pads itself through its own head/body. */
section { padding: var(--section-y) 0; }
.intel-app section,
.dash section,
.fund section,
.spy-app section,
.chat-app section { padding: 0; }
.section-head {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: clamp(24px, 4vw, 56px);
  align-items: end;
  margin-bottom: var(--stack-md);
}
.section-head h2 {
  font-size: clamp(28px, 3.6vw, 46px);
  line-height: 1.08;
  letter-spacing: -0.025em;
  font-weight: 600;
  margin: 12px 0 0;
  text-wrap: balance;
}
.section-head p {
  font-size: clamp(15px, 1.3vw, 17px);
  color: var(--body);
  line-height: 1.55;
  margin: 0;
}

@media (max-width: 900px) {
  .section-head { grid-template-columns: 1fr; gap: 20px; align-items: start; }
}

/* Minimum 44px tap target on any touch-primary device, not just narrow ones —
   a 1024px tablet needs this as much as a phone does. */
@media (hover: none) and (pointer: coarse) {
  .btn, .nav-links a, .foot-col a, .foot-contact {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  .foot-col a { display: flex; }
}

/* ---------------------------------------------------------------------------
   Print. Without this the reveal effect prints blank sections, and the dark
   band burns a page of ink.
   --------------------------------------------------------------------------- */
@media print {
  .reveal { opacity: 1 !important; transform: none !important; }
  .nav, .nav-toggle, .hero-bg, .skip-link, .preview-frame { display: none !important; }
  .dark {
    background: none !important;
    color: #000 !important;
  }
  .dark .lab, .dark .author-line { color: #333 !important; }
  .grad-text, .grad-text-light {
    background: none !important;
    -webkit-text-fill-color: #000;
    color: #000 !important;
  }
  body { background: #fff; }
  section, .hero { padding: 24px 0 !important; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 11px; color: #555; }
}

/* ===== theme toggle =====
   Global, because the control now sits in the header of every app page. Only
   one icon is ever visible and it shows the DESTINATION, matching the label:
   a moon while you are in light mode, because that is what clicking gets you.
   Both icons ship in the markup so the swap costs no request and no reflow. */
.theme-btn { display: inline-flex; align-items: center; gap: 7px; }
.theme-icon-sun { display: none; }
.theme-icon-moon { display: inline-flex; }
[data-theme="dark"] .theme-icon-sun { display: inline-flex; }
[data-theme="dark"] .theme-icon-moon { display: none; }

/* --- sortable headers ---
   A button inside the th rather than a click handler on the th itself: the
   header has to be reachable by keyboard and announce itself as pressable,
   and only a real button does both for free. Inherits the th's uppercase
   label styling so the column looks unchanged until you hover it. */
/* Shared by every sortable table — signals and the intelligence views.
   The th surrenders its padding to the button so the whole header cell is the
   click target rather than just the word. Listed explicitly instead of with
   :has() so the rule cannot quietly stop applying on an older engine and
   leave every header double-padded. */
.signals-table th,
.intel-table th { padding: 0; }
.th-sort {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: 8px 14px; border: 0; background: none; cursor: pointer;
  font: inherit; color: inherit; letter-spacing: inherit;
  text-transform: inherit; text-align: left;
}
.th-sort:hover { color: var(--ink-2); }
.th-sort.is-sorted { color: var(--ink); }
.th-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The idle glyph stays faint so seven columns of arrows do not compete with
   the data, but it is always present — a sort affordance that appears only on
   hover is one a reader never discovers. */
.th-arrow { font-size: 9px; opacity: .35; }
.th-sort.is-sorted .th-arrow { opacity: 1; color: var(--accent); }
.th-sort:hover .th-arrow { opacity: .7; }

/* === components/chat.css === */
/* === QuantX chat ===
   Layout follows the familiar assistant shape — a centred prompt that becomes a
   thread — but stays on the site's warm-cream palette rather than the dark
   chrome those tools use, so it reads as part of Athrive and not as an embedded
   third-party widget. All colour comes from tokens.css. */

/* Every surface now uses the one .brand lockup from nav.css — marketing nav,
   app bar, login card. There is no second brand rule left to keep in step.
   .brand carries a bottom margin from the nav context that the bar does not
   want. */
.dash-bar .brand { margin-bottom: 0; text-decoration: none; }

.chat-body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.chat {
  flex: 1;
  /* Horizontal width and safe-area gutters come from the shared app shell.
     Prose retains its own readable measure below while result tables may use
     the monitor width. */
  padding-top: 24px;
  padding-bottom: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* --- empty state --- */
.chat-hero {
  margin: auto 0;
  padding: 32px 0 24px;
  text-align: center;
}
/* The empty state's one prompt. It is display type — it appears on an
   otherwise blank screen and is gone after the first message — so it is
   allowed to be larger than body copy, but 42px was a billboard. */
.chat-hero h1 {
  font-size: clamp(26px, 2.6vw, 32px);
  font-weight: 650;
  letter-spacing: -0.02em;
  margin: 0 0 12px;
}

.chat-suggest {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin-top: 22px;
}
.chat-chip {
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink-2);
  border-radius: 999px;
  padding: 8px 14px;
  font: inherit;
  font-size: 13.5px;
  cursor: pointer;
  transition: border-color .15s, background .15s, transform .15s;
}
/* Hover LIFTS. --surface-2 is a step below the page in both themes, so the
   chip used to sink under the cursor while its border brightened — two
   signals pointing opposite ways. */
.chat-chip:hover {
  border-color: var(--accent);
  background: var(--surface);
  transform: translateY(-1px);
}

/* --- thread --- */
.chat-thread {
  flex: 1;
  overflow-y: auto;
  padding: 8px 0 16px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
/* Label beside the message, not stacked above it.
   Stacked, every turn cost a line of vertical space to say "YOU" — and on a
   wide screen that line was 3 characters followed by a great deal of nothing.
   In a gutter it reads as a transcript and roughly a third more of the
   conversation fits on screen.

   The label is right-aligned so YOU and QUANTX end at the same x and the
   content starts on one axis regardless of which is speaking.

   EVERY child except the label is forced into column 2. renderData() appends
   the result table to .chat-msg directly, and without this it would land in
   the next implicit grid row — underneath the label, in the gutter. Same for
   the error box and the stopped note. */
.chat-msg {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 6px;
}
.chat-msg > *:not(.chat-role) { min-width: 0; }

@media (min-width: 760px) {
  .chat-msg {
    grid-template-columns: 76px minmax(0, 1fr);
    column-gap: 18px;
    row-gap: 8px;
    align-items: start;
  }
  .chat-role { text-align: right; padding-top: 3px; }
  .chat-msg > *:not(.chat-role) { grid-column: 2; }
}

.chat-role {
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--muted);
  font-weight: 600;
}
/* Your own message is RAISED, so it takes --surface, not --surface-2.
   It was --surface-2, which in dark maps to #181818 — darker than the #1F1F1F
   page — so the bubble read as a hole punched into the thread rather than as
   something sitting on it. In a dark interface a raised surface is lighter:
   light falls from above, and elevation is more of it landing on the surface.
   --surface-2 is right for the sidebar, which is chrome recessed at the edge,
   and backwards for a message. One token was doing two jobs with opposite
   elevation semantics.

   Now it is above the page in both themes — #FFFFFF on cream, #2A2A2A on
   #1F1F1F — and the text clears AA on both: 18.39:1 and 8.94:1.

   No border. --line measures 1.01:1 against the new dark fill and 1.23:1
   against white, so it was drawing nothing in either theme; the fill alone
   carries the shape. A border that cannot be seen is weight in the stylesheet
   and a hairline the browser still has to paint. */
.chat-msg-user .chat-text {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 11px 15px;
  /* justify-self, not align-self: in a grid the latter moves it on the block
     axis and the bubble would stretch the full column. */
  justify-self: start;
  max-width: min(100%, 68ch);
}
.chat-text {
  color: var(--ink);
  font-size: 16px;
  line-height: 1.65;
  white-space: pre-wrap;
  word-break: break-word;
  /* Measure, not container width. At 760px and 15.5px this ran to ~97
     characters per line; comfortable reading is 45-75 and past ~85 it
     measurably degrades. Expressed in ch so it tracks the font size instead
     of silently drifting if that changes.
     The result table is a SIBLING of .chat-text, not a child, so it takes the
     full container width — prose is narrowed, data is not. That split is what
     lets the container widen for tables without running the prose out to a
     100-character line.
     80ch rather than 68: with the container fluid there is room, and 80 is
     still inside the comfortable band where 100+ is not. */
  max-width: 80ch;
}
.chat-text table {
  border-collapse: collapse;
  margin: 10px 0;
  font-size: 13.5px;
  width: 100%;
  display: block;
  overflow-x: auto;        /* wide result tables scroll, the page never does */
}
.chat-text th, .chat-text td {
  border-bottom: 1px solid var(--line);
  padding: 6px 10px;
  text-align: left;
  white-space: nowrap;
}
.chat-text th { color: var(--muted); font-weight: 600; }
/* Inline code needs to read as a distinct run of text, and --surface-2 sits
   1.08:1 from the page — technically a different colour, visually the same
   one. --surface is 1.35:1 and actually shows. */
.chat-text code {
  font-family: var(--font-mono);
  font-size: .9em;
  background: var(--surface);
  padding: 1px 5px;
  border-radius: var(--radius-sm);
}

.chat-cursor::after {
  content: '▍';
  color: var(--accent);
  animation: chat-blink 1s steps(2, start) infinite;
}
@keyframes chat-blink { to { visibility: hidden; } }

.chat-error {
  color: var(--danger);
  background: var(--danger-bg);
  border: 1px solid var(--danger-line);
  border-radius: var(--radius-md);
  padding: 10px 13px;
  font-size: 14px;
}

/* --- composer --- */
/* The composer is the bottom-most thing on screen, so on a notched iPhone in
   standalone mode it lands under the home indicator — the send button sits
   beneath the gesture bar and taps go to the system, not the page.
   env() resolves to 0 everywhere else, so this costs nothing on desktop. */
.chat-composer {
  position: sticky;
  bottom: 0;
  padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(to bottom, rgba(var(--paper-rgb), 0), var(--paper) 22%);
  padding-top: 14px;
}
/* The thread widens for tables; the composer does not follow it all the way.
   A single-line input 1400px across makes the caret hard to find and the text
   run away from the eye. Capped and centred — it is one line of typing, not a
   data view. */
.chat-box {
  max-width: 1080px;
  margin: 0 auto;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 12px 14px 10px;
  box-shadow: 0 1px 2px rgba(var(--ink-rgb), .04);
  transition: border-color .15s, box-shadow .15s;
}
.chat-box:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), .10);
}
.chat-box textarea {
  width: 100%;
  border: 0;
  outline: 0;
  resize: none;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 15.5px;
  line-height: 1.55;
  max-height: 200px;
}
.chat-box-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 8px;
}
.chat-hint { font-size: 12px; color: var(--muted); }

@media (max-width: 640px) {
  /* Landscape on a notched phone puts the rounded corner over the left edge. */
  .chat {
    padding-top: 16px;
    padding-bottom: 0;
  }
  .chat-hero h1 { font-size: 26px; }
  .chat-hint { display: none; }
}

/* Result rows, rendered from the query output rather than from model text —
   see renderData() in chat.js. Kept visually quieter than the prose: the
   sentence is the answer, this is the evidence behind it. */
/* The block owns the vertical rhythm; the inner wrapper owns the horizontal
   scroll. Splitting them lets the expander sit outside the scroll area so it
   holds its position on a wide table. */
.chat-data-block { margin-top: 10px; }
.chat-data-wrap { overflow-x: auto; }
table.chat-data {
  border-collapse: collapse;
  /* Tracks the prose: both came down together, so the sentence and the
     evidence under it keep their relationship. */
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
  min-width: 100%;
}
table.chat-data th {
  text-align: left;
  font-weight: 600;
  font-size: 11.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 4px 14px 6px 0;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
/* One row of data is one line on screen, and it is never clipped.
   Two earlier attempts were both wrong. nowrap + text-overflow: ellipsis cut
   every headline at 340px — and a truncated headline is not a shorter
   headline, it is a different one, since the clipped half usually carries the
   direction of the story. Replacing that with overflow-wrap: anywhere was
   worse: `anywhere` lowers a cell's MIN-CONTENT width to a single character,
   so the table happily crushed TITLE to one word per line and broke words
   mid-syllable ("Forec / asters").
   So: no wrapping, no clipping, no max-width. The full sentence sits on its
   own line and the table scrolls sideways in .chat-data-wrap if it must —
   which is why prose columns are ordered last (proseLast() in chat.js), where
   their length displaces nothing. */
table.chat-data td {
  padding: 6px 14px 6px 0;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
  vertical-align: top;
}
/* Numbers and timestamps stay on one line — they are short, and a wrapped
   price is harder to read than a wide column. */
table.chat-data td.num, table.chat-data th.num { white-space: nowrap; }
table.chat-data tr:last-child td { border-bottom: 0; }
/* Scores and prices are compared down the column, so right-align them and use
   lining figures — 7.51 above 6.55 only reads as bigger when the decimal
   points line up. Tagged in renderData(); text columns are unaffected. */
table.chat-data td.num, table.chat-data th.num { text-align: right; }
/* Now a real control, so it has to look and behave like one — a link that
   reads as static text is worse than no link, because nobody clicks it. */
.chat-data-more {
  /* Right-aligned and always in the same spot, directly under the table's
     right edge — a control that moves between renders is one the eye has to
     hunt for each time. */
  display: block;
  margin-left: auto;
  text-align: right;
  font-size: 11.5px;
  color: var(--accent);
  padding: 6px 0 2px;
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: underline;
  font-family: inherit;
}
.chat-data-more:hover { color: var(--ink); }
.chat-data-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Stop button while a turn is streaming. Deliberately the same near-black as
   the primary button: red read as an error state, when stopping is a normal
   thing to do. Only the label changes, so the control never moves or resizes
   mid-answer — a click target that shifts under the cursor at the exact moment
   it is wanted is worse than no button. */
.btn-stop {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
.btn-stop:hover { background: #000; border-color: #000; }

/* A cancel is not a failure, so it is muted rather than red like .chat-error. */
.chat-stopped {
  font-size: 12.5px;
  color: var(--muted);
  font-style: italic;
  margin-top: 8px;
}

/* --- dictation --- */
/* The mic and Ask sit together so the two ways of asking a question are in
   one place, and so adding the mic did not move the Ask button people have
   already learned the position of. */
.chat-actions { display: flex; align-items: center; gap: 8px; }

.chat-mic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
  transition: border-color .15s, background .15s, color .15s;
}
.chat-mic:hover { border-color: var(--accent); color: var(--ink); }
.chat-mic:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chat-mic[disabled] { opacity: .55; cursor: default; }

/* Recording is the one state that must be unmistakable — a mic you cannot
   tell is live is a privacy problem, not a styling one. Red is correct here
   (unlike on the stop button, where it wrongly implied an error) because it
   is the universal recording signal, and it pulses so it reads as live rather
   than merely selected. */
.chat-mic.is-recording {
  /* Deliberately NOT var(--danger): this is a filled button with white text,
     and the dark --danger is a light red that white cannot sit on (1.6:1). A
     recording indicator should also be the same red in both themes — it is a
     universal signal, not a themed accent. */
  background: #B3261E;
  border-color: #B3261E;
  color: #fff;
  animation: chat-mic-pulse 1.4s ease-in-out infinite;
}
@keyframes chat-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(179, 38, 30, .45); }
  50%      { box-shadow: 0 0 0 6px rgba(179, 38, 30, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .chat-mic.is-recording { animation: none; }
}

/* === components/dashboard.css === */
/* ---------------------------------------------------------------------------
   Sign-in + QuantX dashboard.
   Uses the existing token palette so the authed surface reads as the same
   product as the marketing site.
   --------------------------------------------------------------------------- */

/* ---- Sign in ---- */
.auth {
  min-height: 100vh;
  min-height: 100dvh; /* avoids the mobile URL-bar jump where supported */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(24px, 6vw, 64px) var(--gutter);
  background: var(--surface-2);
}
.auth-card {
  width: 100%;
  max-width: 440px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: clamp(28px, 5vw, 44px);
  box-shadow: var(--shadow-lg);
}
/* The login card carries the full brand lockup (.brand / .brand-word from
   nav.css) rather than a smaller variant of its own. It is the first screen
   anyone sees, so it should state the name at full strength — and a second
   set of brand rules is a second thing to keep in step, which the old
   .auth-brand had already failed to do: weight 600 against 700, tracking
   -0.02em against -0.045em, and a size that just inherited the body. */
.auth-card .brand { margin-bottom: 28px; text-decoration: none; }

/* Informational, not an error: being signed out by your own second sign-in is
   normal behaviour, and a red alarm box would suggest something went wrong. */
.auth-notice {
  margin: 0 0 18px;
  padding: 11px 14px;
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 14px;
  line-height: 1.5;
}
.auth-card h1 {
  font-size: clamp(21px, 2.6vw, 26px);
  line-height: 1.25;
  letter-spacing: -0.02em;
  margin: 0 0 10px;
}
.auth-sub { color: var(--body); font-size: 14px; line-height: 1.6; margin: 0 0 24px; }
.auth-card label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
.auth-card input {
  width: 100%;
  font-family: var(--font-mono);
  font-size: 14px;
  padding: 12px 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--paper);
  color: var(--ink);
  margin-bottom: 16px;
}
.auth-card input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.auth-card button[type="submit"] { width: 100%; justify-content: center; }
.auth-error {
  color: var(--danger);
  background: var(--danger-bg);
  border: 1px solid var(--danger-line);
  border-radius: var(--radius-sm);
  font-size: 13px;
  padding: 10px 12px;
  margin: 14px 0 0;
}
.auth-foot { color: var(--muted); font-size: 12.5px; line-height: 1.6; margin: 24px 0 0; }

/* ---- Dashboard chrome ----

   ONE LANE. Brand at the left, destinations beside it, and at the far end the
   theme control, who this is, and the way out. It used to wrap: at 1280px the
   lockup, the account, eleven outlined pills and the two controls took four
   stacked rows — 184px of chrome above the first row of content, on every
   page. It no longer wraps at all; app-nav.js measures the lane and moves the
   destinations that do not fit into a "More" menu, and the mobile rules in
   workspace.css define the deliberate two-row phone layout.

   It is chrome, so it recedes: near-monochrome, one hairline at the bottom
   doing the separating, and nothing inside drawing a box around itself. The
   only emphasis in the bar is the soft chip on the page you are looking at. */
.dash-bar {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: nowrap;
  min-height: 52px;
  padding: 8px var(--gutter);
  border-bottom: 1px solid var(--line);
  /* --paper, not --surface. On white the bar read as a separate panel laid
     over a cream page — a hard edge across the top that belonged to no
     component. It is chrome for the page, so it takes the page's ground and
     the hairline below does the separating.
     Still fully opaque, which is what a sticky bar needs: content scrolls
     underneath it, and a translucent ground would let the thread show
     through. In dark this lands on the editor ground while the rail stays a
     step darker, which is the VS Code relationship. */
  background: var(--paper);
  position: sticky;
  top: 0;
  z-index: 20;
}

/* The far end, in reading order: theme, who this is, the way out. */
.dash-bar-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
  flex-wrap: nowrap;
  margin-left: auto;
}
.dash-fresh { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* An identity marker, not a control. It reads as secondary through WEIGHT and
   COLOUR — 600/--ink made it the loudest thing at that end of the bar — and
   not by being shrunk: it keeps the 12.5px it always had. No border, no badge.
   Capped and clipped so a long address can never push the lane wider;
   app-nav.js mirrors the full value into `title`, so what the ellipsis hides
   is one hover away. */
.dash-account {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  max-width: 24ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Quiet controls. Outside the bar these are outlined buttons and stay that
   way; inside it a box around every item is eleven competing buttons, so the
   bar strips them back to text and lets a soft hover carry the affordance.

   This is also what bought the lane its width. The TYPE IS UNCHANGED at the
   13px .btn-sm has always used — nothing here is smaller than it was. What
   went is the border and 8px of the horizontal padding on each item, which
   is about 24px per destination and roughly 260px across the row: more than
   shrinking the text to 11px would have won, and it reads better. */
.dash-bar .btn {
  padding: 6px 9px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  border-radius: var(--radius-sm);
  border-color: transparent;
  background: transparent;
  color: var(--muted);
  box-shadow: none;
}
.dash-bar .btn:hover {
  border-color: transparent;
  background: rgba(var(--ink-rgb), 0.06);
  color: var(--ink);
  transform: none;
  box-shadow: none;
}
.dash-bar .btn:active { transform: none; }

/* The theme control is an icon in the bar. Its label still exists for the
   accessible name and for theme.js to keep current — it is simply not the
   thing worth 85px of a lane that eleven destinations are competing for.

   The width is FIXED rather than left to the glyph. Light shows a moon and
   dark shows a sun, and a control that changes width with its own state would
   make the navigation lane's fit depend on the theme: the same window could
   hold one more destination at night than during the day. Surprising, and the
   kind of difference nobody would think to look for. */
.dash-bar .theme-btn {
  gap: 0;
  padding: 6px;
  width: 30px;
  flex: none;
  justify-content: center;
}
.dash-bar .theme-btn #theme-label {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.btn-sm { padding: 7px 13px; font-size: 13px; }

.dash { padding: clamp(24px, 4vw, 40px) var(--gutter) 80px; }
.dash-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: var(--stack-md);
}
.dash-head h1 {
  font-size: clamp(26px, 3.4vw, 38px);
  letter-spacing: -0.03em;
  margin: 6px 0 0;
}
.dash-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.dash-controls label { font-size: 12.5px; color: var(--muted); }
.dash-controls select {
  font: inherit;
  font-size: 13px;
  padding: 7px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
}

.dash-error {
  color: var(--danger);
  background: var(--danger-bg);
  border: 1px solid var(--danger-line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  font-size: 13.5px;
  margin-bottom: 20px;
}

/* ---- Panels ---- */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  margin-bottom: 22px;
  overflow: hidden;
}
.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}
.panel-head h2 { font-size: 15px; letter-spacing: -0.01em; margin: 0; }
.panel-note { font-size: 12px; color: var(--muted); font-family: var(--font-mono); }
.panel-body { padding: 4px 0 0; }
.dash-loading, .dash-empty { color: var(--muted); font-size: 13.5px; padding: 20px; margin: 0; }

/* Why a panel is showing what it is showing: a widened window, or rows that
   were held back by a gate. Sits above the table it explains. */
.dash-state {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline;
  margin: 0; padding: 11px 14px; color: var(--body); font-size: 12.5px; line-height: 1.5;
  border-bottom: 1px solid var(--line); background: var(--paper);
}
.dash-state strong {
  font: 11px/1.4 var(--font-mono); text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted);
}
.dash-state span { color: var(--muted); }
/* A row that did not clear the gate. Legible, but plainly not a candidate —
   dimmed rather than hidden, which is the whole point of showing it. */
.sig-row.is-held td, .social-row.is-held td { opacity: .62; }

/* Wide tables scroll inside the panel — the page body must never scroll
   sideways on a phone. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* 13px was below the answer text in the chat and below the composer — small
   for the densest data on the site. Raised, while the row padding comes DOWN:
   bigger glyphs and tighter rows put more readable data on one screen, which
   is the opposite trade from bigger glyphs and looser rows. */
.dash-table { width: 100%; border-collapse: collapse; font-size: 14.5px; table-layout: auto; }
/* A tall row must not centre its short cells: the timestamp belongs beside
   the FIRST line of the post it timestamps, not halfway down it. */
.dash-table td { vertical-align: top; }
.dash-table th {
  text-align: left;
  font-size: 11.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.dash-table td {
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
  color: var(--body);
  vertical-align: top;
}
.dash-table tbody tr:last-child td { border-bottom: 0; }
.dash-table tbody tr:hover { background: var(--paper); }
.dash-table strong { color: var(--ink); font-family: var(--font-mono); }
.headline-link { color: var(--accent); text-decoration: none; }
.headline-link:hover { text-decoration: underline; }

.dir { font-family: var(--font-mono); font-weight: 600; }
.dir-bull { color: var(--up); }
.dir-bear { color: var(--down); }
.dir-flat { color: var(--muted); }

@media (max-width: 700px) {
  .dash-table th, .dash-table td { padding: 7px 10px; }
  .dash-head { align-items: flex-start; }
}

/* ===== report tables ===== */

/* The header row stays put while the rows scroll under it.
   A 200-row table whose header has scrolled away is a grid of unlabelled
   numbers — you cannot tell IMPACT from SPY without scrolling back up, which
   means you scroll back up. position: sticky needs the scroll container to be
   the ancestor that scrolls, which is .table-wrap, and a background or the
   rows show through as they pass beneath. */
.table-wrap {
  max-height: 70vh;
  overflow: auto;
}
.dash-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--paper);
  /* box-shadow, not border-bottom: borders on a sticky cell scroll away with
     the cell's own box in several engines, leaving the header floating with no
     edge against the rows sliding under it. */
  box-shadow: inset 0 -1px 0 var(--line-strong);
}

/* Scores line up on the decimal point so a column can be compared by eye.
   `th.num` and table-wide tabular figures come from .data-table in data.css,
   which every table on these pages now also wears. */
.dash-table td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* The range pair, fixed to two decimals and hung off a centred ellipsis, so
   the two ends align down the column instead of drifting with each value's
   own precision. */
.range { display: inline-flex; align-items: baseline; gap: 4px; font-variant-numeric: tabular-nums; }
.range-min, .range-max { min-width: 4.2ch; }
.range-min { text-align: right; }
.range-max { text-align: left; }
.range-sep { color: var(--muted); }

/* One cell answering both questions: which way, and how much.
   Hue is direction — the ONLY thing on this page that means up or down — and
   the number is magnitude. The arrow is not decoration: red/green is exactly
   the pair ~8% of men cannot separate, so direction is never carried by colour
   alone. */
.impact {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  justify-content: flex-end;
  font-variant-numeric: tabular-nums;
  font-weight: 650;
}
.impact-arrow { font-size: 11px; }
.impact-value { min-width: 4.2ch; text-align: right; }

/* The post, in full. It was clamped to two lines with the remainder in a
   title tooltip — and the tooltip covered the rows around it, so reading one
   post hid three others. A post you must hover to read, in a table you are
   scanning, is a post you do not read.
   The column takes the width instead: it is the widest cell because it carries
   the most meaning, and everything beside it is short by nature. */
.dash-table td.post {
  white-space: normal;
  width: 46%;
  min-width: 34ch;
}
.post-text { color: var(--body); line-height: 1.5; }

/* ===== per-ticker calls =====
   Each chip is one call: direction, symbol, conviction. They wrap and are
   ordered by score, so the strongest reads first and the row can be scanned
   left to right rather than parsed. */
.dash-table td.calls { white-space: normal; min-width: 20ch; max-width: 30ch; }
.calls { display: flex; flex-wrap: wrap; gap: 4px 6px; }
.call {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  padding: 1px 6px 2px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.call-arrow { font-size: 9.5px; }
.call-sym { font-weight: 650; }
.call-score { color: var(--muted); font-size: 11.5px; }
.call-more { align-self: center; font-size: 11.5px; color: var(--muted); }

/* ===== event flags =====
   A walkback reverses an earlier post, so it is the one flag that changes the
   DIRECTION of a trade rather than its size — it earns the louder treatment.
   Threat is context and stays quiet. */
.topic-cell { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.topic-name { }
.flag {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .05em;
  padding: 2px 5px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
.flag-walkback { background: var(--danger-bg); color: var(--danger); border: 1px solid var(--danger-line); }
.flag-threat { background: var(--surface-2); color: var(--muted); }

/* Tickers WRAP instead of stacking.
   The cell was as narrow as its longest ticker, so eight symbols became eight
   lines and a two-line post sat in an eight-line row — the row height was
   being set by the least important column on the page. Given room to flow they
   take one or two lines and the row is as tall as the post. */
.dash-table td.tickers {
  white-space: normal;
  min-width: 15ch;
  max-width: 22ch;
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
}

/* Short by nature — these should never be the reason a row wraps. */
.dash-table td.nowrap { white-space: nowrap; }

/* ===== the NSS scale in the header ===== */
.scale { display: inline-flex; align-items: center; gap: 7px; }
.scale-end { font-size: 11.5px; font-weight: 650; font-variant-numeric: tabular-nums; }
.scale-bear { color: var(--down); }
.scale-bull { color: var(--up); }
.scale-bar {
  width: 76px;
  height: 5px;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--down) 0%, var(--line-strong) 50%, var(--up) 100%);
}

.dash-updated {
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The panels carried the vertical rhythm of a marketing section, which on a
   dense report is a screen of empty cream above the first row. */
/* ===== vertical rhythm =====
   No single rule was reserving the empty band above the first table — five
   were, each individually reasonable and cumulatively a third of a screen
   before any data:

     .dash      padding-top   up to 40px
     .dash-head h1 margin           6px
     .dash-head margin-bottom        6px
     .panel     margin-top          18px
     .panel-head padding-top        14px
                                 ------
                                   ~84px

   These are the numbers of a marketing page, where whitespace is the product.
   On a report the data is the product, and every pixel of chrome is a row of
   signals pushed below the fold. Roughly halved. */
.dash { padding-top: clamp(14px, 2vw, 22px); padding-bottom: 48px; }
.dash-head { margin-bottom: 2px; }
.dash-head h1 { margin-top: 2px; font-size: clamp(24px, 2.6vw, 30px); }
.panel { margin-top: 10px; margin-bottom: 14px; }
.panel-head { padding: 9px 16px 8px; }
.panel-body { padding: 0 0 2px; }
/* The eyebrow repeats what the logo two inches away already says, on its own
   line, above every load. */
.dash .eyebrow { display: none; }

/* The pager. Full width and inside the panel so it reads as the end of the
   table rather than a control floating after it. */
.load-more {
  display: block;
  width: 100%;
  padding: 11px 16px;
  border: 0;
  border-top: 1px solid var(--line);
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background .14s;
}
.load-more:hover:not([disabled]) { background: var(--surface-2); }
.load-more[disabled] { color: var(--muted); cursor: default; }
.load-more:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* === components/data.css === */
/* =========================================================================
   Data surfaces — the shared layer every research page is built from.

   THE RULE THIS FILE ENCODES
   --------------------------
   On a report the data is the product and every pixel of chrome is a row
   pushed below the fold. Density here is bought with SPACING, HAIRLINES and
   REMOVED WRAPPERS — never with smaller type. There is no font-size token in
   this file for that reason: if the only way to fit something is to shrink it,
   the layout is wrong, not the type.

   Colour is meaning. A cell may be coloured for direction, impact or severity
   and for nothing else; a border, a badge or a heading never is.

   WHAT LIVES HERE
   ---------------
     .metric-strip   the replacement for a row of bordered metric tiles
     .data-pane      a scrolling table pane with a header that stays put
     .data-table     hairline rows, header by weight, tabular numbers
     .data-note      the one line above a table saying why it shows this
     .data-empty     nothing to show, and which kind of nothing
     .tag            a small constant label inside a cell

   The page-level shell (.page-head, .app-section, .jump-bar) is in
   workspace.css, because it is layout rather than data.
   ========================================================================= */


/* ===== metric strip =======================================================
   Five facts used to cost a band of screen: each one in its own bordered,
   padded, backgrounded card, stacked label over value over note. The border
   and the padding were the height — not the number.

   Same numbers at the SAME type size, laid on one baseline and separated by
   hairlines, cost one row. Works with three values and with eight; it wraps
   at whatever width runs out, and a wrapped second line is still shorter than
   one row of cards.

   <div class="metric-strip">
     <div class="metric">
       <span class="metric-k">Peak impact</span>
       <span class="metric-v">2.5<small>/10</small></span>
       <span class="metric-note">across all scored posts</span>
     </div>
     …
   </div>

   .metric-k is the label, .metric-v the number, .metric-note the qualifier
   that says WHICH population the number counts. The note is optional; on a
   number whose label is ambiguous it is not.                                */
.metric-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px var(--data-gap);
  margin: 0 0 var(--data-stack);
  padding: 7px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.metric {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding-right: var(--data-gap);
  border-right: 1px solid var(--line);
}
/* The last item on each line would otherwise hang a divider off the end. */
.metric:last-child { border-right: 0; padding-right: 0; }
.metric-k {
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.metric-v {
  color: var(--ink);
  font-size: 24px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
  white-space: nowrap;
}
/* The unit rides with the number and never competes with it. */
.metric-v small { font-size: 13px; font-weight: 600; color: var(--muted); }
.metric-note { color: var(--muted); font-size: 11.5px; white-space: nowrap; }

/* Colour on the VALUE only, and only where the value means a direction or a
   severity. The label and the divider stay neutral in every state. */
.metric.is-up .metric-v { color: var(--up); }
.metric.is-down .metric-v { color: var(--down); }
.metric.is-warn .metric-v { color: var(--warning); }
.metric.is-mute .metric-v { color: var(--muted); }

/* An inline meter, for a value that is a position on a fixed scale rather
   than a count. Fixed width so the reading is judged against the whole range
   and not against today's extremes. */
.metric-meter {
  position: relative;
  align-self: center;
  width: 84px;
  height: 5px;
  border-radius: 999px;
  background: var(--surface-2);
  overflow: hidden;
}
.metric-meter > span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--line-strong);
}
.metric.is-up .metric-meter > span { background: var(--up); }
.metric.is-down .metric-meter > span { background: var(--down); }
.metric.is-warn .metric-meter > span { background: var(--warning); }
/* A signed meter needs its zero drawn, or "slightly bullish" and "flat" look
   the same. .metric-meter.is-signed puts a hairline at the midpoint. */
.metric-meter.is-signed::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--line-strong);
}


/* ===== the scrolling pane =================================================
   The pane owns the overflow so the document never scrolls sideways on a
   phone, and it is the scroll container the sticky header pins to. Capping it
   means a long table cannot bury whatever section comes after it — which is
   what makes one stacked page readable instead of endless.                  */
.data-pane {
  max-height: var(--pane-max);
  overflow: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}


/* ===== the table ==========================================================
   Hairline separators at low contrast, not zebra stripes and not boxes. The
   header is distinguished by WEIGHT and letter-spacing, not by a filled bar:
   a filled header row is a second piece of furniture competing with the data
   under it.                                                                 */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14.5px;
  table-layout: auto;
}
.data-table th {
  padding: var(--data-row-y) var(--data-row-x);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
  vertical-align: bottom;
}
/* A constant that is true of every row belongs in the header, once, not in
   sixty cells. "scenario only", "/10", "ET" — all header material. */
.data-table th small {
  display: block;
  margin-top: 2px;
  /* The label stays on one line; the note is allowed to wrap rather than be
     clipped by the header's nowrap. A clipped caveat is worse than a tall
     header — "scenario only — n…" is not a caveat. */
  white-space: normal;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted);
}
.data-table td {
  padding: var(--data-row-y) var(--data-row-x);
  border-top: 1px solid var(--line);
  /* One hairline between two rows, not two. Explicit because .data-table is
     usually worn NEXT TO the older .dash-table, which draws a bottom edge. */
  border-bottom: 0;
  color: var(--body);
  vertical-align: top;
}
.data-table tbody tr:first-child td { border-top: 0; }
.data-table tbody tr:hover td { background: var(--surface-2); }

/* The header stays put while the rows scroll under it. A hundred-row table
   whose header has scrolled away is a grid of unlabelled numbers.
   box-shadow, not border-bottom: a border on a sticky cell scrolls away with
   the cell's own box in several engines and leaves the header edgeless. */
.data-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--paper);
  box-shadow: inset 0 -1px 0 var(--line-strong);
}

/* Numbers line up on the decimal point or the column cannot be read down.
   .num on a cell; .tnum on a span anywhere else. */
.data-table td.num,
.data-table th.num { text-align: right; }
.data-table td.num,
.tnum { font-variant-numeric: tabular-nums; }
/* Consistent decimals down a column: reserve the widest form so 9.0 and 12.5
   end on the same pixel. .dec-1 / .dec-2 = digits after the point. */
.dec-1 { display: inline-block; min-width: 4ch; text-align: right; font-variant-numeric: tabular-nums; }
.dec-2 { display: inline-block; min-width: 5.5ch; text-align: right; font-variant-numeric: tabular-nums; }
.data-table td.nowrap { white-space: nowrap; }

/* A row that did not clear the gate, or that the filter would have removed.
   Dimmed rather than hidden — showing it is the whole point, because a blank
   table and a dead feed look identical. */
.data-table tbody tr.is-held td { opacity: .62; }
.data-table tbody tr.is-open td { background: var(--surface-2); }


/* ===== the line above the table ===========================================
   Why this table is showing what it is showing: a window that was widened,
   rows held back by a gate, a market that is closed. One line, always — a
   four-sentence banner saying the same thing is the thing being removed, but
   the MEANING is not optional and never gets removed with it.

   <p class="data-note is-muted"><strong>Not market-relevant</strong>
      <span>59 posts in 7 days · none market-relevant · showing them muted</span></p>

   .is-muted  a state that is merely unusual
   .is-warn   a state the reader would act differently if they missed
             (after hours, stale feed, partial data)                         */
.data-note {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 9px;
  margin: 0;
  padding: 7px var(--data-row-x);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  color: var(--body);
  font-size: 12.5px;
  line-height: 1.45;
}
.data-note strong {
  color: var(--muted);
  font: 700 11px/1.4 var(--font-mono);
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.data-note span { color: var(--muted); }
.data-note.is-warn { box-shadow: inset 3px 0 0 var(--warning); }
.data-note.is-warn strong { color: var(--warning); }

/* Nothing to show — and WHICH nothing: an empty tape, or a filter the reader
   forgot was on. Those are different answers and a bare "no results" gives
   neither. */
.data-empty {
  margin: 0;
  padding: 18px var(--data-row-x);
  color: var(--muted);
  font-size: 13.5px;
}


/* ===== small constant labels ==============================================
   A tag is a word that qualifies a cell — a topic, a status, a class. It is
   not a button and it is not coloured unless the word itself is a severity. */
.tag {
  display: inline-block;
  margin: 0 4px 3px 0;
  padding: 1px 6px 2px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}
.tag.is-warn { border-color: var(--warning); color: var(--warning); }
.tag.is-up { border-color: var(--up); color: var(--up); }
.tag.is-down { border-color: var(--down); color: var(--down); }
.tag.is-quiet { border-color: transparent; background: var(--surface-2); }


@media (max-width: 760px) {
  /* The strip stops being a strip when there is no width to lay it on; the
     dividers go with it, because a divider between wrapped lines points at
     nothing. Type size is unchanged. */
  .metric-strip { gap: 6px 12px; }
  .metric { padding-right: 12px; }
  .data-table th,
  .data-table td { padding-left: 10px; padding-right: 10px; }
  /* The pane is the only thing that scrolls sideways, and on a phone it needs
     more of the screen than a 70vh cap allows. */
  .data-pane { max-height: none; }
}

@media (max-width: 620px) {
  /* A phone has no width to lay six facts across and no height to spare
     stacking them: six wrapped lines of strip is most of the screen the table
     was supposed to get. One swipeable row instead — the same pattern the app
     bar already uses for its navigation, and nothing is removed. Type size is
     unchanged. */
  .metric-strip {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .metric-strip::-webkit-scrollbar { display: none; }
  .metric { flex: 0 0 auto; }
  .metric:last-child { padding-right: 4px; }
}

/* === components/execution.css === */
/* Trade execution — the tabbed session view.
   Layout classes are shared with the other dashboards (.metric-strip, .filter-bar,
   .filter-search, .filter-chip, .panel); everything here is specific to the
   execution page. Only tokens defined in tokens.css are used, so both themes
   follow without a dark-mode block of their own. */

.exec-auto {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ink-2);
  font-size: 0.875rem;
  cursor: pointer;
  user-select: none;
}
.exec-auto input { accent-color: var(--ink); }
.exec-auto-n { font-family: var(--font-mono); color: var(--muted); }

/* ---- symbol chips inside the search box ---------------------------------- */

/* The box grows to fit its chips instead of clipping them: five symbols on one
   line is the common case, but ten must still wrap rather than disappear. */
.exec-symbols {
  flex-wrap: wrap;
  height: auto;
  min-height: 38px;
  padding-top: 4px;
  padding-bottom: 4px;
  max-width: 560px;
  cursor: text;
}
.exec-symbols input { flex: 1; min-width: 110px; }

.exec-symbol-chips { display: contents; }

.exec-symbol-chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px 4px 2px 8px;
  border-radius: var(--radius-sm);
  background: var(--ink);
  color: var(--paper);
  font: 600 12px/1.4 var(--font-mono);
  letter-spacing: 0.02em;
}

/* Selected but absent from this session: kept, so a quiet day does not erase a
   watchlist, but visibly hollow so it is not mistaken for "no signals yet". */
.exec-symbol-chip.is-empty {
  background: transparent;
  color: var(--muted);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

.exec-symbol-x {
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: transparent;
  color: inherit;
  font-size: 14px;
  line-height: 1;
  opacity: 0.7;
  cursor: pointer;
}
.exec-symbol-x:hover,
.exec-symbol-x:focus-visible { opacity: 1; background: rgba(var(--paper-rgb), 0.18); }
.exec-symbol-chip.is-empty .exec-symbol-x:hover { background: var(--surface-2); }

/* ---- tabs ---------------------------------------------------------------- */

.exec-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 16px 0 10px;
}

.exec-tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--paper);
  color: var(--ink-2);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}
.exec-tab:hover { border-color: var(--ink-2); color: var(--ink); }
.exec-tab-link { text-decoration: none; }

/* Same inverted treatment as the News page's horizon tabs, so "which view am I
   in" reads the same way on every dashboard. */
.exec-tab.is-active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* The ordinal is the tab's place in a signal's life, not decoration: legible,
   never louder than the label. */
.exec-tab-n { font: 12px/1 var(--font-mono); opacity: 0.55; }
.exec-tab.is-active .exec-tab-n { opacity: 0.7; }

/* ---- table --------------------------------------------------------------- */

/* The question this tab answers. On execution.html it also wears .data-note,
   which gives it the hairline and ground of the shared one-line state; the
   padding stays here because indicators.html and spy-options.html render the
   same line without that class. */
.exec-tab-question {
  margin: 0;
  padding: 14px 16px 6px;
  color: var(--muted);
  font-size: 0.95rem;
}

/* Ten columns on a phone scroll sideways rather than crushing each number to
   two characters. The panel clips; this scrolls inside it. */
/* Tables scroll inside their own box, both ways. A box that only scrolled
   sideways would still capture position: sticky, so the header would scroll
   away with the page; scrolling vertically in here keeps it pinned. Tall
   enough to read a session, short enough that the header is always on screen. */
.exec-scroll {
  overflow: auto;
  max-height: max(420px, calc(100vh - 140px));
  max-height: max(420px, calc(100dvh - 140px));
  padding: 0 0 6px;
}
/* The SPY Flow dashboard is a page of blocks, not a table. */
.exec-scroll.exec-scroll-free { overflow: visible; max-height: none; }

/* The first column (time) stays put while the rest scrolls sideways; its
   header sits above both scrolls. */
.exec-table th:first-child,
.exec-table td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--surface);
}
.exec-table th:first-child { z-index: 3; }
.exec-table tr.ind-spacer td:first-child { position: static; background: none; }
.exec-table tbody tr:hover td:first-child { background: var(--surface-2); }


.exec-table {
  width: 100%;
  border-collapse: collapse;
  font: 13px/1.55 var(--font-mono);
  white-space: nowrap;
}

.exec-table th {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: 8px 16px;
  border-bottom: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-align: left;
  text-transform: uppercase;
}

/* A sortable header carries its padding on the button, so the whole cell is
   the click target and the label lines up with the plain headers beside it. */
.exec-table th[aria-sort] { padding: 0; }
.exec-table th .th-sort { padding: 8px 16px; }

.exec-table td {
  padding: 7px 16px;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}

.exec-table tbody tr:last-child td { border-bottom: 0; }
.exec-table tbody tr:hover td { background: var(--surface-2); }

/* Numbers line up only if the glyphs do. Alignment belongs to numeric columns
   alone — colour is applied separately, so a green "Yes" or a red "STOPPED"
   stays left-aligned with the other words. */
.exec-table .exec-num { text-align: right; font-variant-numeric: tabular-nums; }

.exec-sym { font-weight: 700; }
.exec-table .exec-muted { color: var(--muted); }

.exec-table .exec-pos,
.exec-table .exec-yes { color: var(--up); }
.exec-table .exec-neg { color: var(--down); }
.exec-table .exec-no { color: var(--muted); }

/* Direction and risk are categories, not magnitudes — colour carries the
   category so the eye can skip the word when scanning a long session. */
.exec-table .exec-side { font-weight: 700; }
.exec-table .exec-side-long { color: var(--up); }
.exec-table .exec-side-short { color: var(--down); }

.exec-table .exec-risk { font-weight: 700; letter-spacing: 0.02em; }
.exec-table .exec-risk-extreme { color: var(--danger); }
.exec-table .exec-risk-high { color: var(--accent-2); }
.exec-table .exec-risk-medium,
.exec-table .exec-risk-low { color: var(--muted); }

.exec-table .exec-status-open { color: var(--up); font-weight: 700; }
.exec-table .exec-status-closed { color: var(--ink); }
.exec-table .exec-status-blocked,
.exec-table .exec-status-expired { color: var(--muted); }

/* Quality at or above the one tier that measurably separated outcomes. Bold
   rather than coloured: it is a threshold, not a direction. */
.exec-table .exec-strong { font-weight: 700; color: var(--ink); }
.exec-table .exec-strong-text { font-weight: 700; }

/* A target the pipeline flagged as unreachable or pointless. Worth a glance,
   not an alarm — bronze, same as HIGH risk. */
.exec-table .exec-warn { color: var(--accent-2); font-weight: 600; }

.exec-table .exec-reversal { color: var(--ink); font-weight: 600; }

/* An A_PLUS grade inside the A Plus tab — the environment or playbook agreeing
   with the headline grade. Accent, because it is the thing to look for. */
.exec-table .exec-grade-top { color: var(--accent); font-weight: 700; }

/* Filters that do not apply to the current tab: visible, clearly inert. */
.filter-window.is-disabled { opacity: 0.45; }
.filter-window.is-disabled select { cursor: not-allowed; }

/* The reason is a long rule string; truncated in the cell, full text on hover. */
.exec-table td[title] { max-width: 320px; overflow: hidden; text-overflow: ellipsis; }

/* The avg-R tile takes the sign of the session. execution.html now says this
   with .metric.is-up / .is-down (data.css); these two rules stay because
   indicators.js and spy-options.js still build .stat tiles. */
.stat .stat-value.exec-neg { color: var(--down); }
.stat .stat-value.exec-pos { color: var(--up); }

/* A row that arrived since the last 5s poll. The mark fades on its own, so a
   burst of signals is noticeable at a glance and the table is quiet again by
   the next poll. */
@keyframes exec-new-row {
  from { background: rgba(var(--accent-rgb), 0.18); }
  to   { background: transparent; }
}
.exec-table tr.exec-new td { animation: exec-new-row 3.5s ease-out; }
/* The frozen time cell must stay opaque, or scrolled cells show through it. */
.exec-table tr.exec-new td:first-child { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .exec-table tr.exec-new td { animation: none; background: rgba(var(--accent-rgb), 0.1); }
  .exec-table tr.exec-new td:first-child { background: var(--surface); }
}

@media (max-width: 640px) {
  .exec-symbols { max-width: none; }
  .exec-table { font-size: 12px; }
  .exec-table th,
  .exec-table td { padding: 6px 10px; }
}

/* === components/flow-chart.css === */
/* ==========================================================================
   Flow timing chart (Dashboards → Flow timing, inside an expanded row)

   Two hues only — inflow and outflow — validated with the dataviz palette
   validator against this site's card surfaces (--surface #FCFAF5 light,
   #2A2A2A dark), all checks passing. The product's own --up/--down were
   tried first: on light they separate only 7.3 ΔE for deutan vision, on dark
   they sit outside the lightness band and 4.6 ΔE apart. So the green leans
   teal here (8.9 ΔE light, 9.6 ΔE dark against a coral red) — the same
   family, one step further from the red. The sign is also carried by
   position, above or below the zero line, so no reading rests on hue.
   ========================================================================== */

:root {
  --flow-up: #0E7C61;
  --flow-down: #B3261E;
}
:root[data-theme="dark"] {
  --flow-up: #16A085;
  --flow-down: #E45F52;
}

.flow-wrap { position: relative; }
.flow { display: block; width: 100%; height: auto; }

.flow-caption { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; fill: var(--muted); }
.flow-grid { stroke: var(--line); stroke-width: 1; }
.flow-grid.is-zero { stroke: var(--line-strong); }
.flow-tick { font-family: var(--font-mono); font-size: 11px; fill: var(--muted); }

.flow-bar.is-up { fill: var(--flow-up); }
.flow-bar.is-down { fill: var(--flow-down); }
.flow-bar.is-flat { fill: var(--line-strong); }
/* An event day: the bar stays, faint, with its hue hatched over it. */
.flow-bar.is-event { fill-opacity: 0.18; }
.flow-hatch { stroke-width: 2; }
.flow-hatch.is-up { stroke: var(--flow-up); }
.flow-hatch.is-down { stroke: var(--flow-down); }

.flow-close { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.flow-close-dot { fill: var(--ink); stroke: var(--surface); stroke-width: 2; }
.flow-close-label { font-family: var(--font-mono); font-size: 11px; fill: var(--ink); paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round; }

.flow-start { stroke: var(--accent-2-ink); stroke-width: 1.5; }
.flow-start-label { font-family: var(--font-mono); font-size: 11px; fill: var(--accent-2-ink); paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round; }

.flow-cross { stroke: var(--line-strong); stroke-width: 1; pointer-events: none; }

.flow-tip {
  position: absolute;
  z-index: 3;
  min-width: 180px;
  max-width: 220px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
  font-size: 12px;
  pointer-events: none;
}
.flow-tip-title { font-weight: 600; color: var(--ink); margin-bottom: 6px; }
.flow-tip-sub { color: var(--accent-2-ink); font-family: var(--font-mono); font-size: 11px; margin: -4px 0 6px; }

.flow-legend { display: flex; flex-wrap: wrap; gap: 8px 18px; padding: 8px 4px 2px; font-size: 12px; color: var(--body); }
.flow-legend-item { display: inline-flex; align-items: center; gap: 8px; }
.flow-swatch { width: 14px; height: 12px; border-radius: 3px; box-sizing: border-box; border: 1px solid var(--line-strong); }
.flow-swatch.is-hatched { background: repeating-linear-gradient(45deg, var(--muted) 0 2px, transparent 2px 6px); }
.flow-tip-row { display: flex; justify-content: space-between; gap: 12px; padding: 2px 0; color: var(--body); }
.flow-tip-value { font-family: var(--font-mono); color: var(--ink); }

.flow-table { margin-top: 8px; color: var(--body); }
.flow-table summary {
  cursor: pointer;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.flow-table table { width: 100%; margin-top: 8px; border-collapse: collapse; font-family: var(--font-mono); font-size: 12px; }
.flow-table th, .flow-table td { padding: 4px 8px; text-align: right; border-bottom: 1px solid var(--line); white-space: nowrap; }
.flow-table th:first-child, .flow-table td:first-child { text-align: left; }
.flow-table th { color: var(--muted); font-size: 11px; font-weight: 500; }
.flow-table td { color: var(--body); }

/* === components/footer.css === */
.site-footer {
  border-top: 1px solid var(--line);
  padding: clamp(44px, 5vw, 64px) 0 clamp(28px, 3vw, 40px);
  padding-bottom: calc(clamp(28px, 3vw, 40px) + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(180deg, var(--paper) 0%, var(--surface-2) 100%);
  position: relative;
}
/* Footer brand uses larger size, matching the new nav lockup */
.site-footer .brand svg { width: 36px; height: 36px; }
.site-footer .brand-word { font-size: 22px; }
.site-footer::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0; right: 0;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(var(--accent-2-rgb), 0.4) 30%,
    rgba(var(--accent-rgb), 0.4) 70%,
    transparent
  );
}
.foot {
  display: grid;
  grid-template-columns: 1.5fr repeat(4, 1fr);
  gap: clamp(28px, 3.5vw, 48px);
}
.foot > * { min-width: 0; }
.foot-brand .lede {
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.025em;
  color: var(--ink-2);
  max-width: 280px;
  margin-top: 12px;
  line-height: 1.3;
  font-feature-settings: "cv11";
}
.foot-contact {
  display: inline-block;
  margin-top: 14px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--accent);
  letter-spacing: 0.01em;
  border-bottom: 1px solid rgba(var(--accent-rgb), 0.25);
  padding-bottom: 1px;
  overflow-wrap: anywhere;
  transition: color .15s ease, border-color .15s ease;
}
.foot-contact:hover {
  color: var(--ink);
  border-color: var(--ink);
}
.foot-col h4 {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
  margin: 0 0 16px;
}
.foot-col a {
  display: block;
  font-size: 14px;
  color: var(--body);
  padding: 6px 0;
  /* transform rather than padding — padding reflows the whole column on hover */
  transition: color .15s ease, transform .15s ease;
}
.foot-col a:hover {
  color: var(--ink);
  transform: translateX(4px);
}
.foot-bottom {
  margin-top: clamp(32px, 4vw, 56px);
  padding-top: 24px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  justify-content: space-between;
  font-size: 12px;
  color: var(--muted);
}

@media (max-width: 900px) {
  .foot { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foot-brand { grid-column: 1 / -1; }
}

@media (max-width: 420px) {
  .foot { grid-template-columns: minmax(0, 1fr); gap: 24px; }
  .foot-bottom { justify-content: flex-start; }
}

/* === components/fundamentals.css === */
/* -------------------------------------------------------------------------
   Fundamentals research desk
   ------------------------------------------------------------------------- */

.fund { font-size: 15px; }
.fund-head { align-items: flex-start; }


/* The tab strip is now the shared .seg, and it rides on the title row. What
   went is a bordered 58px band, a 30px margin and a filled ink block; the
   words are the size they were. Only the mono index keeps a rule here. */
.fund-tabs .fund-tab span {
  margin-right: 7px;
  color: var(--accent-2-ink);
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: .08em;
}
.fund-tab.is-active span,
.fund-tab[aria-selected="true"] span { color: inherit; opacity: .72; }

@media (max-width: 900px) {
  /* Four positions do not fit beside a title on a laptop; the strip drops to
     its own line rather than squeezing the words. */
  .fund-tabs { order: 3; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

.fund-panel { padding: 0; }
/* Layout comes from .app-section-head (workspace.css). The heading size is the
   shared 17px; this file only keeps what is specific to the desk. */
.fund-asof {
  flex: none;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 12.5px;
}

/* .fund-status wears .data-note, so the box, the dot and the halo are gone;
   what is left is the dot's meaning — ready, or blocked — carried by the
   kicker colour the shared rule already provides for .is-warn. */
.fund-status { margin-bottom: var(--data-stack); }
.fund-status.is-blocked { box-shadow: inset 3px 0 0 var(--down); }
.fund-status.is-ready { box-shadow: inset 3px 0 0 var(--up); }
.fund-status strong { color: var(--ink); }

/* The tiles are a .metric-strip now (data.css). Only the grid-era overrides
   that would fight it are removed; the value keeps its 24px. */
.fund-stat-grid { margin-bottom: var(--data-stack); }

/* The caution that lived here (.fund-methodology) is a span on the status
   line now; .data-note styles it. */

.fund-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.fund-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.fund-chip {
  padding: 7px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
  color: var(--body);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.fund-chip:hover { border-color: var(--ink); color: var(--ink); }
.fund-chip.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--paper);
}
.fund-chip-count {
  margin-left: 5px;
  font-family: var(--font-mono);
  opacity: .78;
}
.fund-search { margin-left: auto; }
.fund-search input {
  width: min(280px, 68vw);
  min-height: 40px;
  padding: 7px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-size: 15px;
}
.fund-search input::placeholder { color: var(--muted); }
.fund-select {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 13px;
  white-space: nowrap;
}
.fund-select select {
  min-height: 40px;
  padding: 6px 26px 6px 9px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-size: 14px;
}
.fund-count {
  margin-left: auto;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 12.5px;
  white-space: nowrap;
}

.fund-table-panel { margin-bottom: 0; }
.fund-table-panel .table-wrap { max-height: 66vh; }
.fund-table {
  min-width: 1300px;
  table-layout: auto;
}
/* sticky thead comes from .data-table (data.css) */
.fund-table td { line-height: 1.45; }
.fund-symbol { display: block; color: var(--ink); font: 650 14.5px/1.3 var(--font-mono); }
.fund-company {
  display: block;
  max-width: 210px;
  margin-top: 2px;
  color: var(--muted);
  font-size: 12px;
  white-space: normal;
}
.fund-metric { color: var(--ink); font-family: var(--font-mono); white-space: nowrap; }
.fund-metric small { display: block; color: var(--muted); font: 11.5px/1.4 var(--font-sans); }
.fund-target {
  display: grid;
  justify-items: start;
  gap: 4px;
  min-width: 154px;
}
.fund-target > .fund-metric + .fund-metric { margin-top: 3px; }
.fund-target-range {
  color: var(--muted);
  font: 11.5px/1.35 var(--font-mono);
  white-space: nowrap;
}
/* Confidence is a word on the range line. Only "low" is coloured: it is the
   one reading that should change what the reader does with the number. */
.fund-target-conf { color: var(--muted); }
.fund-target-conf.is-low { color: var(--warning); font-weight: 600; }
.fund-positive { color: var(--up); }
.fund-negative { color: var(--down); }
.fund-neutral { color: var(--muted); }

.fund-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 7px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  color: var(--body);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .035em;
  text-transform: uppercase;
  white-space: nowrap;
}
.fund-badge.is-good { border-color: var(--up); color: var(--up); }
.fund-badge.is-bad { border-color: var(--down); color: var(--down); }
.fund-badge.is-warn { border-color: var(--accent-2); color: var(--accent-2-ink); }
.fund-blocker {
  display: block;
  max-width: 190px;
  margin-top: 4px;
  color: var(--muted);
  font-size: 11.5px;
  white-space: normal;
}
.fund-quality {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 24px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink);
  font: 650 12.5px var(--font-mono);
}

/* The rotation context for the dip screen. Same shape as the metric strip
   above it — hairline dividers on one baseline — rather than a row of cards,
   which on a single group was one bordered box holding one sentence. */
.fund-context-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 2px var(--data-gap);
  margin: 0 0 var(--data-stack);
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
}
.fund-context-strip:empty { display: none; }
.fund-context-card {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
  padding-right: var(--data-gap);
  border-right: 1px solid var(--line);
}
.fund-context-card:last-child { border-right: 0; padding-right: 0; }
.fund-context-card strong { color: var(--ink); font-size: 14px; white-space: nowrap; }
.fund-context-card span { color: var(--muted); font-size: 12px; white-space: nowrap; }

.fund-density {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: 1px;
  padding: 1px;
  background: var(--line);
}
.fund-day {
  /* No minimum: a quiet day is two lines, a Fed day three. The 86px floor
     existed to hold a filler line that is no longer printed. */
  padding: 9px 12px;
  background: var(--surface);
}
.fund-day-date { display: block; color: var(--ink); font: 650 12.5px var(--font-mono); }
.fund-day-load { display: block; margin-top: 7px; color: var(--body); font-size: 12.5px; }
.fund-day-event { display: block; margin-top: 4px; color: var(--muted); font-size: 11px; line-height: 1.4; }
.fund-day.is-high { box-shadow: inset 0 3px 0 var(--down); }
.fund-day.is-medium { box-shadow: inset 0 3px 0 var(--accent-2); }

.impact-dots { display: inline-flex; gap: 2px; vertical-align: 1px; }
.impact-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--line-strong); }
.impact-dot.is-on { background: var(--accent-2-ink); }
.impact-dots.is-high .impact-dot.is-on { background: var(--down); }

.fund-predictions { display: grid; gap: 18px; }
.fund-prediction {
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
}
.fund-pred-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 20px;
  padding: 18px 20px;
  border-bottom: 1px solid var(--line);
}
.fund-pred-date { color: var(--accent); font: 650 12px var(--font-mono); text-transform: uppercase; }
.fund-pred-head h3 { margin: 5px 0 6px; color: var(--ink); font-size: 21px; }
.fund-pred-badges { display: flex; gap: 5px; flex-wrap: wrap; margin: 0 0 8px; }
.fund-pred-summary { max-width: 850px; margin: 0; color: var(--body); font-size: 14.5px; line-height: 1.6; }
/* When the model read and how far its data reached: the same mono line as
   the date above it, muted, under the badges. */
.fund-pred-meta { display: block; margin: 0 0 8px; color: var(--muted); font: 12px var(--font-mono); }
.fund-pred-score { min-width: 120px; max-width: 260px; text-align: right; }
.fund-pred-score strong { display: block; color: var(--ink); font: 650 24px var(--font-mono); }
.fund-pred-score span { display: block; margin-top: 2px; color: var(--muted); font-size: 12px; }
/* The measured record for this event type, with its n — the one line in
   the block that is a measurement rather than the model's own opinion. */
.fund-pred-score .fund-pred-record { margin-top: 6px; color: var(--body); }
.fund-scenario-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}
.fund-scenario {
  min-width: 0;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--paper);
}
.fund-scenario-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.fund-scenario strong { color: var(--ink); font-size: 14px; }
.fund-scenario-prob { color: var(--accent); font: 650 18px var(--font-mono); }
.fund-prob-track { height: 4px; margin: 8px 0; border-radius: 4px; background: var(--line); overflow: hidden; }
.fund-prob-fill { display: block; height: 100%; background: var(--accent-2); }
.fund-scenario p { margin: 0; color: var(--muted); font-size: 12.5px; line-height: 1.5; }

.fund-details { border-top: 0; }
.fund-details summary {
  padding: 12px 20px;
  color: var(--accent);
  font-size: 14px;
  font-weight: 650;
  cursor: pointer;
  list-style-position: inside;
}
.fund-details[open] summary { border-bottom: 1px solid var(--line); }
.fund-detail-body { padding: 17px 20px 20px; }
.fund-detail-body h4 {
  margin: 18px 0 8px;
  color: var(--ink);
  font-size: 14px;
  letter-spacing: .02em;
}
.fund-detail-body h4:first-child { margin-top: 0; }
.fund-detail-copy { margin: 0; color: var(--body); font-size: 14px; line-height: 1.65; }
.fund-metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(245px, 1fr)); gap: 8px; }
.fund-forecast-metric { padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.fund-forecast-metric strong { display: block; margin-bottom: 6px; color: var(--ink); font-size: 13px; }
.fund-forecast-values { display: flex; gap: 12px; color: var(--body); font: 12.5px var(--font-mono); }
.fund-impact-table { min-width: 760px; }
/* Four short columns; the desk-wide 1300px floor would only add a scrollbar. */
.fund-pending-table { min-width: 0; }
.fund-evidence-list { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; }
.fund-evidence-list li { color: var(--body); font-size: 13px; line-height: 1.55; }
.fund-evidence-list a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.fund-evidence-meta { margin-left: 7px; color: var(--muted); font: 11px var(--font-mono); }
.fund-uncertainties { margin: 0; padding-left: 18px; color: var(--body); font-size: 13px; line-height: 1.6; }

@media (max-width: 900px) {
  .fund-search { margin-left: 0; }
  .fund-count { width: 100%; margin-left: 0; }
  .fund-scenario-grid { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  .fund-pred-head { grid-template-columns: 1fr; padding: 15px; }
  .fund-pred-score { text-align: left; }
  .fund-scenario-grid { padding: 12px 15px; }
  .fund-details summary, .fund-detail-body { padding-left: 15px; padding-right: 15px; }
  .fund-calendar-toolbar .fund-search { order: 3; width: 100%; }
  .fund-calendar-toolbar .fund-search input { width: 100%; }
}

/* ---- Calendar: the day-by-day forward agenda ----------------------------- */

.fund-ahead-day {
  border-top: 1px solid var(--line);
  padding: 14px 0 6px;
}
.fund-ahead-day:first-child { border-top: 0; padding-top: 4px; }

.fund-ahead-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}
.fund-ahead-date {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.02em;
  font-weight: 600;
}
/* A day carrying a high-impact print is marked on the date itself, so the
   week reads at a glance rather than one row at a time. */
.fund-ahead-day.is-high .fund-ahead-date { color: var(--warn, #B45309); }
.fund-ahead-load { font-size: 12px; color: var(--muted); font-family: var(--font-mono); }

.fund-ahead-row {
  display: grid;
  grid-template-columns: 72px 1fr auto;
  align-items: start;
  gap: 10px;
  padding: 6px 0;
}
.fund-ahead-what { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
/* The event's name and its timing · forecast · prior · source share a line
   and wrap only when the line runs out. */
.fund-ahead-name { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; min-width: 0; }
.fund-ahead-research { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fund-ahead-prob { font-family: var(--font-mono); font-size: 12px; font-weight: 600; }
.fund-ahead-summary {
  font-size: 12px;
  color: var(--body);
  /* Two lines of what the research said; the Predictions tab carries the rest. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.fund-ahead-muted { color: var(--muted); }
/* Timing · forecast · prior · source, from the flat table that used to
   repeat every row under this block. */
.fund-ahead-meta { color: var(--muted); font: 12px var(--font-mono); }

@media (max-width: 640px) {
  .fund-ahead-row { grid-template-columns: 60px 1fr; }
  .fund-ahead-row .impact-dots { grid-column: 2; }
}

/* Staleness note beside the rotation model's trade date. */
.fund-stale { color: var(--muted); }
.fund-stale.is-stale { color: var(--warn, #B45309); font-weight: 600; }

/* === components/indicators.css === */
/* Indicators page. Table, tabs, chips and tiles reuse the execution classes;
   this file is only what the board and its notes need. */

/* Wears .data-note .is-muted; this is the page-local margin only. 0.85rem
   (13.6px) was below the page's reading size for a line that explains what
   the table underneath is counting. */
.ind-note { margin: 0 0 8px; font-size: 14px; }

/* The board is a grid of glyphs: centred, compact, one family per column. */
.ind-board .ind-fam { text-align: center; white-space: nowrap; }

.exec-table .ind-cell {
  text-align: center;
  font-size: 15px;
  line-height: 1;
  cursor: default;
}
.exec-table .ind-cell.is-buy { color: var(--up); font-weight: 700; }
.exec-table .ind-cell.is-sell { color: var(--down); font-weight: 700; }
.exec-table .ind-cell.is-none { color: var(--line-strong); }

/* The family's score beside its arrow, small enough not to compete with it. */
/* The family score beside its arrow. It was 10px superscript at 0.75 opacity
   — a measured value drawn below the readable floor. It is a real figure, so
   it is now legible; it stays SECONDARY by weight and colour rather than by
   being too small to read. */
.ind-score {
  margin-left: 4px;
  font-size: 12px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  vertical-align: baseline;
}
.exec-table .ind-cell.is-buy .ind-score,
.exec-table .ind-cell.is-sell .ind-score { color: inherit; opacity: .75; }

/* Context families always hold a view, so they are drawn quieter than events —
   an always-on arrow should not look like a fresh signal. */
.exec-table .ind-context { opacity: 0.55; }
.exec-table thead .ind-context { font-style: italic; }

/* Net is the column a scan starts from. */
.exec-table .ind-net { font-weight: 700; border-right: 1px solid var(--line); }

/* Controls that do not apply to the current tab. */
.filter-chips.is-disabled,
.filter-search.is-disabled { opacity: 0.45; pointer-events: none; }

/* Timeline board: bars where any event family fired stand out from the quiet
   ones, so a day of 130 rows can be scanned for its signals at a glance.
   Light and dark need OPPOSITE directions here: on cream the firing row is
   darker than the page, on the dark ground it has to be lighter. The old
   single rule painted --ink at 3.5%, and --ink in dark mode is #CCCCCC, so
   the tint was a 3.5% white wash over #1F1F1F — present in the markup,
   invisible on screen. */
.exec-table tr.ind-firing td { background: rgba(var(--ink-rgb), 0.045); }
:root[data-theme="dark"] .exec-table tr.ind-firing td { background: rgba(var(--ink-rgb), 0.10); }

/* Board: pick one indicator. */
.ind-family-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  padding: 0 0 10px;
}
.ind-family-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.filter-chip.ind-chip-context { font-style: italic; }
.ind-signals-only { margin-left: auto; }

/* Family headers in the all-indicators grid are buttons into that family. */
.ind-fam-pick {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  border-bottom: 1px dotted var(--line-strong);
}
.ind-fam-pick:hover,
.ind-fam-pick:focus-visible { color: var(--ink); border-bottom-color: var(--ink); }

/* Levels in a family's detail: overhead vs underneath the close. Subtle — the
   number is the point, the tint is orientation. */
.exec-table .ind-above { color: var(--down); }
.exec-table .ind-below { color: var(--up); }

.exec-table .ind-text { max-width: 280px; overflow: hidden; text-overflow: ellipsis; }

/* Virtualised tables: every row the same height, or the spacer arithmetic that
   stands in for off-screen rows would drift and the scrollbar would lie. */
.exec-table.ind-virtual td {
  height: 33px;
  padding-top: 0;
  padding-bottom: 0;
  line-height: 33px;
  white-space: nowrap;
  overflow: hidden;
  box-sizing: border-box;
}
.exec-table.ind-virtual .ind-score { line-height: 1; }
.exec-table tr.ind-spacer td { padding: 0; border: 0; line-height: 0; background: none; }
.exec-table tbody tr.ind-spacer:hover td { background: none; }

/* WATCH-level reversal: hollow and faint — present, deliberately not a signal. */
.exec-table .ind-cell.is-watch { font-weight: 400; opacity: 0.6; }
.exec-table .ind-cell.is-watch-buy { color: var(--up); }
.exec-table .ind-cell.is-watch-sell { color: var(--down); }
.exec-table .ind-mask { font-family: var(--font-mono); letter-spacing: 0.02em; color: var(--ink-2); }

/* ADR: nearing a full average day, and past it. */
.exec-table .ind-adr-near { color: var(--warning, var(--accent-2)); font-weight: 600; }
.exec-table .ind-adr-over { color: var(--down); font-weight: 700; }
.filter-chip.ind-chip-measure { border-style: dashed; }

/* ---------------------------------------------------------------------------
   Page-local: the metric strip carries eight values on this page, so it keeps
   this page's tile size rather than the 24px strip default.
   --------------------------------------------------------------------------- */
.ind-page .metric-strip { margin-bottom: 10px; }
.ind-page .metric-v { font-size: 19px; }
.ind-page .metric-note { font-size: 11.5px; }
.ind-page .page-head { margin-bottom: 10px; }
.ind-page .filter-bar { margin-bottom: 8px; }
.ind-page .exec-tabs { margin-bottom: 8px; }

/* The view's question now rides on the title row, so it drops the padding it
   carried as a row of its own inside the panel. */
.ind-page .page-head > .exec-tab-question { padding: 0; margin: 0; font-size: 14px; }
/* The panel is a container for the table, not a card around it. */
.ind-page .panel { border: 0; border-radius: 0; background: none; padding: 0; box-shadow: none; }
.ind-page .panel > .panel-body { padding: 0; }
.ind-page .dash > * + * { margin-top: 0; }
.ind-page .panel { margin-top: 0; margin-bottom: 8px; }
.ind-page .metric-strip { padding: 7px 0; margin-bottom: 8px; }
.ind-page .metric-note { line-height: 1.3; }
.ind-page .filter-bar { margin-bottom: 6px; }
.ind-page .exec-tabs { margin-bottom: 6px; }
.ind-page .ind-family-strip { padding: 0 0 8px; }

/* === components/intelligence.css === */
/* -------------------------------------------------------------------------
   QuantX specialist decision dashboards
   ------------------------------------------------------------------------- */

/* DENSITY.
   This page had roughly a thousand pixels of chrome above the first number:
   a 54px hero title, then a 40px view title saying much the same thing, then
   68px nav tiles, on 56px of padding with 90px beneath. On a 1150px viewport
   no data was visible without scrolling — the screen described itself instead
   of showing anything.

   The proportions were also inverted: decoration at 54px, table rows at
   14.5px. So the headings shrink and the DATA grows. A research dashboard
   should look like a terminal, not a landing page. */
.intel-app {
  padding-top: clamp(16px, 1.6vw, 24px);
  padding-bottom: 40px;
  font-size: 16px;
}






.intel-shell {
  display: block;
}
.intel-rail {
  /* NOT sticky any more.
     Everything else on this page accounted for 14px between the pills and the
     first heading; the rendered gap was ~130px. The DOM has nothing between
     them and no other rule sets a margin, which leaves the sticky positioning
     as the only candidate — a sticky flex container that wraps can reserve
     height its own content does not fill.

     The strip is ~38px tall and sits at the top of the page, so pinning it
     bought very little: scrolling a screen or two puts it out of view either
     way, and the cost was a third of the fold. */
  display: flex;
  flex-wrap: wrap;
  /* Wider than the old 6px because the pill borders used to do the separating
     and no longer exist; still far less than the boxes cost. */
  gap: 2px 16px;
  margin: 0 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
}
.intel-nav-label { display: none; }
.intel-nav-label {
  margin: 10px 11px 4px;
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 750;
  letter-spacing: .1em;
  text-transform: uppercase;
}
/* No box. Fifteen pills each spent 26px on horizontal padding and 2px on a
   border — 420px of the row on furniture rather than on words — and that is
   what pushed the strip onto a second line. The label keeps its size; only the
   container around it goes, and the gap does the separating instead. */
.intel-nav {
  flex: 0 0 auto;
  gap: 6px;
  padding: 5px 2px;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  white-space: nowrap;
}
.intel-nav:hover { background: none; color: var(--ink); }
.intel-nav small { display: none; }
/* The one entry that navigates away rather than switching a view. Same pill,
   and an arrow so the difference is visible before you click it. */
.intel-nav-ext {
  flex: 0 0 auto;
  gap: 6px;
  padding: 5px 2px;
  border: 0;
  border-radius: 0;
  background: none;
  white-space: nowrap;
}
.intel-nav-ext:hover { background: none; color: var(--ink); }
.intel-nav-ext > span { color: var(--accent-2-ink); font: 600 11px/1.2 var(--font-mono); opacity: .7; }
.intel-nav-ext b { font-size: 13.5px; font-weight: 600; }
.intel-nav-ext::after { content: "\2197"; margin-left: 1px; color: var(--muted); font-size: 11px; }

/* With the pill gone the selected view is marked by an underline and full-ink
   text — the same signal a terminal tab strip uses, and one that costs no
   width at all. */
.intel-nav.is-active {
  background: none;
  color: var(--ink);
  font-weight: 700;
  box-shadow: inset 0 -2px 0 var(--accent);
}
.intel-nav.is-active:hover { background: none; color: var(--ink); }
.intel-nav > span {
  color: var(--accent-2-ink);
  font: 600 11px/1.2 var(--font-mono);
  opacity: .7;
}
/* The index takes the pill's own foreground at reduced strength. A fixed hue
   here was readable on the light theme's ink pill and nearly invisible on the
   dark theme's, where --ink and --paper swap and the pill turns pale. */
.intel-nav.is-active > span { color: var(--accent); opacity: 1; }
.intel-nav b { font-size: 13.5px; font-weight: 600; line-height: 1.2; }
.intel-nav small { margin-top: 1px; color: var(--muted); font-size: 11.5px; line-height: 1.3; }


.intel-workspace { min-width: 0; }
/* Layout is .page-head (workspace.css). The dashboard heading is the page's
   h1 now that the intro band above it is gone, so it takes the shared 24px
   rather than the 22px it used as a subordinate title. */
.intel-view-head h1 { font-weight: 720; }
.intel-view-actions { gap: 10px; }
/* Some of these descriptions run to a dozen words, and a note long enough to
   push the search box onto a second line costs more height than putting it on
   its own line does. So it takes one: title and controls share the top row,
   the sentence sits under them. */
.intel-view-head .page-head-note {
  order: 3;
  width: 100%;
  max-width: 96ch;
  margin-top: 1px;
}
.intel-universe-lookup {
  position: relative;
  width: min(320px, 30vw);
}
.intel-global-search {
  width: 100%;
  min-height: 42px;
  padding: 9px 12px 9px 38px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background:
    linear-gradient(45deg, transparent 46%, var(--muted) 47% 53%, transparent 54%) 17px 23px / 7px 7px no-repeat,
    radial-gradient(circle at center, transparent 43%, var(--muted) 45% 58%, transparent 60%) 12px 10px / 14px 14px no-repeat,
    var(--surface);
  color: var(--ink);
  font-size: 14px;
}
.intel-global-search:focus { border-color: var(--accent); outline: 2px solid rgba(var(--accent-rgb), .15); }
.intel-search-popover {
  position: absolute;
  top: calc(100% + 7px);
  right: 0;
  z-index: 30;
  width: min(440px, 88vw);
  max-height: min(520px, 68vh);
  padding: 7px;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  background: var(--surface);
  box-shadow: 0 18px 45px rgba(15, 23, 42, .16);
  overflow-y: auto;
}
.intel-search-option {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  width: 100%;
  padding: 10px;
  border-radius: 8px;
  color: var(--body);
  text-align: left;
  cursor: pointer;
}
.intel-search-option:hover,
.intel-search-option.is-active { background: var(--surface-2); }
.intel-search-option > strong { color: var(--ink); font: 700 13px var(--font-mono); }
.intel-search-option-copy { min-width: 0; }
.intel-search-option-copy b,
.intel-search-option-copy small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.intel-search-option-copy b { color: var(--ink); font-size: 13px; }
.intel-search-option-copy small { margin-top: 2px; color: var(--muted); font-size: 11.5px; }
.intel-search-option-state { color: var(--muted); font: 11px var(--font-mono); white-space: nowrap; }
.intel-search-message { margin: 0; padding: 13px; color: var(--muted); font-size: 13px; }
.intel-asof { color: var(--muted); font: 12.5px/1.4 var(--font-mono); white-space: nowrap; }

/* .intel-status wears .data-note (data.css): one line, a hairline under it,
   no box and no dot. The dot's meaning — ready, or blocked — survives as the
   left rule, which is the same signal without the 42px indent it needed. */
.intel-status:empty { display: none; }
/* .is-warn comes from data.css (the --warning left rule and kicker). */
.intel-status.is-ready { box-shadow: inset 3px 0 0 var(--up); }
.intel-status.is-blocked { box-shadow: inset 3px 0 0 var(--down); }

/* The tiles are a .metric-strip now (data.css): the same numbers at the same
   size on one baseline, without the border, the padding and the ground each
   one used to sit in. The strip wraps at whatever width runs out, so the
   four/five-column variants it used to need are gone with it. */
.intel-stat-grid:empty { display: none; }
.intel-stat-grid .metric-v { font-family: var(--font-mono); letter-spacing: -.035em; }

.intel-tools {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 0;
  margin-bottom: 14px;
}
.intel-tools:empty { display: none; }
.intel-search {
  width: min(370px, 100%);
  min-height: 42px;
  padding: 9px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  font-size: 15px;
}
.intel-select {
  min-height: 42px;
  padding: 8px 32px 8px 11px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  font-size: 14px;
}
.intel-count { margin-left: auto; color: var(--muted); font: 12px var(--font-mono); }

.intel-results { display: grid; gap: 17px; }
.intel-panel {
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  overflow: hidden;
}
.intel-panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 15px 18px;
  border-bottom: 1px solid var(--line);
}
.intel-panel-head h3 { margin: 0; color: var(--ink); font-size: 16.5px; font-weight: 700; }
.intel-panel-head span { color: var(--muted); font: 12.5px var(--font-mono); }
.intel-panel-body { min-width: 0; }
.intel-panel-copy { margin: 0; padding: 14px 18px; color: var(--body); font-size: 14.5px; line-height: 1.55; }

/* The numbers are the product. Everything above shrank so these could grow. */
.intel-table { min-width: 980px; font-size: 15.5px; }
.intel-table th { font-size: 12.5px; }
.intel-table td { padding: 10px 13px; line-height: 1.42; }
.intel-symbol { display: block; color: var(--ink); font: 700 14px/1.3 var(--font-mono); }
.intel-company { display: block; max-width: 235px; margin-top: 3px; color: var(--muted); font-size: 13px; white-space: normal; }
.intel-number { color: var(--ink); font: 600 13.5px/1.35 var(--font-mono); white-space: nowrap; }
.intel-sub { display: block; margin-top: 3px; color: var(--muted); font: 12.25px/1.4 var(--font-sans); white-space: normal; }
.intel-positive { color: var(--up); }
.intel-negative { color: var(--down); }

.intel-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  color: var(--body);
  font-size: 11px;
  font-weight: 740;
  letter-spacing: .035em;
  text-transform: uppercase;
  white-space: nowrap;
}
.intel-badge.is-good { border-color: var(--up); color: var(--up); }
.intel-badge.is-bad { border-color: var(--down); color: var(--down); }
.intel-badge.is-warn { border-color: var(--accent-2); color: var(--accent-2-ink); }
/* Neither good nor bad, but not nothing either: take-profit, grade B. */
.intel-badge.is-info { border-color: var(--accent); color: var(--accent); }

/* Flow timing: labelled selects in the tools row, an expanding row per
   candidate holding the 30-session chart beside the reasons. */
.intel-tool {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--muted);
  font: 600 11.5px var(--font-mono);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.intel-tool .intel-select { font-family: var(--font-sans); letter-spacing: 0; text-transform: none; }
.intel-flow-time { flex-wrap: wrap; }
.intel-flow-time .btn { font-family: var(--font-sans); letter-spacing: 0; text-transform: none; }
.intel-flow-time .btn.is-active { background: var(--ink); color: var(--surface); border-color: var(--ink); }
.intel-flow-toggle {
  display: block;
  min-width: 150px;
  padding: 0 0 0 16px;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  position: relative;
}
.intel-flow-toggle::before {
  content: "";
  position: absolute;
  left: 1px;
  top: 6px;
  border: 5px solid transparent;
  border-left-color: var(--muted);
  transition: transform .14s;
}
.intel-flow-toggle[aria-expanded="true"]::before { transform: rotate(90deg); }
.intel-flow-toggle:hover .intel-symbol { color: var(--accent); }
.intel-flow-toggle .intel-company { max-width: 190px; }
.intel-table .intel-flow-detail > td { padding: 0; background: var(--paper); }
.intel-table .intel-flow-detail:hover { background: var(--paper); }
/* The table is wider than the viewport and scrolls sideways; the detail is
   pinned to the left edge at the visible width (set when the row opens), so
   the chart and the reasons are never off to the right of it. Chart and
   growth on top, the rules underneath in short columns. */
.intel-flow-detail-grid {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
  gap: 14px;
  padding: 14px;
  position: sticky;
  left: 0;
  box-sizing: border-box;
  max-width: 100%;
}
.intel-flow-chart { min-width: 0; padding: 12px 12px 8px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); }
.intel-flow-subpanel { min-width: 0; }
.intel-flow-subpanel .intel-panel-head { padding: 11px 14px; }
.intel-flow-subpanel .intel-panel-head h3 { font-size: 14px; }
.intel-flow-reasons-panel { grid-column: 1 / -1; }
.intel-flow-side-body { padding: 0 14px 12px; }
.intel-flow-rule-groups { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 22px; align-items: start; }
.intel-flow-reasons { margin: 0; padding: 0; list-style: none; }
.intel-flow-reasons li {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  color: var(--body);
  font-size: 13px;
  line-height: 1.45;
  white-space: normal;
}
.intel-flow-reasons li:last-child { border-bottom: 0; }
.intel-flow-reasons strong { color: var(--ink); font: 600 12.5px var(--font-mono); text-align: right; white-space: nowrap; }
.intel-flow-h { margin: 12px 0 2px; color: var(--muted); font: 600 11px var(--font-mono); letter-spacing: .08em; text-transform: uppercase; }
.intel-flow-h:first-child { margin-top: 4px; }
/* One rule per line: a mark that says pass / fired / idle in a glyph, never in colour alone. */
.intel-flow-reasons li.intel-flow-rule-line { justify-content: flex-start; gap: 8px; }
.intel-flow-mark { flex: none; width: 18px; color: var(--muted); font: 700 13px/1.45 var(--font-mono); text-align: center; }
.intel-flow-rule-line.is-pass .intel-flow-mark { color: var(--up); }
.intel-flow-rule-line.is-fired .intel-flow-mark { color: var(--down); }
.intel-flow-rule-text { flex: 1 1 auto; min-width: 0; color: var(--ink); text-align: left; }
.intel-flow-rule-text .intel-sub { white-space: normal; }
.intel-flow-thresholds { margin-top: 10px; }
.intel-flow-thresholds summary { cursor: pointer; color: var(--muted); font: 600 11px var(--font-mono); letter-spacing: .08em; text-transform: uppercase; }
.intel-flow-rule { margin: 10px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.5; white-space: normal; }
.intel-flow-growth {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 6px 16px;
  margin: 0;
  padding: 4px 14px 12px;
  font-size: 13px;
}
.intel-flow-growth dt { color: var(--muted); white-space: nowrap; }
.intel-flow-growth dd { margin: 0; color: var(--ink); font: 600 12.5px/1.45 var(--font-mono); white-space: normal; overflow-wrap: anywhere; }
/* The sector strip: one chip per sector, clean net large, raw net small. */
.intel-flow-rollup { padding: 12px 14px 10px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }
.intel-flow-rollup-head { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 8px; }
.intel-flow-rollup-head strong { color: var(--ink); font-size: 13.5px; }
.intel-flow-rollup-head span { color: var(--muted); font: 12px var(--font-mono); }
.intel-flow-rollup-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.intel-flow-chip {
  display: grid;
  grid-template-columns: auto;
  gap: 1px;
  min-width: 120px;
  padding: 7px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--paper);
  color: var(--body);
  text-align: left;
  cursor: pointer;
  transition: border-color .14s;
}
.intel-flow-chip:hover { border-color: var(--accent); }
.intel-flow-chip.is-active { border-color: var(--ink); background: var(--surface-2); }
.intel-flow-chip-name { color: var(--muted); font-size: 11.5px; white-space: nowrap; }
.intel-flow-chip-clean { color: var(--ink); font: 650 13.5px var(--font-mono); }
.intel-flow-chip small { color: var(--muted); font: 11px var(--font-mono); }
.intel-flow-rules { margin: 0; padding: 12px 18px 12px 34px; color: var(--body); font-size: 13.5px; line-height: 1.55; }
.intel-flow-rules li { margin: 4px 0; white-space: normal; }
.intel-flow-rules li::marker { color: var(--accent-2-ink); }

.intel-card-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 11px;
}
/* These cards are the cockpit's only copy of each coverage number now, and
   they are also the link to the dashboard that owns it — so they stay cards.
   What comes off is the air: the 14px gap that pushed the value onto its own
   band, and six pixels of padding. Every type size is unchanged. */
.intel-card {
  min-width: 0;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--surface);
}
button.intel-card { text-align: left; cursor: pointer; transition: border-color .14s, transform .14s; }
button.intel-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.intel-card-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.intel-card h3 { margin: 0; color: var(--ink); font-size: 17px; }
/* The number sits on the title's row rather than under it. */
.intel-card-value { display: block; margin-top: 2px; color: var(--ink); font: 680 25px/1.15 var(--font-mono); }
.intel-card p { margin: 1px 0 0; color: var(--muted); font-size: 13.5px; line-height: 1.45; }

.intel-flow-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); padding: 14px; }
.intel-flow-card { position: relative; background: var(--paper); overflow: hidden; }
.intel-flow-card::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--line-strong);
}
.intel-flow-card.is-positive::before { background: var(--up); }
.intel-flow-card.is-negative::before { background: var(--down); }
.intel-flow-leaders {
  display: block;
  margin-top: 10px;
  color: var(--body);
  font: 11.5px/1.45 var(--font-mono);
}
.intel-flow-share { display: block; min-width: 120px; }
.intel-flow-share strong { color: var(--ink); font: 650 13px var(--font-mono); }
.intel-flow-share-track {
  display: block;
  height: 5px;
  margin-top: 7px;
  border-radius: 99px;
  background: var(--surface-2);
  overflow: hidden;
}
.intel-flow-share-fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--up);
}
.intel-group-label { display: block; min-width: 235px; }
.intel-group-label strong { display: block; color: var(--ink); font-weight: 680; }
.intel-group-label small {
  display: block;
  margin-top: 4px;
  color: var(--muted);
  font: 11.5px/1.35 var(--font-mono);
}
.intel-group-cell.is-theme .intel-group-label { padding-left: 12px; }
.intel-group-cell.is-subcategory .intel-group-label { padding-left: 25px; }

.intel-company-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(280px, .8fr);
  gap: 1px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--line);
  overflow: hidden;
}
.intel-company-main,
.intel-company-summary { padding: 21px 22px; background: var(--surface); }
.intel-company-main h3 { margin: 0; color: var(--ink); font-size: 25px; letter-spacing: -.025em; }
.intel-company-main h3 span { margin-right: 9px; font-family: var(--font-mono); }
.intel-company-meta { margin: 6px 0 0; color: var(--muted); font-size: 13px; }
.intel-company-description { max-width: 860px; margin: 15px 0 0; color: var(--body); font-size: 14px; line-height: 1.62; }
.intel-summary-row { display: flex; justify-content: space-between; gap: 16px; padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.intel-summary-row:last-child { border-bottom: 0; }
.intel-summary-row span { color: var(--muted); }
.intel-summary-row strong { color: var(--ink); font-family: var(--font-mono); text-align: right; }

.intel-macro-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 9px; padding: 14px; }
.intel-macro-card { padding: 13px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--paper); }
.intel-macro-card span { display: block; color: var(--muted); font-size: 12px; }
.intel-macro-card strong { display: block; margin: 6px 0 4px; color: var(--ink); font: 680 19px var(--font-mono); }
.intel-macro-card small { color: var(--body); font-size: 12.5px; }

.intel-pipeline { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 9px; padding: 14px; }
.intel-pipeline-card { padding: 13px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--paper); }
.intel-pipeline-card strong { display: block; color: var(--ink); font-size: 13px; }
.intel-pipeline-counts { display: flex; gap: 10px; flex-wrap: wrap; margin: 9px 0 6px; font: 12px var(--font-mono); }
.intel-pipeline-card p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.45; }
/* Sits beside the pipeline grid, not inside it — the grid owns its own padding. */
.intel-pipeline-source { margin: 0; padding: 0 14px 14px; color: var(--muted); font-size: 12px; line-height: 1.45; }
.intel-pipeline-source strong { font: 11px/1 var(--font-mono); text-transform: uppercase; letter-spacing: .06em; }
/* Reachable and being-fed are different failures; these stack under the
   Live/Fallback line rather than replacing it. Only the top padding is
   dropped so the group reads as one block. */
.intel-collection { padding-top: 0; padding-bottom: 8px; }
.intel-collection:last-child { padding-bottom: 14px; }
.intel-collection.is-stale strong,
.intel-collection.is-unknown strong { color: var(--danger); }

.intel-empty { margin: 0; padding: 28px 20px; color: var(--muted); font-size: 14px; }

@media (max-width: 1120px) {
  .intel-card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .intel-flow-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .intel-flow-detail-grid { grid-template-columns: 1fr; }
  .intel-flow-rule-groups { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {


  .intel-rail { margin-bottom: 10px; }
  .intel-company-hero { grid-template-columns: 1fr; }
  .intel-universe-lookup { width: min(300px, 40vw); }
}

@media (max-width: 680px) {
  .intel-app { padding-top: 25px; }




  .intel-view-head { display: block; }
  .intel-view-actions { margin-top: 12px; }
  .intel-view-actions { align-items: stretch; flex-wrap: wrap; }
  .intel-universe-lookup { width: 100%; flex: 1 0 100%; }
  .intel-search-popover { left: 0; right: auto; width: 100%; }
  .intel-card-grid, .intel-pipeline { grid-template-columns: 1fr 1fr; }
  .intel-tools { align-items: stretch; }
  .intel-search { width: 100%; }
  .intel-count { width: 100%; margin-left: 0; }
}

@media (max-width: 460px) {
  .intel-card-grid, .intel-pipeline { grid-template-columns: 1fr; }
  .intel-flow-grid { grid-template-columns: 1fr; }
  .intel-nav { flex-basis: 188px; }
}

/* ---- Buy timing (tab 11) --------------------------------------------- */

.intel-live-state { display: block; font-size: 11px; font-family: var(--font-mono); color: var(--muted); margin-top: 2px; }
.intel-live-state.is-in_zone { color: var(--up); font-weight: 600; }
.intel-live-state.is-triggered { color: var(--up); }
.intel-live-state.is-stop_hit { color: var(--down); font-weight: 600; }
.intel-muted { color: var(--muted); }
.intel-note { font-size: 12px; color: var(--muted); margin: 8px 0 0; }
.intel-why { display: flex; flex-wrap: wrap; gap: 3px; width: 300px; max-width: 300px; }
.intel-table td:has(> .intel-why) { vertical-align: top; }
.intel-why-reason, .intel-why-blocker {
  font-size: 11px; line-height: 1.3; padding: 2px 6px; border-radius: 4px;
  background: var(--surface-2); color: var(--body); white-space: normal;
}
.intel-why-blocker { background: transparent; border: 1px dashed var(--line-strong); color: var(--muted); }

/* Leader chips on the sector cards: the action beside the symbol. */
.intel-leader { color: inherit; text-decoration: none; padding: 0 4px; border-radius: 4px; border: 1px solid transparent; }
.intel-leader.is-buy_now { border-color: var(--up); color: var(--up); font-weight: 600; }
.intel-leader.is-buy_on_pullback { border-color: var(--up); }
.intel-leader.is-avoid, .intel-leader.is-wait_earnings { border-color: var(--down); color: var(--down); }
.intel-leader.is-watch_no_prices { color: var(--muted); }
.intel-leader:hover { text-decoration: underline; }

/* v2: since / valuation / exit columns, the earnings expander, the track record.
   Colour is only ever --up / --down / the surface tokens: no new hex here. */
.intel-val { display: block; min-width: 200px; }
.intel-val-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 5px; }
.intel-val-line .intel-sub { display: inline; margin-top: 0; }
.intel-expand {
  display: block; margin-top: 5px; padding: 0; border: 0; background: none;
  color: var(--accent); font: 12px var(--font-mono); cursor: pointer; text-align: left;
}
.intel-expand:hover, .intel-expand[aria-expanded="true"] { text-decoration: underline; }
.intel-exit { display: block; min-width: 150px; }
.intel-exit-reasons { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 5px; max-width: 230px; }
.intel-why-reason.is-live { background: transparent; border: 1px solid var(--down); color: var(--down); font-weight: 600; }
.intel-badge.is-bad.intel-exit-now { background: var(--down); color: var(--paper); }

.intel-detail-row td { padding: 0 13px 14px; background: var(--paper); }
/* The detail cell spans every column of a table wider than the viewport;
   sticky-left keeps the box in view wherever the reader has scrolled. */
.intel-earn {
  position: sticky; left: 13px; max-width: 940px; padding: 13px 15px;
  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); white-space: normal;
}
.intel-earn-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 10px 18px; color: var(--ink); font-size: 14px; }
.intel-earn-facts { display: flex; flex-wrap: wrap; gap: 8px 22px; margin: 11px 0 12px; }
.intel-earn-fact span { display: block; color: var(--muted); font-size: 11.5px; }
.intel-earn-fact strong { display: block; margin-top: 2px; color: var(--ink); font: 650 14.5px var(--font-mono); }
.intel-earn-fact small { display: block; color: var(--body); font-size: 11.5px; }
.intel-earn-grid { border-collapse: collapse; font: 12.5px var(--font-mono); }
.intel-earn-grid th, .intel-earn-grid td { padding: 6px 12px; border: 1px solid var(--line); text-align: right; vertical-align: top; }
.intel-earn-grid th { color: var(--muted); font-weight: 500; font-size: 11.5px; }
.intel-earn-grid th:first-child, .intel-earn-grid td:first-child { text-align: left; color: var(--ink); font-weight: 600; }
.intel-earn-grid th small, .intel-earn-grid td small { display: block; margin-top: 2px; color: var(--muted); font-size: 11px; font-weight: 500; }
.intel-earn-grid td small.intel-positive { color: var(--up); }
.intel-earn-grid td small.intel-negative { color: var(--down); }
.intel-earn-grid td.is-base { outline: 2px solid var(--up); outline-offset: -2px; color: var(--ink); font-weight: 700; }
.intel-earn-method { margin: 10px 0 0; color: var(--muted); font-size: 12px; line-height: 1.45; }
.intel-track-summary { border-bottom: 1px solid var(--line); font: 13px/1.5 var(--font-mono); }

/* Company 360 capital flow: an event day's badge beside its date, and the
   one-line summary under the table. */
.intel-flow-day { white-space: nowrap; }
.intel-flow-day .intel-badge { margin-left: 4px; }
.intel-flow-summary { border-top: 1px solid var(--line); font: 13px/1.5 var(--font-mono); }

/* Short pressure: the reliability line, a direction dot beside its word
   (never colour alone), setup chips under the covering probability, the
   number box in the tools row, and the sessions table in the row expander. */
.intel-sp-reliability {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  align-items: baseline;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  color: var(--body);
  font: 13px/1.5 var(--font-mono);
}
.intel-sp-reliability strong { color: var(--ink); font: 600 13.5px/1.5 var(--font-sans); }
.intel-sp-dot { display: inline-block; width: 9px; height: 9px; margin-right: 7px; border-radius: 50%; background: var(--muted); }
.intel-sp-dot.is-up { background: var(--down); }
.intel-sp-dot.is-down { background: var(--up); }
.intel-sp-chips { display: flex; flex-wrap: wrap; gap: 4px; max-width: 250px; margin-top: 5px; }
.intel-sp-chips .intel-badge { padding: 2px 6px; font-size: 10.5px; letter-spacing: .02em; text-transform: none; }
/* On the Company 360 line the chips follow the probability on the same line. */
.intel-flow-growth dd .intel-sp-chips { display: inline-flex; margin: 0; vertical-align: middle; }
.intel-sp-number { width: 82px; }
.intel-sp-detail { grid-template-columns: minmax(0, 1fr); }
.intel-sp-detail-head { display: flex; flex-wrap: wrap; gap: 6px 16px; color: var(--body); font: 13px/1.5 var(--font-mono); }
.intel-sp-detail-head strong { color: var(--ink); }
.intel-sp-detail .intel-table { min-width: 0; }
.intel-sp-search { width: 9.5rem; font-family: var(--font-mono); text-transform: uppercase; }

/* Buy priority (tab 00). The rank, the drivers as signed chips (the sign is
   in the words, the colour repeats it), the score's components as bars from
   a centre line, the 20-session chart, and Company 360's one line.
   No new colour: the close is --rrg-up (validated for the Rotation graph on
   both surfaces), the 20-day average the grey reference beside it (ΔE 15.0
   light / 15.3 dark from the blue: emphasis, one series in colour), the
   entry zone a wash of --accent-2, and the components the validated
   --flow-up / --flow-down pair, with the side of the centre line and the
   signed value carrying the sign as well. */
.intel-bp-rank { display: block; color: var(--ink); font: 700 15px/1.25 var(--font-mono); }
.intel-why-reason.is-plus { background: transparent; border: 1px solid var(--up); color: var(--up); }
.intel-why-reason.is-minus { background: transparent; border: 1px solid var(--down); color: var(--down); }

.intel-bp-bars { display: grid; gap: 7px; padding: 12px 14px 14px; }
.intel-bp-bar-row {
  display: grid;
  grid-template-columns: minmax(118px, 1.15fr) minmax(90px, 1fr) 46px;
  align-items: center;
  gap: 10px;
  font-size: 12.5px;
}
.intel-bp-bar-label { color: var(--body); line-height: 1.3; }
.intel-bp-bar-label small { margin-left: 6px; color: var(--muted); font: 11px var(--font-mono); white-space: nowrap; }
.intel-bp-bar-track { position: relative; height: 12px; }
.intel-bp-bar-track::before { content: ""; position: absolute; left: 50%; top: -3px; bottom: -3px; width: 1px; background: var(--line-strong); }
.intel-bp-bar { position: absolute; top: 0; bottom: 0; }
.intel-bp-bar.is-plus { left: calc(50% + 1px); border-radius: 0 4px 4px 0; background: var(--flow-up); }
.intel-bp-bar.is-minus { right: calc(50% + 1px); border-radius: 4px 0 0 4px; background: var(--flow-down); }
.intel-bp-bar-value { color: var(--ink); font: 600 12px var(--font-mono); text-align: right; }

.intel-bp-close { fill: none; stroke: var(--rrg-up); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.intel-bp-sma { fill: none; stroke: var(--muted); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.intel-bp-close-dot { fill: var(--rrg-up); stroke: var(--surface); stroke-width: 2; }
.intel-bp-sma-dot { fill: var(--muted); stroke: var(--surface); stroke-width: 2; }
.intel-bp-zone { fill: var(--accent-2); fill-opacity: .18; }
.intel-bp-end { font-family: var(--font-mono); font-size: 11px; fill: var(--ink); paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round; }
.intel-bp-end.is-sma { fill: var(--muted); }
.intel-bp-key { display: inline-block; width: 16px; height: 2px; border-radius: 1px; background: var(--muted); }
.intel-bp-key.is-close { background: var(--rrg-up); }
.intel-bp-swatch { display: inline-block; width: 14px; height: 10px; border-radius: 3px; background: rgba(var(--accent-2-rgb), .3); }

.intel-bp-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 9px;
  width: 100%;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  color: var(--body);
  font: 13px/1.5 var(--font-mono);
  text-align: left;
  cursor: pointer;
}
.intel-bp-line:hover { border-color: var(--accent); }
.intel-bp-line strong { color: var(--ink); font: 600 13.5px/1.5 var(--font-sans); }
.intel-bp-line .intel-number { font-size: 14px; }
.intel-bp-line > span + span::before { content: "\00b7"; margin-right: 9px; color: var(--muted); }
/* Twelve columns on one desktop screen: the headers wrap instead of setting
   the widths, and the cells and the chips are a little tighter than the
   other tabs'. Narrower screens scroll sideways as every table here does. */
.intel-bp-table > .intel-table { min-width: 1180px; }
/* padding 0 as base.css intends for a sortable header (the button is the
   padded click target); .dash-table th's own padding otherwise adds 28px. */
.intel-bp-table > .intel-table > thead > tr > th { padding: 0; white-space: normal; vertical-align: bottom; }
.intel-bp-table > .intel-table > thead > tr > th .th-sort { padding: 8px 8px; }
.intel-bp-table > .intel-table > tbody > tr:not(.intel-flow-detail) > td { padding: 10px 8px; }
.intel-bp-table .intel-why { width: 232px; max-width: 232px; }
/* Rank, the two chances, the expected return and the grade are short
   numbers under long headers: their width comes from here, not the words. */
.intel-bp-table > .intel-table > thead > tr > th:nth-child(1) { width: 84px; }
.intel-bp-table > .intel-table > thead > tr > th:nth-child(3),
.intel-bp-table > .intel-table > thead > tr > th:nth-child(4) { width: 78px; }
.intel-bp-table > .intel-table > thead > tr > th:nth-child(5) { width: 70px; }
.intel-bp-table > .intel-table > thead > tr > th:nth-child(10) { width: 96px; }
.intel-bp-table .intel-flow-toggle .intel-company { max-width: 155px; }

/* Research · Buy (tab 14). The day's list on the left (kept in view while
   the case file scrolls), the case file on the right: the head, the plan,
   the eight sections, risks and invalidation, the sources. The verdict chips
   reuse the badge colours (buy --up, buy on pullback --accent, wait
   --accent-2, avoid --down) and a section's 3px edge repeats its chip; the
   word is always there, the colour only repeats it. No new colour. */
.intel-br-layout {
  display: grid;
  grid-template-columns: minmax(250px, 300px) minmax(0, 1fr);
  gap: 17px;
  align-items: start;
}
.intel-br-listpanel {
  position: sticky;
  top: 12px;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 24px);
}
.intel-br-listpanel .intel-panel-head { padding: 12px 14px; }
.intel-br-listpanel .intel-panel-head h3 { font-size: 14.5px; }
.intel-br-listpanel .intel-panel-body { overflow-y: auto; }
.intel-br-list { display: grid; gap: 2px; margin: 0; padding: 6px; list-style: none; }
.intel-br-item {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  /* The third row carries what became of the call on a past session; it
     collapses to nothing on the newest one, where there is no outcome. */
  grid-template-areas: "rank who chip" "rank facts facts" "rank outcome outcome";
  gap: 2px 8px;
  align-items: center;
  width: 100%;
  padding: 8px 9px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: none;
  color: var(--body);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.intel-br-item:hover { background: var(--surface-2); }
.intel-br-item.is-active { border-color: var(--ink); background: var(--surface-2); }
.intel-br-rank { grid-area: rank; align-self: start; color: var(--muted); font: 600 12px/1.75 var(--font-mono); text-align: right; }
.intel-br-who { grid-area: who; min-width: 0; }
.intel-br-who strong { display: block; color: var(--ink); font: 700 14px/1.3 var(--font-mono); }
.intel-br-who small { display: block; overflow: hidden; color: var(--muted); font-size: 12px; line-height: 1.35; text-overflow: ellipsis; white-space: nowrap; }
.intel-br-item > .intel-badge { grid-area: chip; justify-self: end; padding: 3px 7px; font-size: 10px; }
.intel-br-facts { grid-area: facts; color: var(--body); font: 12px/1.4 var(--font-mono); }

.intel-br-case { min-width: 0; }
/* One column that content cannot widen: a wide table scrolls in its own
   wrapper instead of pushing the page sideways on a phone. */
.intel-br-file { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
.intel-br-head { padding: 18px 20px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }
.intel-br-top { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 8px 14px; }
.intel-br-top h3 { margin: 0; color: var(--ink); font-size: 22px; letter-spacing: -.02em; line-height: 1.25; }
.intel-br-top h3 span { margin-right: 9px; font-family: var(--font-mono); }
.intel-br-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.intel-br-meta { margin: 5px 0 0; color: var(--muted); font: 12.5px/1.5 var(--font-mono); }
.intel-br-headline { margin: 12px 0 0; color: var(--ink); font-size: 16.5px; font-weight: 650; line-height: 1.45; }
.intel-br-summary { max-width: 1040px; margin: 8px 0 0; color: var(--body); font-size: 14.5px; line-height: 1.6; }

/* .intel-app section { padding: 0 } (0,1,1) would win over one class: the
   file's own sections are named under .intel-br-file. */
.intel-br-file .intel-br-plan {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 30px;
  padding: 14px 20px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
}
/* Child combinators: a CPR fact holds its own spans, strongs and chips. */
.intel-br-fact > span { display: block; color: var(--muted); font-size: 12px; }
.intel-br-fact > strong { display: block; margin-top: 2px; color: var(--ink); font: 650 16px/1.3 var(--font-mono); }
.intel-br-fact > small { display: block; margin-top: 2px; color: var(--muted); font: 11.5px/1.4 var(--font-mono); }
.intel-br-fact.is-stop > strong { color: var(--down); }
.intel-br-fact.is-target > strong { color: var(--up); }
.intel-br-size { flex-basis: 100%; margin: 0; color: var(--body); font-size: 13px; }

.intel-br-sections { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-items: start; }
.intel-br-file .intel-br-section {
  position: relative;
  min-width: 0;
  padding: 14px 16px 12px 19px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  overflow: hidden;
}
.intel-br-section::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: var(--line-strong); }
.intel-br-section.is-supports::before { background: var(--up); }
.intel-br-section.is-against::before { background: var(--down); }
.intel-br-section.is-na::before { background: transparent; border-left: 3px dashed var(--line-strong); }
.intel-br-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.intel-br-section-head h4 { margin: 0; color: var(--ink); font-size: 14.5px; font-weight: 700; }
.intel-br-section-headline { margin: 8px 0 0; color: var(--ink); font-size: 14px; font-weight: 600; line-height: 1.45; }
.intel-br-section-detail { margin: 6px 0 0; color: var(--body); font-size: 13.5px; line-height: 1.55; }
/* n/a: not good, not bad, not measured — a dashed ring, the muted word. */
.intel-badge.is-na { border-style: dashed; color: var(--muted); }

.intel-br-evidence-wrap { margin-top: 10px; overflow-x: auto; }
.intel-br-evidence { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.intel-br-evidence th,
.intel-br-evidence td { padding: 5px 10px 5px 0; border-top: 1px solid var(--line); text-align: left; vertical-align: top; }
.intel-br-evidence th:last-child,
.intel-br-evidence td:last-child { padding-right: 0; }
.intel-br-evidence thead th { border-top: 0; color: var(--muted); font: 600 10.5px/1.4 var(--font-mono); letter-spacing: .06em; text-transform: uppercase; }
.intel-br-evidence tbody th { color: var(--body); font-weight: 500; line-height: 1.4; }
.intel-br-value { color: var(--ink); font: 600 12.5px/1.4 var(--font-mono); }
.intel-br-asof { color: var(--muted); font: 11.5px/1.4 var(--font-mono); white-space: nowrap; }
.intel-br-source { color: var(--muted); font: 11.5px/1.4 var(--font-mono); }

.intel-br-lists { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start; }
.intel-br-lines-panel .intel-panel-head { padding: 12px 16px; }
.intel-br-lines-panel .intel-panel-head h3 { font-size: 14.5px; }
.intel-br-lines { margin: 0; padding: 10px 18px 12px 36px; color: var(--body); font-size: 14px; line-height: 1.55; }
.intel-br-lines li { margin: 4px 0; }
.intel-br-lines li::marker { color: var(--accent-2-ink); font-family: var(--font-mono); }

.intel-br-sources {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 16px;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  color: var(--body);
  font: 12px/1.5 var(--font-mono);
}
.intel-br-sources strong { color: var(--ink); font: 600 13px/1.5 var(--font-sans); }
.intel-br-src b { color: var(--ink); font-weight: 600; }
.intel-br-src small { margin-left: 6px; color: var(--accent-2-ink); font: inherit; }

/* CPR (sql/067): three compact lines, D · W · M — the range, the width
   word, where the close sits (above --up, below --down, the word always
   there) — and the best buy with its gap and the levels it rests on. */
.intel-cpr-lines {
  display: grid;
  grid-template-columns: auto auto auto auto auto;
  gap: 2px 8px;
  align-items: baseline;
  justify-content: start;
  font: 12px/1.35 var(--font-mono);
  white-space: nowrap;
}
.intel-cpr-h { color: var(--muted); font-weight: 600; }
.intel-cpr-range { color: var(--ink); font-weight: 600; }
.intel-cpr-width,
.intel-cpr-rel { color: var(--body); font-size: 10.5px; letter-spacing: .03em; }
.intel-cpr-pos { color: var(--muted); }
.intel-cpr-pos.is-above { color: var(--up); }
.intel-cpr-pos.is-below { color: var(--down); }
.intel-cpr-buy { display: block; min-width: 140px; }
.intel-cpr-buy > strong { display: block; }
.intel-cpr-basis { display: flex; flex-wrap: wrap; gap: 3px; max-width: 210px; margin-top: 4px; }
.intel-bp-table.has-cpr > .intel-table { min-width: 1560px; }
.intel-bp-table.has-cpr > .intel-table > thead > tr > th:nth-child(10) { width: auto; }
.intel-bp-table.has-cpr > .intel-table > thead > tr > th:nth-child(12) { width: 96px; }

/* The live Price column (shown while a live reading is on the response) sits
   after Plan, so every positional width after it moves along by one. The
   shifted ones are restated here and the ones they displaced are switched off;
   the alternative is a nth-child set written twice for two column counts. */
.intel-bp-table.has-live > .intel-table { min-width: 1320px; }
.intel-bp-table.has-live > .intel-table > thead > tr > th:nth-child(10) { width: auto; }
.intel-bp-table.has-live > .intel-table > thead > tr > th:nth-child(11) { width: 96px; }
.intel-bp-table.has-live.has-cpr > .intel-table { min-width: 1700px; }
.intel-bp-table.has-live.has-cpr > .intel-table > thead > tr > th:nth-child(11) { width: auto; }
.intel-bp-table.has-live.has-cpr > .intel-table > thead > tr > th:nth-child(12) { width: auto; }
.intel-bp-table.has-live.has-cpr > .intel-table > thead > tr > th:nth-child(13) { width: 96px; }
/* The stop / target tags under the price, on their own line: they are the
   reading that changes what the reader does, and they must not be mistaken
   for part of the number above them. */
.intel-bp-hits { display: block; margin-top: 3px; }
.intel-br-subhead { margin: 12px 0 0; color: var(--muted); font: 600 10.5px/1.4 var(--font-mono); letter-spacing: .06em; text-transform: uppercase; }
/* The case's CPR levels: their own block at the foot of the plan box,
   under a rule, apart from the plan above it. */
.intel-br-plan > .intel-br-cpr { flex-basis: 100%; min-width: 0; padding-top: 2px; border-top: 1px solid var(--line); }
.intel-br-cpr > .intel-br-subhead { margin-top: 10px; }
.intel-br-cpr-buy { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 5px 16px; margin: 10px 0 0; font-size: 13px; }
.intel-br-cpr-buy dt { color: var(--muted); white-space: nowrap; }
.intel-br-cpr-buy dd { margin: 0; color: var(--ink); font: 600 12.5px/1.45 var(--font-mono); }
.intel-br-cpr-buy dd .intel-cpr-basis { max-width: none; margin-top: 0; }
/* Sized to its values, not stretched across a wide plan box. */
.intel-br-evidence.intel-cpr-table { width: auto; }
.intel-cpr-table th, .intel-cpr-table td { padding-right: 22px; }
.intel-cpr-table tbody th { text-transform: capitalize; white-space: nowrap; }
.intel-cpr-table th small,
.intel-cpr-table td small { display: block; color: var(--muted); font: 500 10.5px/1.3 var(--font-mono); text-transform: none; }
.intel-cpr-table td { white-space: nowrap; }
.intel-cpr-table td.intel-cpr-pos { font: 600 12px/1.4 var(--font-mono); }

/* "Research →" under a Buy priority symbol, and beside Company 360's line. */
.intel-br-link {
  display: inline-block;
  margin-top: 5px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: 600 12px/1.4 var(--font-mono);
  white-space: nowrap;
  cursor: pointer;
}
.intel-br-link:hover { text-decoration: underline; }
.intel-bp-symbol { display: block; }
.intel-bp-symbol > .intel-br-link { margin-left: 16px; }
.intel-br-strip { display: flex; align-items: stretch; gap: 8px; }
.intel-br-strip > .intel-bp-line { flex: 1 1 auto; width: auto; min-width: 0; }
.intel-br-strip > .intel-br-link {
  flex: none;
  margin: 0;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
}
.intel-br-strip > .intel-br-link:hover { border-color: var(--accent); text-decoration: none; }

@media (min-width: 1360px) {
  .intel-br-sections { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .intel-br-layout { grid-template-columns: minmax(0, 1fr); }
  .intel-br-listpanel { position: static; max-height: none; }
  .intel-br-list { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 4px; }
  .intel-br-item { grid-template-areas: "rank who" "rank chip" "rank facts" "rank outcome"; grid-template-columns: 22px minmax(0, 1fr); }
  .intel-br-item > .intel-badge { justify-self: start; }
  .intel-br-lists { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 460px) {
  .intel-br-head { padding: 14px; }
  .intel-br-file .intel-br-plan { padding: 14px; gap: 10px 20px; }
  .intel-br-strip { flex-direction: column; }
}

/* ==========================================================================
   Track record (tabs 00 and 14) — how the past calls went.

   Two sections that never share a number: live first, then the backfill,
   each with its own badge. The month line is the only new chart: two series,
   target first against stop first, in the flow chart's two hues (--flow-up /
   --flow-down, validated with the dataviz palette validator against this
   site's card surfaces: 8.9 ΔE deutan on light, 9.6 on dark, normal-vision
   26.5 / 27.3). Both lines are also labelled at their ends and in the
   legend, and the months repeat as a table, so no reading rests on hue.
   ========================================================================== */
.intel-tr-host { display: block; }
.intel-tr-host.is-loading { opacity: .55; }
.intel-tr-panel > .intel-panel-body { padding: 0; }
.intel-tr { display: grid; gap: 16px; padding: 14px; }
.intel-tr-head { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; justify-content: space-between; }
.intel-tr-definition { margin: 0; color: var(--muted); font: 12.5px/1.5 var(--font-mono); }

.intel-tr-origin { display: grid; gap: 12px; }
.intel-tr-origin-head { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: baseline; }
.intel-tr-origin-line { color: var(--body); font: 13px/1.6 var(--font-mono); }
.intel-tr-origin.is-backfill { padding-top: 12px; border-top: 1px solid var(--line); }
/* Stacked, not side by side: thirteen columns of counts and rates do not
   fit beside another table without hiding the ones on the right. */
.intel-tr-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; align-items: start; }

.intel-tr-table > .intel-table { min-width: 940px; }
.intel-tr-matrix { min-width: 560px; }
.intel-tr-matrix th[scope="row"] { white-space: nowrap; }
.intel-tr-matrix td { vertical-align: top; }
.intel-tr-cell-rate { display: block; color: var(--ink); font: 600 14px/1.3 var(--font-mono); }
.intel-tr-matrix .intel-sub { display: block; margin-top: 2px; color: var(--muted); font-size: 11px; }
.intel-tr-margin > th, .intel-tr-margin > td { border-top: 1px solid var(--line-strong); }

.intel-tr-chart { margin-top: 4px; }
.intel-tr-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.intel-tr-line.is-target { stroke: var(--flow-up); }
.intel-tr-line.is-stop { stroke: var(--flow-down); }
.intel-tr-dot { stroke: var(--surface); stroke-width: 2; }
.intel-tr-dot.is-target { fill: var(--flow-up); }
.intel-tr-dot.is-stop { fill: var(--flow-down); }
.intel-tr-end {
  font-family: var(--font-mono);
  font-size: 11px;
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 3px;
  stroke-linejoin: round;
}
.intel-tr-end.is-target { fill: var(--flow-up); }
.intel-tr-end.is-stop { fill: var(--flow-down); }
.intel-tr-key { width: 14px; height: 3px; border-radius: 2px; }
.intel-tr-key.is-target { background: var(--flow-up); }
.intel-tr-key.is-stop { background: var(--flow-down); }
.intel-tr-monthpanel .flow-wrap { padding: 4px 14px 0; }
.intel-tr-monthpanel .flow-legend, .intel-tr-monthpanel .flow-table { padding-left: 14px; padding-right: 14px; }

.intel-tr-calls { display: grid; gap: 10px; }
.intel-tr-filters { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: flex-end; padding: 12px 14px 0; }
.intel-tr-filters .intel-tool { margin: 0; }
.intel-tr-selection { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 0; padding: 0 14px; color: var(--body); font: 12.5px/1.6 var(--font-mono); }
.intel-tr-selection-line { display: inline-flex; align-items: center; gap: 8px; }
.intel-tr-calls-table > .intel-table { min-width: 1120px; }
.intel-tr-pager { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; padding: 0 14px 12px; }
.intel-tr-pager .intel-count { margin-left: 0; }
.intel-tr-result { display: inline-flex; flex-direction: column; gap: 2px; align-items: flex-start; }

.intel-tr-company { width: 100%; }
.intel-tr-company > .intel-badge { margin-right: 2px; }
.intel-tr-company > span + span::before { content: ""; margin: 0; }

/* Buy priority: the case file opened inline under its row (tab 14's own
   renderer), and the button that closes it again. */
.intel-bp-case-row { scroll-margin-top: 96px; }
.intel-bp-case { position: relative; padding: 14px; }
.intel-bp-case .intel-br-file { max-width: none; }
.intel-br-close {
  position: absolute;
  top: 10px;
  right: 12px;
  z-index: 2;
  padding: 4px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
  color: var(--body);
  font: 600 11px/1.4 var(--font-mono);
  cursor: pointer;
}
.intel-br-close:hover { border-color: var(--accent); color: var(--accent); }

/* Research · Buy: what became of a past call, in a list entry and in the
   case head. */
.intel-br-outcome { display: inline-flex; flex-wrap: wrap; gap: 4px 8px; align-items: center; margin-top: 4px; }
.intel-br-outcome .intel-sub { font: 11px/1.4 var(--font-mono); }
.intel-br-item > .intel-br-outcome { grid-area: outcome; justify-self: start; }
.intel-br-outcome > .intel-badge { padding: 3px 7px; font-size: 10px; }
.intel-br-outcome-line {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 10px;
  align-items: center;
  margin: 8px 0 0;
  color: var(--body);
  font: 12.5px/1.6 var(--font-mono);
}


@media (max-width: 900px) {
  .intel-tr { padding: 10px; }
  .intel-tr-filters { padding: 10px 10px 0; }
  .intel-tr-selection, .intel-tr-pager { padding-left: 10px; padding-right: 10px; }
}

/* ------------------------------------------------- 18 · Gold & silver ---
   Four classes, and the reason for each one:

   .intel-metals-levels    the shared .metric-strip sits flush inside a panel,
                           which has no body padding of its own. This is the
                           inset, not a second strip.
   .intel-metals-drivers   three prose lists side by side. Nothing in the
                           product lays lists out in columns yet.
   .intel-md / -body       rendered markdown. There is no prose-document style
                           anywhere else here, and reusing .flow-table (the
                           only other <details>) would set a paragraph in
                           right-aligned mono.

   Everything else on this tab is .metric-strip, .data-table, .data-note,
   .tag and panel(). */

.intel-metals-levels { margin: 0; padding: 10px 18px; border-top: 0; }

.intel-metals-drivers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 4px 26px;
  padding: 12px 18px 16px;
}
.intel-metals-drivers h4 {
  margin: 0 0 6px;
  color: var(--muted);
  font: 700 11px/1.4 var(--font-mono);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.intel-metals-drivers ul { margin: 0; padding-left: 17px; }
.intel-metals-drivers li { margin-bottom: 7px; color: var(--body); font-size: 13.5px; line-height: 1.5; }

/* The 11.8k-character report: its own section, shut until asked for. */
.intel-md { padding: 0 0 4px; }
.intel-md > summary {
  padding: 11px 18px;
  color: var(--muted);
  font: 600 12px/1.4 var(--font-mono);
  letter-spacing: .04em;
  cursor: pointer;
}
.intel-md > summary:hover { color: var(--accent); }
.intel-md[open] > summary { border-bottom: 1px solid var(--line); }
.intel-md-body { padding: 4px 18px 8px; color: var(--body); font-size: 14px; line-height: 1.6; }
.intel-md-body h4 { margin: 20px 0 6px; color: var(--ink); font-size: 16px; font-weight: 700; }
.intel-md-body h5 { margin: 16px 0 5px; color: var(--ink); font-size: 14.5px; font-weight: 700; }
.intel-md-body h6 { margin: 14px 0 4px; color: var(--muted); font-size: 13px; font-weight: 700; }
.intel-md-body > :first-child { margin-top: 4px; }
.intel-md-body p { margin: 0 0 10px; }
.intel-md-body ul { margin: 0 0 12px; padding-left: 18px; }
.intel-md-body li { margin-bottom: 6px; }
.intel-md-body code { padding: 1px 4px; border-radius: var(--radius-sm); background: var(--surface-2); font: 12.5px var(--font-mono); }
.intel-md-body hr { margin: 16px 0; border: 0; border-top: 1px solid var(--line); }
.intel-md-body .table-wrap { margin: 0 0 14px; }
.intel-md-body .data-table { min-width: 0; font-size: 13px; }

@media (max-width: 900px) {
  .intel-metals-levels { padding-left: 12px; padding-right: 12px; }
  .intel-metals-drivers { padding-left: 12px; padding-right: 12px; }
  .intel-md > summary, .intel-md-body { padding-left: 12px; padding-right: 12px; }
}

/* === components/intraday.css === */
/* -------------------------------------------------------------------------
   SPY intraday workbench
   ------------------------------------------------------------------------- */

.spy-app {
  /* Was clamp(28px, 3.5vw, 52px). The eyebrow, the 66px headline and the
     rationale paragraph that used to sit under it are gone, so the page no
     longer needs a run-up to its first fact. */
  padding-top: 18px;
  padding-bottom: 90px;
  color: var(--ink);
  font-size: 16px;
}

/* The symbol is the loudest word on the page but it is still a heading, not a
   billboard: 24px per the page-head contract, down from clamp(38px, 5vw, 66px).
   It used to be painted in --accent as well, which said nothing — the accent is
   reserved for state now, so the title is ink like every other title. */
.spy-head h1 span { font-family: var(--font-mono); letter-spacing: -.02em; }
/* Phase · clock · session, the three facts that used to be a bordered card. */
.spy-head .page-head-note { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.spy-head .page-head-note > strong {
  color: var(--ink);
  font: 650 13px var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.spy-head .page-head-note > span:first-child {
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
}

/* One control row, unboxed. A bordered, filled, radiused panel around a row of
   controls that are already bordered is a box inside a box; a single hairline
   underneath separates the controls from the data just as well and costs
   18px less. */
.spy-command {
  display: flex;
  align-items: flex-end;
  gap: 8px 18px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  padding: 0 0 10px;
  border-bottom: 1px solid var(--line);
}
.spy-timeframes { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.spy-timeframes > span { margin: 0 7px; color: var(--muted); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; }
.spy-timeframes button {
  min-width: 48px;
  min-height: 38px;
  padding: 7px 11px;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--body);
  font: 650 13px var(--font-mono);
  cursor: pointer;
}
.spy-timeframes button:hover { border-color: var(--line-strong); }
.spy-timeframes button.is-active { border-color: var(--ink); background: var(--ink); color: var(--paper); }
.spy-replay-controls { display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap; }
.spy-replay-controls label { display: grid; gap: 3px; color: var(--muted); font-size: 11px; font-weight: 720; letter-spacing: .05em; text-transform: uppercase; }
.spy-replay-controls input,
.spy-replay-controls select {
  min-height: 36px;
  padding: 6px 9px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink);
  font: 12px var(--font-mono);
}
.spy-replay-controls input { width: 142px; }
.spy-replay-controls select { width: 184px; }
.spy-auto { display: inline-flex; align-items: center; gap: 7px; color: var(--body); font-size: 13px; white-space: nowrap; }
.spy-auto input { accent-color: var(--accent); }
.spy-auto span { color: var(--muted); font-family: var(--font-mono); }
.spy-last-refresh { color: var(--muted); font: 11.5px var(--font-mono); white-space: nowrap; }

/* Symbol picker — /intraday only. The fixed-symbol pages ship no such markup,
   so none of this applies to them. */
.spy-symbol-picker { display: grid; gap: 3px; align-self: flex-end; }
/* The panel anchors here — to the input alone — so it cannot cover the status
   line that sits below the picker. */
.spy-symbol-field { position: relative; display: block; }
.spy-symbol-picker > label {
  color: var(--muted); font-size: 11px; font-weight: 720;
  letter-spacing: .05em; text-transform: uppercase;
}
.spy-symbol-field > input {
  display: block;
  min-height: 36px;
  width: 268px;
  padding: 6px 9px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink);
  font: 650 13px var(--font-mono);
  text-transform: uppercase;
}
.spy-symbol-field > input::placeholder { font-weight: 400; text-transform: none; color: var(--muted); }
.spy-symbol-field > input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* 10.5px was below the readable floor for a line that reports a failed search.
   It sits on its own row now, so it can be read at the same size as the rest
   of the page furniture without pushing the controls around. */
.spy-symbol-status:not(:empty) {
  display: block;
  margin: -4px 0 10px;
  color: var(--muted);
  font: 12px var(--font-mono);
}

.spy-symbol-results {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 40;
  width: 268px;
  max-height: 320px;
  overflow-y: auto;
  margin-top: 5px;
  padding: 4px;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: var(--paper);
  box-shadow: 0 12px 28px rgb(0 0 0 / .16);
}
/* One symbol per row. Nothing else competes for the width, so a long ticker
   still fits and the rows cannot collide. */
.spy-symbol-option {
  display: block;
  width: 100%;
  min-height: 36px;
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: none;
  color: var(--ink);
  font: 700 13px var(--font-mono);
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}
.spy-symbol-option:hover,
.spy-symbol-option.is-active { border-color: var(--line-strong); background: var(--surface); }
.spy-symbol-option.is-current { color: var(--accent); }

.spy-symbol-empty { display: grid; gap: 3px; margin: 0; padding: 10px 9px; }
.spy-symbol-empty strong { color: var(--ink); font-size: 12.5px; }
.spy-symbol-empty span { color: var(--muted); font-size: 11.5px; line-height: 1.45; }

/* Both state lines wear .data-note; these are the page-local margins only.
   The uncalibrated banner is warn-toned rather than error-toned on purpose:
   the payload is complete and the gates ran, only the tuning is unproven. */
.spy-calibration-note { margin: 0 0 10px; }
.spy-gate { margin: 0 0 12px; }

/* The jump strip carries a mono ordinal so the six sections can be referred to
   by number. Bronze on the numbers was decoration; they are muted now, and the
   only colour in the strip marks where the reader is. */
.spy-jumps { margin-bottom: 14px; }
.spy-jumps .seg-item > span {
  margin-right: 6px;
  color: var(--muted);
  font: 600 11px/1.2 var(--font-mono);
}
.spy-jumps .seg-item[aria-current="true"] > span,
.spy-jumps .seg-item.is-active > span { color: inherit; }

/* Six sections of one page, never hidden. The page scrolls; the jump strip
   above only moves the reader to a section it never conceals. */
.spy-panel { display: block; }

/* The page's verdict. It keeps a shape when nothing else does, because it is
   the one block a reader looks for first — but the shape is a 3px rule in the
   state colour, not a border, a radius and a fill. The colour is the state; it
   is never decoration here. */
.spy-decision-gate {
  display: grid;
  grid-template-columns: minmax(150px, .3fr) minmax(180px, .38fr) minmax(0, 1.32fr);
  gap: 20px;
  align-items: center;
  margin-bottom: 14px;
  padding: 2px 0 14px 14px;
  border-left: 3px solid var(--line-strong);
  border-bottom: 1px solid var(--line);
}
.spy-decision-gate.is-research_candidate,
.spy-decision-gate.is-historical_conditions_met { border-left-color: var(--up); }
.spy-decision-gate.is-early_research_candidate,
.spy-decision-gate.is-historical_early_conditions_met,
.spy-decision-gate.is-blocked,
.spy-decision-gate.is-historical_blocked { border-left-color: var(--warning, var(--accent-2)); }
.spy-decision-signal { border-right: 1px solid var(--line); }
.spy-decision-signal span { display: block; color: var(--muted); font-size: 11px; font-weight: 750; letter-spacing: .07em; text-transform: uppercase; }
.spy-decision-signal strong { display: block; margin-top: 5px; font: 720 clamp(28px, 4vw, 42px)/1 var(--font-mono); }
.spy-confidence-hero { padding-right: 20px; border-right: 1px solid var(--line); }
.spy-confidence-hero > span { display: block; color: var(--muted); font-size: 11px; font-weight: 750; letter-spacing: .07em; text-transform: uppercase; }
.spy-confidence-hero > strong { display: block; margin-top: 4px; font: 720 27px var(--font-mono); }
.spy-confidence-hero > strong small { color: var(--muted); font: 600 11px var(--font-sans); letter-spacing: .04em; }
.spy-confidence-track,
.spy-component-track { height: 5px; margin-top: 8px; border-radius: 999px; background: var(--line); overflow: hidden; }
.spy-confidence-track i,
.spy-component-track i { display: block; height: 100%; border-radius: inherit; background: var(--ink-2); }
.spy-confidence-hero.is-low .spy-confidence-track i { background: var(--down); }
.spy-confidence-hero.is-limited .spy-confidence-track i { background: var(--warning, var(--accent-2-ink)); }
.spy-confidence-hero.is-moderate .spy-confidence-track i { background: var(--up); }
.spy-decision-copy strong { display: block; font-size: 17px; }
.spy-decision-copy p { margin: 6px 0 0; color: var(--body); font-size: 13px; line-height: 1.55; }
.spy-decision-copy .spy-caution-copy { color: var(--warning, var(--accent-2-ink)); font-weight: 650; }
.spy-decision-layout { grid-template-columns: 1fr; margin-bottom: 17px; }
.spy-timing-card { margin-bottom: 17px; }
.spy-timing-body { padding: 12px 0 0; }
.spy-timing-now { display: grid; grid-template-columns: minmax(0, 1fr) 36px minmax(0, 1fr); gap: 12px; align-items: stretch; }
.spy-stage-mark { padding: 0 0 0 12px; border-left: 2px solid var(--line); }
.spy-stage-mark.is-early { border-left-color: var(--warning, var(--accent-2)); }
.spy-stage-mark.is-confirmation { border-left-color: var(--line-strong); }
.spy-stage-mark > span { display: block; color: var(--muted); font-size: 11px; font-weight: 750; letter-spacing: .06em; text-transform: uppercase; }
.spy-stage-mark > strong { display: block; margin-top: 6px; font: 700 24px var(--font-mono); }
.spy-stage-mark > small { display: block; margin-top: 5px; color: var(--body); font-size: 12px; }
.spy-stage-arrow { display: grid; place-items: center; color: var(--muted); font: 650 24px var(--font-mono); }
.spy-lag-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1px; margin-top: 12px; background: var(--line); }
.spy-confidence-card { margin-bottom: 17px; }
.spy-confidence-body { padding: 12px 0 0; }
.spy-confidence-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; }
.spy-confidence-component { min-width: 0; padding: 0 12px; border-left: 1px solid var(--line); }
.spy-confidence-component > div:first-child { display: flex; justify-content: space-between; gap: 8px; }
.spy-confidence-component strong { font-size: 12px; }
.spy-confidence-component span { color: var(--ink); font: 650 12px var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.spy-confidence-component p { margin: 8px 0 0; color: var(--muted); font-size: 11.5px; line-height: 1.4; }
.spy-confidence-notes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 12px; padding: 14px; border-top: 1px solid var(--line); }
.spy-confidence-notes h4 { margin: 0 0 8px; font-size: 11.5px; letter-spacing: .05em; text-transform: uppercase; }
.spy-readiness-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px 12px; }
.spy-readiness-list > div { display: grid; grid-template-columns: 17px minmax(0, 1fr) auto; gap: 5px; color: var(--body); font-size: 11.5px; }
.spy-readiness-list > div > span { color: var(--down); font: 700 12px var(--font-mono); }
.spy-readiness-list > div.is-pass > span { color: var(--up); }
.spy-readiness-list > div.is-na > span,
.spy-readiness-list small { color: var(--muted); }
.spy-ticket-body { padding: 12px 0 0; }
.spy-ticket-body .spy-plan-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); margin-top: 0; }
.spy-ticket-body h4 { margin: 17px 0 7px; font-size: 12px; letter-spacing: .04em; text-transform: uppercase; }
.spy-ticket-protocol { margin: 13px 0 0; color: var(--body); font-size: 12.5px; line-height: 1.55; }
.spy-rule-list { display: grid; gap: 6px; margin: 0; padding-left: 19px; color: var(--body); font-size: 12.5px; line-height: 1.45; }
.spy-rule-list.is-blocked { color: var(--down); }
.spy-history-body { padding-top: 12px; }
.spy-history-banner {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 15px;
  align-items: center;
  padding: 0 0 0 12px;
  border-left: 2px solid var(--line-strong);
}
.spy-history-banner.is-qualified { border-left-color: var(--up); }
.spy-history-banner.is-unqualified { border-left-color: var(--warning, var(--accent-2)); }
.spy-history-banner > div > strong { display: block; font-size: 13px; }
.spy-history-banner p { margin: 4px 0 0; color: var(--body); font-size: 12.5px; line-height: 1.5; }
.spy-history-flow {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  margin-top: 13px;
  background: var(--line);
}
.spy-history-step { min-width: 0; padding: 13px; background: var(--paper); }
.spy-history-step > span,
.spy-history-step > strong,
.spy-history-step > small { display: block; }
.spy-history-step > span { color: var(--muted); font-size: 11px; font-weight: 750; letter-spacing: .055em; text-transform: uppercase; }
.spy-history-step > strong { margin-top: 6px; font: 700 17px var(--font-mono); }
.spy-history-step > small { margin-top: 5px; color: var(--body); font-size: 11.5px; line-height: 1.4; }
.spy-history-step.is-good > strong { color: var(--up); }
.spy-history-step.is-bad > strong { color: var(--down); }
.spy-history-plan { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; }
.spy-history-exit { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; }
.spy-history-qualified-note {
  margin: 12px 0 0;
  padding: 2px 0 2px 11px;
  border-left: 2px solid var(--warning, var(--accent-2));
  color: var(--body);
  font-size: 13px;
  line-height: 1.5;
}
.spy-combination-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 12px 0 0; }
.spy-combination { padding: 0 0 0 12px; border-left: 2px solid var(--line); }
.spy-combination.is-aligned { border-left-color: var(--up); }
.spy-combination.is-conflicted { border-left-color: var(--down); }
.spy-combination-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 9px; }
.spy-combination-head h4 { margin: 2px 0 0; font-size: 14px; }
.spy-combination-stage { display: block; color: var(--muted); font-size: 11px; font-weight: 750; letter-spacing: .06em; text-transform: uppercase; }
.spy-combination-check { display: grid; grid-template-columns: 18px minmax(150px, .8fr) minmax(0, 1.2fr); gap: 7px; padding: 7px 0; border-top: 1px solid var(--line); font-size: 11.5px; }
.spy-combination-check > span { color: var(--down); font: 700 13px var(--font-mono); }
.spy-combination-check.is-pass > span { color: var(--up); }
.spy-combination-check strong { color: var(--body); font-size: 11.5px; }
.spy-combination-check small { color: var(--muted); font: 11.5px var(--font-mono); overflow-wrap: anywhere; }
/* .app-section-head supplies the 17px h2 and the row; this is the as-of stamp
   only. It used to carry 19px of top padding purely to optically align with a
   40px headline that no longer exists. */
.spy-asof { color: var(--muted); font: 12px var(--font-mono); text-align: right; }

/* The five stat grids are .metric-strip now — no borders, no radii, no fill,
   one hairline rule top and bottom. The number is the same size it always was;
   the height came back from the boxes around it. */
.spy-stat-grid { margin-bottom: 14px; }
.spy-positive { color: var(--up) !important; }
.spy-negative { color: var(--down) !important; }

/* Score and direction per candle (/intraday only).
   Two rows sharing one column per bar, so a score bar always sits directly
   above the letter for the same candle. */
.spy-candle-card { margin-bottom: 14px; }
.spy-candle-body { padding: 12px 0; }
.spy-candle-summary { margin-bottom: 12px; }
/* The strip scrolls rather than compressing: a full 1-minute session is 390
   bars, and squeezing those into the panel width would render every candle
   sub-pixel. Horizontal scroll is contained here so the page never scrolls. */
.spy-candle-strip {
  overflow-x: auto;
  padding: 10px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.spy-candle-track { display: flex; gap: 2px; align-items: flex-end; min-width: min-content; }
.spy-candle { flex: 1 0 15px; min-width: 15px; }
.spy-candle-bar {
  position: relative;
  height: 46px;
  /* The zero line. Scores are signed, so the baseline is the centre and a bar
     grows up or down from it — a bottom-anchored bar would read a −0.40 score
     as a strong positive. */
  background: linear-gradient(to bottom, transparent calc(50% - 1px), var(--line-strong) calc(50% - 1px), var(--line-strong) calc(50% + 1px), transparent calc(50% + 1px));
}
.spy-candle-bar i { position: absolute; left: 1px; right: 1px; display: block; border-radius: 2px; }
.spy-candle-bar i.is-up { bottom: 50%; background: var(--up); }
.spy-candle-bar i.is-down { top: 50%; background: var(--down); }
.spy-candle-code {
  display: block;
  margin-top: 6px;
  text-align: center;
  font: 700 12px/1.5 var(--font-mono);
  border-radius: 4px;
  color: var(--muted);
  background: var(--surface);
}
.spy-candle.is-b .spy-candle-code { color: var(--up); background: color-mix(in srgb, var(--up) 14%, transparent); }
.spy-candle.is-s .spy-candle-code { color: var(--down); background: color-mix(in srgb, var(--down) 14%, transparent); }
.spy-candle-axis {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 9px;
  color: var(--muted);
  font: 650 12px var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.spy-candle-axis span:nth-child(2) { color: var(--body); text-align: center; }

/* A block inside a section, not a card on a board. The border, radius and fill
   are gone: what separates one block from the next is the rule under its own
   heading, which is a line the eye follows rather than a shape it has to
   parse. Keeps overflow:hidden so the chart and the candle strip still clip. */
.spy-card {
  min-width: 0;
  overflow: hidden;
}
.spy-card > header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 15px;
  padding: 0 0 7px;
  border-bottom: 1px solid var(--line);
}
.spy-card > header h3 { margin: 0; font-size: 15px; font-weight: 650; letter-spacing: -.01em; }
.spy-card > header span { color: var(--muted); font: 11.5px var(--font-mono); text-align: right; }
.spy-panel > .spy-card { margin-bottom: 18px; }
/* The two-up blocks still need a visible boundary between them, because side
   by side there is no "below" for a rule to divide. One hairline between,
   never a box around each. */
.spy-two-grid > .spy-card + .spy-card,
.spy-three-grid > .spy-card + .spy-card { border-left: 1px solid var(--line); padding-left: 14px; }
.spy-tape-layout { display: grid; grid-template-columns: minmax(0, 2.15fr) minmax(300px, .75fr); gap: 14px; margin-bottom: 17px; }
.spy-chart { min-height: 385px; padding: 10px 0 0; }
.spy-chart svg { display: block; width: 100%; height: 380px; overflow: visible; }
.spy-chart-grid { stroke: var(--line); stroke-width: 1; }
.spy-chart-price { fill: none; stroke: var(--ink); stroke-width: 2.2; }
.spy-chart-vwap { fill: none; stroke: var(--accent-2-ink); stroke-width: 1.5; stroke-dasharray: 5 4; }
.spy-chart-fast { fill: none; stroke: var(--up); stroke-width: 1.25; opacity: .9; }
.spy-chart-slow { fill: none; stroke: var(--down); stroke-width: 1.25; opacity: .82; }
.spy-chart-volume { fill: rgba(var(--accent-rgb), .16); }
.spy-chart text { fill: var(--muted); font: 11px var(--font-mono); }
.spy-chart-legend { display: flex; gap: 16px; flex-wrap: wrap; padding: 8px 0 4px; color: var(--muted); font-size: 12px; }
.spy-chart-legend span::before { content: ""; display: inline-block; width: 15px; height: 2px; margin-right: 6px; vertical-align: 3px; background: var(--ink); }
.spy-chart-legend .is-vwap::before { background: var(--accent-2-ink); }
.spy-chart-legend .is-fast::before { background: var(--up); }
.spy-chart-legend .is-slow::before { background: var(--down); }

.spy-state-body { padding: 10px 0 0; }
.spy-state-hero { padding: 4px 0 16px; border-bottom: 1px solid var(--line); }
.spy-state-hero strong { display: block; font: 700 28px var(--font-mono); font-variant-numeric: tabular-nums; }
.spy-state-hero span { color: var(--muted); font-size: 13px; }
.spy-state-row { display: flex; justify-content: space-between; gap: 16px; padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.spy-state-row:last-child { border-bottom: 0; }
.spy-state-row span { color: var(--muted); }
.spy-state-row strong { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.spy-table { min-width: 1060px; font-size: 14.5px; }
.spy-table th { padding: var(--data-row-y, 8px) var(--data-row-x, 14px); font-size: 11.5px; }
.spy-table td { padding: var(--data-row-y, 8px) var(--data-row-x, 14px); line-height: 1.4; }
.spy-number { color: var(--ink); font: 620 14px var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.spy-number small { display: block; margin-top: 3px; color: var(--muted); font: 12px var(--font-sans); white-space: normal; }
.spy-badge {
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  color: var(--body);
  font-size: 11px;
  font-weight: 750;
  letter-spacing: .035em;
  text-transform: uppercase;
  white-space: nowrap;
}
.spy-badge.is-good { border-color: var(--up); color: var(--up); }
.spy-badge.is-bad { border-color: var(--down); color: var(--down); }
.spy-badge.is-warn { border-color: var(--warning, var(--accent-2)); color: var(--warning, var(--accent-2-ink)); }

.spy-event-list { display: grid; max-height: 460px; overflow-y: auto; }
.spy-event {
  font-variant-numeric: tabular-nums;
  display: grid;
  grid-template-columns: 64px 105px minmax(160px, .8fr) minmax(220px, 1.5fr) 90px;
  gap: 13px;
  align-items: center;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  color: var(--body);
  font-size: 13px;
}
.spy-event:last-child { border-bottom: 0; }
.spy-event time,
.spy-event-price { font-family: var(--font-mono); }
.spy-event-detail { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.spy-setup-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-bottom: 17px; }
.spy-setup {
  min-width: 0;
  padding: 0 0 0 13px;
  border-left: 2px solid var(--line);
}
.spy-setup.is-active { border-left-color: var(--up); }
.spy-setup.is-watch { border-left-color: var(--warning, var(--accent-2)); }
.spy-setup-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 13px; }
.spy-setup h3 { margin: 0; font-size: 16px; }
.spy-setup-score { margin-top: 4px; color: var(--muted); font: 12.5px var(--font-mono); font-variant-numeric: tabular-nums; }
.spy-setup-scenario { margin: 13px 0 0; color: var(--body); font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
.spy-setup-reason { margin: 7px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.5; }
.spy-plan-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1px; margin-top: 13px; background: var(--line); }
.spy-plan-cell { padding: 9px 10px; background: var(--paper); }
.spy-plan-cell span,
.spy-plan-cell strong { display: block; }
.spy-plan-cell span { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
.spy-plan-cell strong { margin-top: 4px; font: 650 13px var(--font-mono); font-variant-numeric: tabular-nums; }
.spy-risk-body { padding: 12px 0 0; }
.spy-risk-warning { margin: 0; color: var(--body); font-size: 13px; line-height: 1.55; }

.spy-level-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 17px; }
.spy-level-card { min-width: 0; }
.spy-level-card h3 { margin: 0; padding: 0 0 7px; border-bottom: 1px solid var(--line); font-size: 15px; font-weight: 650; }
.spy-level { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 11px; padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.spy-level:last-child { border-bottom: 0; }
.spy-level span { color: var(--body); }
.spy-level strong { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }
.spy-level em { color: var(--muted); font: normal 12px var(--font-mono); font-variant-numeric: tabular-nums; }

.spy-three-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 17px; }
.spy-two-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.spy-metric-list { padding: 4px 0 8px; }
.spy-metric-row { display: flex; align-items: baseline; justify-content: space-between; gap: 15px; padding: 9px 0; border-bottom: 1px solid var(--line); }
.spy-metric-row:last-child { border-bottom: 0; }
.spy-metric-row span { color: var(--muted); font-size: 13px; }
.spy-metric-row strong { font: 650 13px var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }

.spy-evidence-status { margin-bottom: 14px; }
.spy-data-body { padding: 12px 0 0; }
.spy-data-body p { margin: 0 0 14px; color: var(--body); font-size: 13px; line-height: 1.55; }

@media (max-width: 1180px) {
  .spy-level-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .spy-confidence-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .spy-tape-layout, .spy-two-grid { grid-template-columns: 1fr; }
  .spy-two-grid > .spy-card + .spy-card { border-left: 0; padding-left: 0; padding-top: 14px; }
  .spy-combination-grid { grid-template-columns: 1fr; }
  .spy-confidence-notes { grid-template-columns: 1fr; }
  .spy-lag-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .spy-history-flow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .spy-history-plan,
  .spy-history-exit { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .spy-three-grid { grid-template-columns: 1fr; }
  .spy-three-grid > .spy-card + .spy-card { border-left: 0; padding-left: 0; padding-top: 14px; }
}

@media (max-width: 900px) {
}

@media (max-width: 680px) {
  .spy-app { padding-top: 14px; }
  /* The picker stops being a fixed-width control and the dropdown tracks it,
     so neither can overhang the viewport on a phone. */
  .spy-symbol-field > input { width: 100%; }
  .spy-symbol-results { width: 100%; }
  .spy-setup-grid, .spy-level-grid { grid-template-columns: 1fr; }
  .spy-section-head { display: block; }
  .spy-decision-gate { grid-template-columns: 1fr; }
  .spy-timing-now { grid-template-columns: 1fr; }
  .spy-stage-arrow { transform: rotate(90deg); }
  .spy-lag-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .spy-decision-signal { padding-bottom: 12px; border-right: 0; border-bottom: 1px solid var(--line); }
  .spy-confidence-hero { padding: 0 0 12px; border-right: 0; border-bottom: 1px solid var(--line); }
  .spy-confidence-grid { grid-template-columns: 1fr; }
  .spy-readiness-list { grid-template-columns: 1fr; }
  .spy-history-banner { grid-template-columns: 1fr; }
  .spy-history-flow { grid-template-columns: 1fr; }
  .spy-ticket-body .spy-plan-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .spy-combination-check { grid-template-columns: 18px minmax(0, 1fr); }
  .spy-combination-check small { grid-column: 2; }
  .spy-asof { display: block; text-align: left; }
  .spy-event { grid-template-columns: 58px 90px minmax(150px, 1fr); }
  .spy-event-detail, .spy-event-price { display: none; }
  .spy-chart { min-height: 310px; }
  .spy-chart svg { height: 305px; }
}

@media (max-width: 430px) {
}

/* A divider belongs BETWEEN things. The first cell of a row has nothing to its
   left, so it takes no rule and no inset. */
.spy-confidence-grid > .spy-confidence-component:first-child { border-left: 0; padding-left: 0; }

/* === components/nav.css === */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(var(--paper-rgb), 0.82);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--line);
}
/* Firefox < 103 and older WebKit views get no blur — a translucent bar would
   let body text show through and become unreadable, so go near-opaque. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav { background: rgba(var(--paper-rgb), 0.98); }
}
.nav::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(var(--accent-2-rgb), 0.45) 50%,
    transparent 100%
  );
  opacity: 0.6;
  pointer-events: none;
}
.nav-inner {
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  line-height: 1;
  flex: none;
}
.brand svg {
  display: block;
  flex: none;
  width: clamp(28px, 2.6vw, 33px);
  height: clamp(28px, 2.6vw, 33px);
}
/* The wordmark. The app bar uses this same class on the same markup rather
   than a lookalike of its own — an earlier copy had already drifted to
   -0.02em with neither font-feature setting, which spelled the brand in a
   visibly different cut of the same typeface. */
.brand-word {
  font-family: var(--font-sans);
  font-weight: 700;
  letter-spacing: -0.045em;
  font-size: clamp(24px, 2.4vw, 29px);
  line-height: 1;
  color: var(--ink);
  font-feature-settings: "cv11";
  font-variation-settings: "wght" 700;
}

/* Wrapper so the links and the CTAs collapse into one panel together on
   mobile — no duplicated markup, nothing hidden-but-focusable. */
.nav-menu {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: space-between;
  gap: clamp(16px, 2.5vw, 32px);
  margin-left: clamp(20px, 3.5vw, 56px);
  min-width: 0;
}
.nav-links {
  display: flex;
  align-items: center;
  gap: clamp(16px, 2.2vw, 32px);
  font-size: 14px;
  color: #374151;
}
.nav-links a {
  position: relative;
  padding: 4px 2px;
  white-space: nowrap;
  transition: color .15s ease;
}
.nav-links a::after {
  content: "";
  position: absolute;
  left: 2px; right: 2px; bottom: -2px;
  height: 1px;
  background: var(--accent-2);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .2s ease;
}
.nav-links a:hover { color: var(--ink); }
.nav-links a:hover::after { transform: scaleX(1); }
.nav-cta { display: flex; gap: 8px; align-items: center; flex: none; }

/* ---------------------------------------------------------------------------
   Mobile menu toggle
   --------------------------------------------------------------------------- */
.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  margin-right: -10px;
  align-items: center;
  justify-content: center;
  flex: none;
  cursor: pointer;
  color: var(--ink);
  border-radius: var(--radius-sm);
  -webkit-appearance: none;
  appearance: none;
}
.nav-toggle-box {
  position: relative;
  display: block;
  width: 22px;
  height: 16px;
}
.nav-toggle-bar,
.nav-toggle-bar::before,
.nav-toggle-bar::after {
  position: absolute;
  left: 0;
  width: 22px;
  height: 2px;
  background-color: currentColor;
  border-radius: 2px;
  transition: transform .2s ease, background-color .15s ease;
}
.nav-toggle-bar { top: 7px; }
.nav-toggle-bar::before { content: ""; top: -7px; }
.nav-toggle-bar::after  { content: ""; top: 7px; }
.nav.is-open .nav-toggle-bar { background-color: transparent; }
.nav.is-open .nav-toggle-bar::before { transform: translateY(7px) rotate(45deg); }
.nav.is-open .nav-toggle-bar::after  { transform: translateY(-7px) rotate(-45deg); }

@media (max-width: 900px) {
  .nav-toggle { display: inline-flex; }

  .nav-menu {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin: 0;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    padding: 4px var(--gutter) 20px;
    background: var(--paper);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 24px 40px -24px rgba(var(--ink-rgb), 0.35);
    /* Long menus on short landscape phones must scroll, not run off-screen. */
    max-height: calc(100vh - var(--nav-h));
    max-height: calc(100dvh - var(--nav-h));
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .nav.is-open .nav-menu { display: flex; }

  .nav-links {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    font-size: 16px;
  }
  .nav-links a {
    display: flex;
    align-items: center;
    min-height: 52px;
    padding: 8px 2px;
    border-bottom: 1px solid var(--line);
  }
  .nav-links a::after { display: none; }

  .nav-cta {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    margin-top: 18px;
  }
  .nav-cta .btn {
    justify-content: center;
    min-height: 48px;
    font-size: 15px;
  }
}

/* === components/options-trade.css === */
/* ==========================================================================
   Options Trade (/options-trade) — two views, one page.

   Almost nothing is here. The page is built from the shared data layer —
   .page-head and .app-section from workspace.css, .metric-strip, .data-table,
   .data-pane, .data-note, .tag and .seg from data.css — because a research
   page that invents its own surfaces is a second design system.

   WHAT IS HERE, AND WHY NOTHING IN THE SHARED LAYER COVERED IT
   -----------------------------------------------------------
   .odte-gex        the per-strike gamma chart. A chart needs a plot box, a
                    hover tooltip and stroke/fill hooks that CSS custom
                    properties can theme; there is no chart primitive in the
                    shared layer (flow-chart.css and rotation-chart.css are
                    each their own file for the same reason).
   .odte-path       the session-path chart, same argument.
   .odte-trade-*    the contract's target/stop pair, set as one cell so the
                    two prices and the arrow between them stay on one line and
                    the column reads down. A .metric would put a label on each.
   .odte-flag       a row-level warning that is NOT a tag: .tag is a constant
                    label inside a cell, and this one has to be legible as a
                    sentence ("calibration borrowed from SPY") because that is
                    the caveat itself, not a category.
   .odte-auto       the auto-refresh checkbox, copied in spirit from
                    .exec-auto on /execution so the two live pages carry the
                    same control.

   COLOUR
   ------
   No new palette. The gamma chart is a DIVERGING encoding — net gamma either
   side of zero — and this site already ships a validated diverging pair for
   exactly that job in flow-chart.css (--flow-up / --flow-down: inflow above a
   zero line, outflow below). Re-validated here against both themes' panel and
   page surfaces with the dataviz validator, all six checks passing on all
   four:

     light #FCFAF5 / #F6F2E9   #0E7C61 ↔ #B3261E   ΔE 8.9 deutan · 26.5 normal
     dark  #2A2A2A / #1F1F1F   #16A085 ↔ #E45F52   ΔE 9.6 deutan · 27.3 normal

   The sign is also carried by POSITION — left or right of the zero line — so
   no reading rests on hue. Spot and the gamma flip are annotation, not data,
   and wear ink/bronze text tokens accordingly.
   ========================================================================== */

/* ONE ROW, THREE GROUPS, in the order the desk reads them:
     [ Live 0DTE ]  [ SPY ][ QQQ ][ SPXW ]  [ Morning report ]

   The symbols sit BETWEEN the two view buttons, which are one tablist — they
   are one choice with one selected tab and must not be split into two
   controls. So the view group keeps its element and `display: contents`
   drops only its box: its buttons become items of this row, `order` puts
   the live one first and the morning one last, and each then wears the pill
   itself. Selectors are two classes deep because workspace.css defines .seg
   AFTER this file in the bundle and would otherwise win on equal specificity.

   The row itself never wraps. It is one unbreakable item at the head of the
   tools row, so when width runs out the trailing controls — auto-refresh,
   sound, the as-of stamp, Refresh — wrap under it and the choices stay
   together on the first line. */
.odte-tabs { display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center; gap: 10px; flex: none; }
.odte-tabs > .odte-view-seg { display: contents; }
.odte-tabs > .odte-view-seg > .seg-item {
  order: 3;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-2);
}
.odte-tabs > .odte-view-seg > .seg-item[data-view="live"] { order: 1; }
.odte-tabs > .odte-view-seg > .seg-item[aria-selected="true"] {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--paper);
}
.odte-tabs > .odte-symbol-seg { order: 2; }

/* The morning report has no symbol. The group stays exactly where it is,
   greyed and inert — never hidden, because removing it would reflow the row
   on every view change, which is the jitter this layout exists to avoid. */
.odte-tabs > .odte-symbol-seg.is-inert { opacity: .45; }
.odte-tabs > .odte-symbol-seg.is-inert .seg-item { cursor: not-allowed; }

@media (max-width: 620px) {
  /* Five buttons do not fit a phone row: back to two stacked full-width
     groups, which is what .seg does here on its own. */
  .odte-tabs { flex-wrap: wrap; gap: 8px; width: 100%; }
  .odte-tabs > .odte-view-seg { display: flex; width: 100%; }
  .odte-tabs > .odte-view-seg > .seg-item {
    flex: 1;
    justify-content: center;
    padding: 5px 8px;
    border: 0;
    border-radius: calc(var(--radius-md) - 3px);
    background: transparent;
  }
  .odte-tabs > .odte-view-seg > .seg-item[aria-selected="true"] { background: var(--ink); }
  .odte-tabs > .odte-symbol-seg { width: 100%; }
}

/* The auto-refresh control, matching /execution's. */
.odte-auto {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 12.5px;
  white-space: nowrap;
  cursor: pointer;
}
.odte-auto input { accent-color: var(--accent); }
.odte-auto-n { font-variant-numeric: tabular-nums; }

/* The as-of stamp lives in .page-head-tools beside the controls. It is the
   DATA's clock, never the browser's, so it is set in mono and never wraps. */
.odte-page .dash-updated {
  color: var(--muted);
  font: 500 12px/1.4 var(--font-mono);
  white-space: nowrap;
}
.odte-page .dash-updated.is-stale { color: var(--warning); font-weight: 700; }


/* ----- the contract's target → stop pair ---------------------------------
   One cell, two prices, one arrow. Two columns would double the header row
   for a pair that is only ever read together. */
.odte-trade-pair {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.odte-trade-arrow { color: var(--muted); }
.odte-trade-sub { display: block; margin-top: 1px; color: var(--muted); font-size: 11.5px; font-variant-numeric: tabular-nums; }

/* The side a contract is on. Coloured because the word IS the direction —
   the one thing data.css allows colour for. */
.odte-side { font-weight: 700; text-transform: uppercase; letter-spacing: .04em; font-size: 12px; }
.odte-side.is-call { color: var(--up); }
.odte-side.is-put { color: var(--down); }
.odte-side.is-none { color: var(--muted); }

/* A caveat that rides a row. Not a .tag: a tag is a one-word category and
   this is a sentence the reader has to actually read. */
.odte-flag {
  display: block;
  margin-top: 3px;
  max-width: 22ch;
  margin-left: auto;
  text-align: right;
  color: var(--warning);
  font-size: 11.5px;
  line-height: 1.35;
}

/* The OCC symbol. Long, constant-width, and never the thing you read first. */
.odte-occ { color: var(--muted); font: 500 11.5px/1.4 var(--font-mono); white-space: nowrap; }

/* One symbol's own heading inside the detail section. */
.odte-sym { font-weight: 700; letter-spacing: -0.01em; }


/* ----- charts -------------------------------------------------------------
   Both plots are inline SVG sized by viewBox, so they scale with the column
   and carry no raster. The wrapper is positioned because the tooltip is an
   absolutely positioned HTML node — text in HTML rather than SVG so it wraps,
   selects and inherits type like everything else. */
.odte-charts {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 16px var(--data-gap);
  align-items: start;
}
@media (max-width: 1100px) { .odte-charts { grid-template-columns: minmax(0, 1fr); } }

.odte-chart-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin: 0 0 6px;
}
.odte-chart-head h3 { margin: 0; font-size: 13.5px; letter-spacing: -0.01em; }
.odte-chart-head p { margin: 0; color: var(--muted); font-size: 12px; }

.odte-plot { position: relative; }
.odte-plot svg { display: block; width: 100%; height: auto; overflow: visible; }

/* Axes and grid: solid hairlines one step off the surface, never dashed. */
.odte-grid { stroke: var(--line); stroke-width: 1; }
.odte-zero { stroke: var(--line-strong); stroke-width: 1; }
.odte-tick { font-family: var(--font-mono); font-size: 10.5px; fill: var(--muted); }
.odte-axis-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  fill: var(--muted);
}

/* The diverging pair, from flow-chart.css. Declared as a fallback too, so
   this file is readable on its own and the chart still themes if the files
   are ever split. */
.odte-bar.is-up { fill: var(--flow-up, #0E7C61); }
.odte-bar.is-down { fill: var(--flow-down, #B3261E); }
/* The strike the price is sitting at. Not a different hue — the same fill,
   with the surface ring that marks it as the one being read. */
.odte-bar.is-here { stroke: var(--ink); stroke-width: 1.5; }
.odte-band { fill: var(--surface-2); }

/* Annotation. Ink for spot (where you are), bronze for the gamma flip (the
   level the regime turns on). Solid, 1.5px, with a direct label — never a
   dash, which reads as a projection. */
.odte-mark-spot { stroke: var(--ink); stroke-width: 1.5; }
.odte-mark-flip { stroke: var(--accent-2-ink); stroke-width: 1.5; }
.odte-mark-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  paint-order: stroke;
  stroke: var(--paper);
  stroke-width: 3px;
  stroke-linejoin: round;
}
.odte-mark-label.is-spot { fill: var(--ink); font-weight: 700; }
.odte-mark-label.is-flip { fill: var(--accent-2-ink); }

/* The session path: a 2px line with a ringed end dot. */
.odte-line { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.odte-dot { fill: var(--ink); stroke: var(--paper); stroke-width: 2; }
.odte-level { stroke-width: 1.5; }
.odte-level.is-target { stroke: var(--flow-up, #0E7C61); }
.odte-level.is-stop { stroke: var(--flow-down, #B3261E); }
.odte-level.is-entry { stroke: var(--line-strong); }
.odte-level-label { font-family: var(--font-mono); font-size: 10.5px; paint-order: stroke; stroke: var(--paper); stroke-width: 3px; stroke-linejoin: round; }
.odte-level-label.is-target { fill: var(--flow-up, #0E7C61); }
.odte-level-label.is-stop { fill: var(--flow-down, #B3261E); }
.odte-level-label.is-entry { fill: var(--muted); }

.odte-hit { fill: transparent; cursor: crosshair; }

.odte-tip {
  position: absolute;
  z-index: 3;
  min-width: 158px;
  padding: 8px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--surface);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .12);
  font-size: 12px;
  pointer-events: none;
}
.odte-tip-title { margin-bottom: 5px; color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.odte-tip-row { display: flex; justify-content: space-between; gap: 14px; padding: 1px 0; color: var(--body); }
.odte-tip-row b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }

/* The two poles, named. Position already carries the sign; this says what the
   sign MEANS, which position cannot. */
.odte-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 6px 0 0; color: var(--muted); font-size: 11.5px; }
.odte-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.odte-swatch { width: 13px; height: 11px; border-radius: 3px; box-sizing: border-box; }
.odte-swatch.is-up { background: var(--flow-up, #0E7C61); }
.odte-swatch.is-down { background: var(--flow-down, #B3261E); }


/* ----- the model's written plan ------------------------------------------
   Collapsed by default and rendered as nodes, never as markup. The measure is
   capped because a 14 KB plan set across a 1440px window is unreadable. */
.odte-md > summary { cursor: pointer; padding: 7px var(--data-row-x); color: var(--body); font-size: 13px; }
.odte-md > summary::marker { color: var(--muted); }
.odte-md-body { max-width: 86ch; padding: 4px var(--data-row-x) 14px; color: var(--body); font-size: 14px; line-height: 1.6; }
.odte-md-body h4, .odte-md-body h5, .odte-md-body h6 { margin: 18px 0 6px; color: var(--ink); font-size: 14px; letter-spacing: -0.01em; }
.odte-md-body p { margin: 0 0 10px; }
.odte-md-body ul { margin: 0 0 10px; padding-left: 20px; }
.odte-md-body li { margin: 0 0 4px; }
.odte-md-body code { font-family: var(--font-mono); font-size: 12.5px; }
.odte-md-body hr { margin: 14px 0; border: 0; border-top: 1px solid var(--line); }

/* The measured-findings list under the morning view's ranking. Definition
   list, because each is a claim and the number behind it. */
.odte-evidence { margin: 0; padding: 4px var(--data-row-x) 12px; max-width: 92ch; }
.odte-evidence dt { margin: 8px 0 1px; color: var(--ink); font-size: 13px; font-weight: 700; }
.odte-evidence dt:first-child { margin-top: 2px; }
.odte-evidence dd { margin: 0; color: var(--muted); font-size: 12.5px; line-height: 1.5; }

/* A long model sentence that is the row's whole content (a thesis, a causal
   chain, a trade note). Capped so it cannot stretch to 1440px. */
.odte-prose { max-width: 78ch; color: var(--body); font-size: 13px; line-height: 1.5; }
.odte-prose + .odte-prose { margin-top: 5px; }
.odte-prose b { color: var(--muted); font: 700 10.5px/1.4 var(--font-mono); letter-spacing: .06em; text-transform: uppercase; }

@media (max-width: 620px) {
  .odte-page .page-head-tools { gap: 8px; }
  .odte-auto { order: 3; }
}


/* ----- when this contract came into being --------------------------------
   The strike walks with the chain — SPY changed 9 times and QQQ 14 on
   2026-09-23 — so "put 769" on its own hides whether it is the standing
   answer or ninety seconds old. The duration is the headline; what it
   replaced rides underneath, so someone who looked away and came back still
   sees that it moved and when, rather than only a highlight they missed.

   Deliberately NOT coloured by how long it has stood. Nothing here has been
   graded: a contract standing an hour is not thereby a better trade than one
   a minute old, and a green "47 min" would say otherwise. */
.odte-standing {
  display: block;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.odte-was {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 11.5px;
  white-space: nowrap;
}

/* A row whose contract changed on THIS poll. Fades from a tint back to
   nothing over four seconds: long enough to catch a glance returning to the
   screen, short enough that it is gone before it becomes furniture. The row's
   own "since / was" text is the permanent record — this is only the cue that
   something moved. */
@keyframes odte-changed {
  from { background: rgba(var(--accent-rgb), 0.16); }
  to { background: transparent; }
}
.data-table tbody tr.is-changed td { animation: odte-changed 4s ease-out 1; }
/* Motion is a cue here, never the only carrier of the information. */
@media (prefers-reduced-motion: reduce) {
  .data-table tbody tr.is-changed td { animation: none; background: rgba(var(--accent-rgb), 0.08); }
}

/* The two clocks in the header: what the DATA says, and when this page last
   got an answer. Both, because a page polling happily against a feed that
   stopped has a fresh first number and a stale second. */
.odte-page .dash-updated { display: inline-flex; align-items: baseline; gap: 4px 10px; flex-wrap: wrap; }
.odte-asof { color: var(--muted); }
.odte-page .dash-updated.is-stale .odte-asof { color: var(--warning); font-weight: 700; }
.odte-checked { color: var(--muted); opacity: .75; font-size: 11px; }


/* ----- the day's contract log --------------------------------------------
   Every contract the module named this session. It is a LOG, not a ledger:
   nothing in it is summed, and no cell is coloured as though a run were a
   result. "reached its target" is a statement about where price went while
   that strike was on screen — the run ended because the chain moved, not
   because a position closed, and the grader's verdict is a different measure
   over a different window.

   So the four outcome words are set in ink and muted, never in --up/--down.
   A green "reached its target" would be a claim this page has not earned. */
/* THE STUCK PAGE (2026-09-23, deployed). Three per-symbol logs kept the
   single log's 30rem cap, so each was a 480px inner scroller with the shared
   pane's overscroll-behavior: contain — the wheel over any of them scrolled
   the pane and never the page, even once the pane had bottomed out, and the
   pane clipped its last row mid-height under the next symbol's header. Two
   ladders over the 70vh cap trapped the same way. On this page no data pane
   caps its height or holds the wheel: tables flow in the document, and their
   column headers cannot pin to the viewport, because there is nothing to
   pin against. Horizontal overflow on a phone still belongs to the pane. */
.odte-page .data-pane { max-height: none; overscroll-behavior-y: auto; }
.odte-page .data-table thead th { position: static; }
/* Side+strike on one line, the OCC under it. Both are inline elsewhere — in
   the board the OCC is the whole cell — so the log sets them to block here
   rather than changing them everywhere and breaking that. */
.odte-log .odte-side,
.odte-log .odte-occ { display: block; }
.odte-log .odte-side { font-size: 12.5px; }
.odte-log .odte-occ { margin-top: 1px; }

.odte-log-target,
.odte-log-stop,
.odte-log-both,
.odte-log-none { display: block; color: var(--ink); font-weight: 650; font-size: 12.5px; }
/* The one exception to "no colour": a level the trade did NOT want. A stop
   reached is a severity, which data.css does allow a value to carry. */
.odte-log-stop { color: var(--warning); }
.odte-log-open,
.odte-log-flat { display: block; color: var(--muted); font-weight: 650; font-size: 12.5px; }

/* Stand-aside rows are part of the day and are not dimmed away — the module
   having no opinion is an answer. They are only made quieter than a contract
   row so the contracts read first. */
.odte-log .odte-side.is-none { color: var(--muted); }

/* The log's columns: when, contract, what price did, then the numbers. The
   outcome column is the only one that wraps, so under auto layout it took
   every pixel of squeeze and read as a one-word strip beside empty number
   columns. It is given the width outright; the short columns stay on one
   line so the row spreads to the right edge instead of stacking. */
.odte-log th:nth-child(3),
.odte-log td:nth-child(3) { width: 38%; }
.odte-log td:nth-child(1),
.odte-log td:nth-child(2),
.odte-log td:nth-child(4),
.odte-log td:nth-child(5),
.odte-log td:nth-child(6) { white-space: nowrap; }


/* ----- what to do now: one card per symbol --------------------------------
   The structured answer the service computes on every snapshot
   (options/action.py): stance, the contract with its prices, the expected
   move, if/then triggers, and the model's latest one-line read beside them.
   Cards rather than a table because each symbol is read on its own — a
   reader holding SPY does not scan across to SPXW's stop — and because the
   if/then list is a list, which a cell cannot hold.

   COLOUR. The stance word carries the direction, the same rule as .odte-side
   above: call in --up, put in --down, stand aside in --muted. Nothing else on
   the card is coloured by outcome; nothing has been graded. */
.odte-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--data-gap, 14px);
  padding: 0 var(--data-row-x) 4px;
}
/* One symbol on the page: one card, full width, its body in two columns so
   the charts under it start higher. Type is unchanged. */
.odte-cards.is-one { grid-template-columns: minmax(0, 1fr); }
.odte-card-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 4px 32px;
  align-items: start;
}
@media (max-width: 900px) { .odte-card-cols { grid-template-columns: minmax(0, 1fr); } }
.odte-card-cols > div { min-width: 0; }
@media (max-width: 1100px) { .odte-cards { grid-template-columns: minmax(0, 1fr); } }

.odte-card {
  padding: 12px 14px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  min-width: 0;
}
.odte-card-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.odte-card-head .odte-sym { font-size: 15px; }
.odte-card-when { margin-left: auto; color: var(--muted); font: 500 11.5px/1.4 var(--font-mono); white-space: nowrap; }

.odte-stance {
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid currentColor;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.odte-stance.is-call { color: var(--up); }
.odte-stance.is-put { color: var(--down); }
.odte-stance.is-stand_aside { color: var(--muted); }

/* ----- the newest model read, above the cards ----------------------------
   Its headline and one-line summary only. Set apart from the cards by a rule
   rather than a panel, because it is prose about them and not another one of
   them, and the foot line is muted so it cannot compete with the reasoning it
   qualifies.                                                                */
.odte-latest {
  margin: 10px 0 2px;
  padding: 10px 0 2px;
  border-top: 1px solid var(--line);
}
.odte-latest-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  color: var(--muted);
  font-size: 12px;
}
.odte-latest-head b { color: var(--ink); font-size: 12.5px; }
.odte-latest-headline {
  margin: 6px 0 0;
  color: var(--ink);
  font-size: 14px;
  font-weight: 700;
  line-height: 1.4;
}
.odte-latest-body { margin: 4px 0 0; color: var(--ink); font-size: 13px; line-height: 1.5; }
.odte-latest-foot { margin: 5px 0 0; color: var(--muted); font-size: 11.5px; line-height: 1.45; }

/* ----- what changed on this read, in plain words -------------------------
   The first line of every card. The tag is a state, not a verdict, so it is
   never coloured green or red: "Still on" is not good news and "Taken off" is
   not bad news, and a colour would say one of those. Only the neutral/accent
   pair is used, and the stand-aside states are muted because nothing changed.
   The tag is set apart by weight and case, which survives a monochrome print
   and a colour-blind reader where a hue would not.                          */
.odte-card-lead {
  margin: 8px 0 2px;
  color: var(--ink);
  font-size: 13px;
  line-height: 1.5;
}
.odte-lead {
  display: inline-block;
  margin-right: 6px;
  color: var(--accent);
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.odte-lead.is-standing,
.odte-lead.is-stood_aside { color: var(--muted); }

.odte-card-headline { margin: 4px 0 6px; color: var(--ink); font-size: 13.5px; font-weight: 600; line-height: 1.45; }

/* Label / value pairs. A definition list, so the labels are labels. */
.odte-kv {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 3px 12px;
  margin: 6px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
}
.odte-kv dt { color: var(--muted); white-space: nowrap; }
.odte-kv dd { margin: 0; color: var(--ink); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.odte-kv dd small { color: var(--muted); font-size: 11.5px; }

/* The contract, set off from the read it came from. */
.odte-contract { margin: 10px 0 0; padding: 8px 10px 10px; border-radius: 8px; background: var(--surface-2); }
.odte-contract .odte-occ { display: block; margin-bottom: 2px; white-space: normal; }

/* ----- the 3-minute risk line, under a card's numbers ----------------------
   It wears .data-note (data.css), so it is the same kind of line as every
   other state line on the site; the card supplies the inset, so the note's
   own row padding and rule are dropped and a hairline above sets it off from
   the numbers. It is coloured by ONE thing — the .is-warn state data.css
   already defines, set only from the stated thresholds in
   scripts/options-trade.js riskLine() — and never by --up or --down: a
   probability is not an outcome. */
.odte-card .odte-risk {
  margin: 10px 0 0;
  padding: 7px 0 0 10px;
  border-bottom: 0;
  border-top: 1px solid var(--line);
  background: transparent;
}
.odte-card .odte-risk strong { white-space: nowrap; }

.odte-card h4 {
  margin: 12px 0 4px;
  color: var(--muted);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.odte-ifthen { margin: 0; padding-left: 18px; color: var(--body); font-size: 12.5px; line-height: 1.42; }
.odte-ifthen li { margin: 0 0 2px; }
.odte-ifthen b { color: var(--ink); font-weight: 600; }
.odte-ifthen li.is-stop b { color: var(--warning); }

.odte-model-read { margin-top: 4px; color: var(--body); font-size: 12.5px; line-height: 1.5; }
.odte-model-read p { margin: 0 0 3px; }
.odte-model-read b { color: var(--ink); font-weight: 600; }


/* ----- the ladder ---------------------------------------------------------
   Every level in price order with spot among them, the distance as a number
   AND as a bar, and what a touch has measured to mean. The spot row is the
   surface ring, not a colour: it is where you are, not a verdict. The bars
   use the diverging pair for above/below — position already carries the
   sign, the hue only repeats it. */
.odte-ladder tbody tr.is-spot td { background: rgba(var(--accent-rgb), 0.10); font-weight: 700; }
.odte-ladder td.odte-role { max-width: 46ch; color: var(--muted); font-size: 12px; line-height: 1.4; white-space: normal; }
.odte-dist {
  display: inline-block;
  width: 84px;
  height: 8px;
  border-radius: 4px;
  background: var(--surface-2);
  vertical-align: middle;
  overflow: hidden;
}
.odte-dist > span { display: block; height: 100%; }
.odte-dist.is-above > span { background: var(--flow-up, #0E7C61); }
.odte-dist.is-below > span { background: var(--flow-down, #B3261E); }
.odte-group { color: var(--muted); font: 600 10.5px/1.4 var(--font-mono); letter-spacing: .06em; text-transform: uppercase; }


/* ----- the model's reads today, as a timeline -----------------------------
   Newest first. Each entry is a line — headline, what changed, one line per
   symbol — not the document: the prose of the newest plan and update is
   collapsed below in .odte-md, as before. */
.odte-timeline { list-style: none; margin: 0; padding: 0 var(--data-row-x); }
.odte-tl { padding: 10px 0; border-top: 1px solid var(--line); }
.odte-tl:first-child { border-top: 0; }
.odte-tl-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; color: var(--muted); font-size: 12px; }
.odte-tl-when { color: var(--ink); font: 600 12px/1.4 var(--font-mono); white-space: nowrap; }
.odte-tl-headline { margin: 4px 0 2px; color: var(--ink); font-size: 13.5px; font-weight: 650; line-height: 1.4; }
.odte-tl-body, .odte-tl-next { margin: 2px 0; max-width: 92ch; color: var(--body); font-size: 12.5px; line-height: 1.5; }
.odte-tl-next { color: var(--muted); }
.odte-tl-syms { margin: 4px 0; padding-left: 18px; max-width: 92ch; color: var(--body); font-size: 12.5px; line-height: 1.5; }
.odte-tl-syms b { color: var(--ink); }


/* ----- the model read's numbers are historical ---------------------------
   A Codex read fires on material change, not on a clock, so its prose can be
   an hour old while the card above it is four minutes old. The header already
   stamps the block, but a price inside a sentence reads as current however
   old the header says the block is — so the sentence gets its own label. */
.odte-aswritten {
  margin: 0 0 6px;
  color: var(--muted);
  font-size: 11.5px;
  font-style: italic;
}

/* ----- which snapshot a section's numbers came from -----------------------
   Not the page's clock and not the poll's: the reading's own. A section of
   prices four minutes old looks identical to one thirty seconds old, and the
   measured block on this page reports the contract decaying about 6% per 15
   minutes — so those are not the same reading and must not look alike. */
.odte-stamp {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  color: var(--muted);
  font-size: 11.5px;
  white-space: nowrap;
}
.odte-stamp-k {
  font: 700 10px/1.4 var(--font-mono);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.odte-stamp-v { color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 650; }
.odte-stamp.is-stale .odte-stamp-v { color: var(--warning); }
.odte-stamp-n { opacity: .8; }

/* Two controls sharing the section's tool slot. */
.odte-tools { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 8px 14px; }


/* ===== position mode ======================================================
   The reader told the page they are in. From then on the symbol's card is an
   EXIT TRACKER: one verdict, the rule that fired and its numbers, the held
   contract's own bid, the plan's levels and the clock. Nothing on it comes
   from the contract the module names now.

   COLOUR IS A STATUS, AND ONLY FOUR OF THEM (dataviz status palette). The
   verdict word always carries the state; the colour repeats it and is never
   the only carrier — a monochrome print still reads HOLD / EXIT.
     HOLD               ink on the surface. Not good news, not coloured.
     TAKE PROFIT        --up: the level the trade WANTED was reached.
     EXIT — STOP        --danger fill: a stop is a severity.
     EXIT — INVALIDATED --danger fill: the module's own rule fired against it.
     EXIT — TIME        --warning: the clock, not the tape.
   The meter's fill carries direction (toward the target --up, toward the stop
   --down); its track is a lighter step of the surface so state reads across
   the whole bar. Type sizes are the card's; nothing is shrunk to fit.      */
.odte-card.odte-track { border-color: var(--line-strong); }
.odte-card.odte-track.is-stop,
.odte-card.odte-track.is-invalid { border-color: var(--danger); box-shadow: 0 0 0 2px var(--danger-line); }
.odte-card.odte-track.is-time { border-color: var(--warning); }
.odte-card.odte-track.is-profit { border-color: var(--up); }

.odte-in {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--ink);
  color: var(--paper);
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The verdict: the one figure this card leads with. ≥ 28px in the page's own
   sans — a hero figure, never a display face. Exactly one per card. */
.odte-verdict {
  margin: 10px 0 8px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--surface-2);
  color: var(--ink);
}
.odte-verdict-word {
  display: block;
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.1;
}
.odte-verdict-why {
  display: block;
  margin-top: 4px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--body);
}
.odte-verdict-when { display: block; margin-top: 3px; color: var(--muted); font: 500 11.5px/1.4 var(--font-mono); }
/* A translucent step of --up for the target state; the literal is the light
   value, the color-mix under it follows the token in both themes. */
.odte-verdict.is-profit { background: rgba(20, 107, 58, 0.12); background: color-mix(in srgb, var(--up) 14%, transparent); }
.odte-verdict.is-profit .odte-verdict-word { color: var(--up); }
.odte-verdict.is-stop,
.odte-verdict.is-invalid { background: var(--danger); color: var(--paper); }
.odte-verdict.is-stop .odte-verdict-word,
.odte-verdict.is-invalid .odte-verdict-word,
.odte-verdict.is-stop .odte-verdict-why,
.odte-verdict.is-invalid .odte-verdict-why,
.odte-verdict.is-stop .odte-verdict-when,
.odte-verdict.is-invalid .odte-verdict-when { color: var(--paper); }
.odte-verdict.is-time { background: rgba(var(--warning-rgb), 0.16); }
.odte-verdict.is-time .odte-verdict-word { color: var(--warning); }

/* The numbers under the verdict. The same definition-list as the stance card
   so the two card modes read as one system; the rule rows are labelled by
   the rule, and the row whose rule fired is marked in weight, not hue. */
.odte-track .odte-kv dt { align-self: start; padding-top: 1px; }
.odte-track .odte-kv dd.is-fired { font-weight: 700; }
.odte-pnl { font-weight: 700; }
.odte-pnl.is-up { color: var(--up); }
.odte-pnl.is-down { color: var(--down); }

/* The plan as a bar: stop at one end, target at the other, the entry spot
   ticked, and where price is now. A meter, because the value is a position
   on a fixed scale (the plan's own range), not a count. */
.odte-meter {
  position: relative;
  height: 10px;
  margin: 8px 0 4px;
  border-radius: 5px;
  background: var(--surface-2);
  overflow: visible;
}
.odte-meter-fill { position: absolute; top: 0; bottom: 0; border-radius: 5px; background: var(--line-strong); }
.odte-meter-fill.is-toward { background: var(--up); }
.odte-meter-fill.is-against { background: var(--down); }
.odte-meter-tick { position: absolute; top: -3px; bottom: -3px; width: 2px; background: var(--ink); opacity: .7; }
.odte-meter-now {
  position: absolute;
  top: -4px;
  width: 18px;
  height: 18px;
  margin-left: -9px;
  border-radius: 50%;
  background: var(--ink);
  border: 2px solid var(--paper);
  box-sizing: border-box;
}
.odte-meter-labels { display: flex; justify-content: space-between; color: var(--muted); font: 500 11px/1.4 var(--font-mono); }
.odte-meter-labels b { color: var(--ink); font-weight: 650; }

/* The bid since entry: a sparkline in the de-emphasis hue with the entry and
   the premium stop as rules. One series, so no legend; the labels beside it
   are the values. */
.odte-spark { display: block; width: 100%; height: 44px; margin: 6px 0 0; overflow: visible; }
.odte-spark-line { fill: none; stroke: var(--muted); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.odte-spark-entry { stroke: var(--line-strong); stroke-width: 1; }
.odte-spark-stop { stroke: var(--down); stroke-width: 1; }
.odte-spark-dot { fill: var(--ink); stroke: var(--paper); stroke-width: 2; }
.odte-spark-label { font-family: var(--font-mono); font-size: 10px; fill: var(--muted); }

/* Card actions: "I'm in" on a stance card, "I'm out" / "Edit" on a tracker. */
.odte-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; }
.odte-actions .btn { padding: 7px 12px; font-size: 13px; }
.odte-actions .btn-primary { font-weight: 700; }

/* The inline form. Inputs at the page's size; labels above them so a phone
   stacks it without a second layout. */
.odte-form { margin: 10px 0 0; padding: 10px 12px 12px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface); }
.odte-form-title { margin: 0 0 6px; color: var(--ink); font-size: 13px; font-weight: 700; }
.odte-form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px 10px; }
.odte-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.odte-field > span { color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; white-space: nowrap; }
.odte-field input,
.odte-field select {
  width: 100%;
  min-width: 0;
  padding: 6px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: var(--paper);
  color: var(--ink);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.odte-field input:focus-visible,
.odte-field select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.odte-field small { color: var(--muted); font-size: 11px; }
.odte-form-note { margin: 8px 0 0; color: var(--muted); font-size: 12px; line-height: 1.45; }
.odte-form-error { margin: 8px 0 0; color: var(--danger); font-size: 12.5px; }

/* The one sentence under every tracker: a rule firing, marked to bid, not a
   fill, the premium stop a rule. Muted, never hidden. */
.odte-rule-note { margin: 10px 0 0; color: var(--muted); font-size: 11.5px; line-height: 1.45; }

/* ----- the exit banner ------------------------------------------------------
   Above the body, persistent while any declared position is in an EXIT
   state. Danger fill for a stop or an invalidation, warning for the clock,
   up for a target; the word is always there, the colour repeats it. Quiet
   once seen: the reminder stays, the emphasis goes. */
.odte-alert {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  margin: 0 0 12px;
  padding: 10px 14px;
  border-radius: 10px;
  border: 1px solid var(--danger);
  background: var(--danger);
  color: var(--paper);
  font-size: 14px;
  line-height: 1.4;
}
.odte-alert.is-time { background: var(--warning); border-color: var(--warning); color: var(--paper); }
.odte-alert.is-profit { background: var(--up); border-color: var(--up); color: var(--paper); }
.odte-alert.is-seen { background: var(--surface); color: var(--ink); }
.odte-alert.is-seen .odte-alert-word { color: var(--danger); }
.odte-alert.is-seen.is-time .odte-alert-word { color: var(--warning); }
.odte-alert.is-seen.is-profit .odte-alert-word { color: var(--up); }
.odte-alert-word { font-size: 16px; font-weight: 800; letter-spacing: .02em; white-space: nowrap; }
.odte-alert-body { flex: 1 1 320px; min-width: 0; }
.odte-alert-when { font: 500 12px/1.4 var(--font-mono); opacity: .9; white-space: nowrap; }
.odte-alert .btn { padding: 5px 10px; font-size: 12.5px; }
.odte-alert:not(.is-seen) .btn-outline { border-color: currentColor; color: inherit; background: transparent; }
.odte-alert:not(.is-seen) .btn-outline:hover { background: var(--paper); color: var(--ink); border-color: var(--paper); }
@keyframes odte-pulse { from { box-shadow: 0 0 0 0 var(--danger-line); } to { box-shadow: 0 0 0 10px transparent; } }
.odte-alert:not(.is-seen) { animation: odte-pulse 1.4s ease-out infinite; }
@media (prefers-reduced-motion: reduce) { .odte-alert:not(.is-seen) { animation: none; } }

/* ----- the reader's own log --------------------------------------------------
   Their fills, kept apart. The result column is on THEIR fills and says so
   in the header; the day total is their fills only, never the grader's. */
.odte-mine td.odte-result { font-weight: 650; }
.odte-mine .is-up { color: var(--up); }
.odte-mine .is-down { color: var(--down); }
.odte-fired { display: block; margin-top: 1px; color: var(--muted); font-size: 11.5px; }

/* The desk is done for the day: the log leads and nothing above it invites
   a trade. The evening review that follows is under its own heading. */
.odte-done { margin: 0 0 10px; }
.odte-review-head { margin: 24px 0 6px; padding-top: 14px; border-top: 1px solid var(--line-strong); color: var(--muted); font-size: 12.5px; }
.odte-review-head h2 { margin: 0 0 2px; color: var(--ink); font-size: 17px; letter-spacing: -0.01em; }

/* The compact model read at the foot of a stance card, collapsed. */
.odte-card-read > summary { padding: 6px 0 2px; color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; cursor: pointer; }
.odte-card-read .odte-md-body { padding: 4px 0 0; font-size: 12.5px; max-width: none; }

/* The symbol's detail block the reader last acted on ("I'm in", or the link
   on the tracker): a ring in the accent, never a colour that means a state. */
.app-section.is-focus { box-shadow: -3px 0 0 0 var(--accent); padding-left: 10px; }
.odte-check { align-self: center; color: var(--accent); font-size: 12.5px; text-decoration: underline; text-underline-offset: 2px; }


/* ===== entries & exits ======================================================
   One section per symbol, jumped to from the card: the reader's own trades
   first (their fills, their size), then — on paper — what the desk's own reads
   would have done on real quotes, in two series that are never added together.

   COLOUR. The money is the one place on this page where a hue means
   something: profit in --up, loss in --down, by sign only, on the figure. The
   rule that fired, the source of a price and a read that stood aside are all
   set in ink or muted — a rule firing is not good or bad news, and a fallback
   is a label, not a warning. */
.odte-ee-block { margin: 0 0 18px; }
.odte-ee-block + .odte-ee-block { padding-top: 12px; border-top: 1px solid var(--line); }
.odte-ee-block > .odte-chart-head h3 { font-size: 14.5px; }
.odte-paper-series { margin: 12px 0 0; }
.odte-paper-series > .odte-chart-head h3 { font-size: 13.5px; }
.odte-paper-count { color: var(--ink); font-weight: 650; }

/* the figure, signed, with a real minus sign; the sign carries the colour */
.odte-money { display: block; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.odte-money.is-up { color: var(--up); }
.odte-money.is-down { color: var(--down); }
.odte-money-sub { display: block; margin-top: 1px; color: var(--muted); font-size: 11.5px; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* where a price came from: a label, never a warning */
.odte-src { display: block; margin-top: 1px; color: var(--muted); font: 500 11px/1.4 var(--font-mono); white-space: nowrap; }
.odte-src.is-fallback { color: var(--accent-2-ink); }

/* the rule that fired, and the tracker's sentence under it, allowed to wrap */
.odte-rule { display: block; color: var(--ink); font-weight: 650; font-size: 12.5px; }
.odte-rule.is-open { color: var(--muted); }
.odte-rule-detail { display: block; margin-top: 2px; max-width: 34ch; color: var(--muted); font-size: 11.5px; line-height: 1.4; white-space: normal; }
/* Side+strike on one line, the OCC under it — the same setting the contract
   log uses, for the same reason: inline, the two run into each other. */
.odte-paper .odte-side, .odte-paper .odte-occ,
.odte-mine .odte-side, .odte-mine .odte-occ { display: block; }
.odte-paper .odte-side, .odte-mine .odte-side { font-size: 12.5px; }
.odte-paper .odte-occ, .odte-mine .odte-occ { margin-top: 1px; }
.odte-paper tbody tr.is-aside td { color: var(--muted); }
.odte-paper tbody tr.is-open td { background: rgba(var(--accent-rgb), 0.05); }
/* A read that named nothing: its watch spans the price and money columns and
   reads as one line — the rule, then what became of it. */
.odte-paper td.odte-watch-cell { white-space: normal; }
.odte-paper td.odte-watch-cell .odte-rule { display: inline; }
.odte-paper td.odte-watch-cell .odte-rule-detail { display: inline; max-width: none; margin: 0 0 0 8px; }
/* The rule column is the row's reason; give it room so it does not wrap into
   a one-word strip beside the price columns. */
.odte-paper td .odte-rule-detail { min-width: 22ch; }
/* The result and underlying cells are short annotated lines; none wraps, so
   a row is exactly as tall as its longest cell and the table's width is the
   sum of what it says. */
.odte-paper td.odte-result,
.odte-paper td.odte-under,
.odte-paper td.odte-under .odte-trade-sub,
.odte-paper td.odte-under .tnum { white-space: nowrap; }
.odte-paper td.odte-under .tnum { display: block; }

/* the date control of the Entries & exits section, and a replayed session */
.odte-paper-pick { gap: 6px; }
.odte-paper-select { padding: 5px 8px; border: 1px solid var(--line-strong); border-radius: 6px; background: var(--paper); color: var(--ink); font: inherit; font-size: 12.5px; }
/* A day the server has no snapshot for: still a choice (its paper rows are
   real), said on the entry and dimmed. */
.odte-paper-select option.is-muted { color: var(--muted); }

/* The banner at the top of a review: the same .data-note line as every
   notice on the page, with the way back on it. */
.odte-review-banner { align-items: center; }
.odte-review-banner .btn { padding: 4px 10px; font-size: 12.5px; }
.odte-paper.is-replayed > .odte-chart-head h3 { color: var(--accent-2-ink); }
.odte-smart { margin: 0 0 10px; }
.odte-smart .metric-strip { margin-bottom: 6px; }

/* the forward check: one line, coloured only by severity, under a verdict or an open row */
.odte-forward { margin: 8px 0 0; padding: 7px 10px; border-left: 3px solid var(--line-strong); border-radius: 0 6px 6px 0; background: var(--surface-2); color: var(--body); font-size: 12.5px; line-height: 1.45; }
.odte-forward b { color: var(--ink); font: 700 10.5px/1.4 var(--font-mono); letter-spacing: .06em; text-transform: uppercase; margin-right: 6px; }
.odte-forward.is-warn { border-left-color: var(--warning); }
.odte-forward.is-mute { border-left-color: var(--line-strong); }
.odte-rule-detail + .odte-forward { margin-top: 6px; font-size: 11.5px; }

/* === components/rotation-chart.css === */
/* ==========================================================================
   Rotation graph (Dashboards → Rotation & industry)

   Two chart hues only, validated with the dataviz palette validator against
   this site's surfaces (--surface #FCFAF5 light, #1F1F1F dark), all pairs:
   blue = momentum ≥ 100, red = momentum < 100. The ratio side is carried by
   the marker (filled vs hollow), never by a third colour.
   ========================================================================== */

:root {
  --rrg-up: #2A78D6;
  --rrg-down: #E34948;
}
:root[data-theme="dark"] {
  --rrg-up: #3987E5;
  --rrg-down: #E66767;
}

/* ---- header: title + controls on one line ------------------------------- */
.rrg-head-row { align-items: center; flex-wrap: wrap; row-gap: 10px; padding: 12px 18px; }
.rrg-body { padding: 14px 18px 16px; }
.intel-graph .rrg-panel { margin-bottom: 18px; }
.rrg-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-left: auto; }
.rrg-controls input[type="date"] {
  font: inherit;
  font-size: 13px;
  padding: 4px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink);
}
.rrg-toggle { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 6px; overflow: hidden; }
.rrg-toggle button {
  font: inherit;
  font-size: 13px;
  padding: 4px 12px;
  border: 0;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.rrg-toggle button + button { border-left: 1px solid var(--line-strong); }
.rrg-toggle button.is-active { background: var(--ink); color: var(--paper); }

/* ---- layout: graph and table side by side when there is room ------------ */
.rrg-layout { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
.rrg-layout.is-wide { grid-template-columns: minmax(0, 1fr) auto; }
.rrg-side.is-measuring { width: max-content; position: absolute; visibility: hidden; }

.rrg-wrap { position: relative; }
.rrg { display: block; max-width: 100%; height: auto; }

/* Quadrant tints: chrome, faint; hue follows the momentum side. */
.rrg-wash { stroke: none; }
.rrg-wash.is-leading { fill: var(--rrg-up); fill-opacity: 0.075; }
.rrg-wash.is-improving { fill: var(--rrg-up); fill-opacity: 0.035; }
.rrg-wash.is-weakening { fill: var(--rrg-down); fill-opacity: 0.035; }
.rrg-wash.is-lagging { fill: var(--rrg-down); fill-opacity: 0.075; }
.rrg-quadrant { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.14em; fill: var(--muted); }
.rrg-frame { fill: none; stroke: var(--line); stroke-width: 1; }
.rrg-cross { stroke: var(--line-strong); stroke-width: 1.25; }
.rrg-tick { font-family: var(--font-mono); font-size: 10.5px; fill: var(--muted); }
.rrg-axis-name { font-size: 11px; fill: var(--muted); }

/* Trails: solid, fading old → new (stroke-opacity per segment). */
.rrg-seg { stroke-width: 1.75; stroke-linecap: round; }
.rrg-trail.is-up .rrg-seg { stroke: var(--rrg-up); }
.rrg-trail.is-down .rrg-seg { stroke: var(--rrg-down); }
.rrg-trail.is-focus .rrg-seg { stroke-width: 2.25; }
.rrg-trail.is-dim { opacity: 0.14; }
.rrg-start { font-family: var(--font-mono); font-size: 10px; fill: var(--muted); paint-order: stroke; stroke: var(--surface); stroke-width: 3px; }

.rrg-head, .rrg-dot { stroke-width: 2; }
.rrg-head.is-up.is-filled, .rrg-dot.is-up.is-filled { fill: var(--rrg-up); stroke: var(--surface); }
.rrg-head.is-down.is-filled, .rrg-dot.is-down.is-filled { fill: var(--rrg-down); stroke: var(--surface); }
.rrg-head.is-up.is-ring, .rrg-dot.is-up.is-ring { fill: var(--surface); stroke: var(--rrg-up); }
.rrg-head.is-down.is-ring, .rrg-dot.is-down.is-ring { fill: var(--surface); stroke: var(--rrg-down); }
.rrg-dot { stroke-width: 1.5; }
.rrg-head.is-dim { opacity: 0.22; }
.rrg-head.is-hover, .rrg-head.is-focus { stroke-width: 2.5; }

.rrg-label {
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 600;
  fill: var(--ink);
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 3px;
  stroke-linejoin: round;
}
.rrg-label.is-dim { opacity: 0.25; }
.rrg-leader { stroke: var(--muted); stroke-width: 1; stroke-opacity: 0.7; }
.rrg-leader.is-dim { stroke-opacity: 0.15; }

.rrg-tip {
  position: absolute;
  z-index: 3;
  min-width: 190px;
  max-width: 250px;
  padding: 9px 11px;
  border-radius: 8px;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
  font-size: 12px;
  pointer-events: none;
}
.rrg-tip-title { display: flex; justify-content: space-between; gap: 10px; font-weight: 600; color: var(--ink); }
.rrg-tip-sym { font-family: var(--font-mono); color: var(--muted); font-weight: 600; }
.rrg-tip-sub { color: var(--muted); font-size: 11px; margin: 1px 0 6px; }
.rrg-tip-row { display: flex; justify-content: space-between; gap: 12px; padding: 1px 0; color: var(--body); }
.rrg-tip-value { font-family: var(--font-mono); color: var(--ink); }

/* ---- ranked table ------------------------------------------------------- */
.rrg-table-scroll { overflow-x: auto; }
.rrg-table { border-collapse: collapse; font-size: 12.5px; width: 100%; }
.rrg-table th, .rrg-table td { padding: 5px 7px; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; }
.rrg-table th { color: var(--muted); font-size: 11px; font-weight: 500; }
.rrg-table .num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.rrg-table tbody tr { cursor: pointer; }
.rrg-table tbody tr:hover td, .rrg-table tbody tr:focus-visible td { background: var(--surface-2); }
.rrg-table tbody tr:focus-visible { outline: none; }
.rrg-table tr.is-focus td { background: var(--surface-2); }
.rrg-table tr.is-focus .rrg-name span { font-weight: 700; }
.rrg-table tr.is-dim td { opacity: 0.55; }
.rrg-name span { color: var(--ink); font-weight: 500; }
.rrg-name small { margin-left: 6px; font-family: var(--font-mono); font-size: 11px; color: var(--muted); }
.rrg-parent { color: var(--body); font-size: 12px; max-width: 124px; overflow: hidden; text-overflow: ellipsis; }
.rrg-qcell { color: var(--ink); }
.rrg-dotmark { display: inline-block; width: 10px; height: 10px; margin-right: 6px; border-radius: 50%; box-sizing: border-box; border: 2px solid transparent; vertical-align: -1px; }
.rrg-dotmark.is-up.is-filled { background: var(--rrg-up); }
.rrg-dotmark.is-down.is-filled { background: var(--rrg-down); }
.rrg-dotmark.is-up.is-ring { border-color: var(--rrg-up); }
.rrg-dotmark.is-down.is-ring { border-color: var(--rrg-down); }
.rrg-arrow { display: inline-block; color: var(--ink); font-size: 15px; line-height: 1; }
.rrg-strip-cell { padding-top: 3px !important; padding-bottom: 3px !important; }
.rrg-strip { display: block; }
.rrg-cell.is-up.is-filled { fill: var(--rrg-up); }
.rrg-cell.is-down.is-filled { fill: var(--rrg-down); }
.rrg-cell.is-up.is-ring { fill: var(--rrg-up); fill-opacity: 0.32; }
.rrg-cell.is-down.is-ring { fill: var(--rrg-down); fill-opacity: 0.32; }
.rrg-cell.is-none { fill: var(--line); }

.rrg-note { font-size: 12px; color: var(--muted); margin: 8px 0 0; }
.rrg-empty { padding: 16px; border: 1px dashed var(--line-strong); border-radius: 8px; color: var(--body); margin: 0; }

@media (max-width: 720px) {
  .rrg-controls { margin-left: 0; }
}

/* === components/sidebar.css === */
/* === Chat sidebar ===
   Conversation list, new chat, search and the theme switch.

   Every colour is a token, which is the only reason dark mode was a palette
   change rather than a rewrite. Nothing here hardcodes a hex. */

/* The collapsed rail is sized by the largest thing IN it, which is a 32px
   icon button — 11px either side gives 54px.

   It was 68px, derived as calc(var(--mark) + 30px) back when the rail held the
   logo. The logo moved to the bar with the rest of the lockup and the
   derivation stayed, so the rail went on reserving width for something that
   was no longer there: 14px of empty column down the whole height of the
   window, on every page, in both themes. --mark had no other reader left and
   is gone.

   32px clears the 24px minimum target size (WCAG 2.5.8) with room, and is the
   same button size the composer uses. */
:root {
  --rail: 54px;
}

.side {
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 40;
  width: 268px;
  display: flex;
  flex-direction: column;
  /* Same ground as the page, in both themes. It was --surface-2, a step
     darker, which put a second background beside the thread and made the rail
     read as a different surface rather than part of the same window.

     That step was doing the separating, though — the two grounds differ by
     only 1.08:1, which is enough to see as a large edge but nothing you could
     call a line. With one shared ground the border has to carry it alone, and
     --line does not: 1.16:1 in dark, 1.17:1 in light, invisible either way.
     Hence --line-strong here, the only place on the site where a border is
     the sole boundary between two regions. */
  background: var(--paper);
  border-right: 1px solid var(--line-strong);
  /* Off-canvas by default; the desktop rule below parks it open. Phones get a
     hamburger, and the thread keeps the full width it needs for result
     tables. */
  transform: translateX(-100%);
  transition: transform .22s ease;
}
.side.is-open { transform: none; }

.side-scrim {
  position: fixed;
  inset: 0;
  z-index: 39;
  background: rgba(var(--ink-rgb), .45);
  border: 0;
}

/* black-translucent means the page paints behind the status bar, so the panel
   head needs the inset or the toggle sits under the clock. */
.side-head {
  padding-top: calc(14px + env(safe-area-inset-top, 0px));
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 12px 10px 14px;
}
.side-toggle, .side-open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.side-toggle:hover, .side-open:hover { background: var(--surface); color: var(--ink); }

.side-actions { padding: 4px 8px 8px; }

.side-item {
  display: flex;
  align-items: center;
  min-width: 0;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition: background .14s, color .14s;
}
.side-item:hover { background: var(--surface); color: var(--ink); }
.side-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.side-item svg { flex: none; }

.side-search { padding: 6px 2px 2px; }
.side-search input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
}
.side-search input:focus {
  outline: 0;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), .12);
}

/* --- conversation list --- */
.side-list {
  flex: 1;
  overflow-y: auto;
  padding: 4px 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-height: 0;
}
.side-group {
  padding: 12px 10px 5px;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}
.side-convo {
  display: flex;
  align-items: center;
  gap: 6px;
  border-radius: var(--radius-md);
}
.side-convo:hover { background: var(--surface); }
.side-convo.is-active { background: var(--surface); }
.side-convo.is-active .side-convo-title { color: var(--ink); font-weight: 600; }

.side-convo-title {
  flex: 1;
  min-width: 0;
  padding: 8px 4px 8px 10px;
  border: 0;
  background: none;
  color: var(--ink-2);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
  /* One line, ellipsised. Unlike the result table — where a clipped headline
     changes the meaning — a clipped thread title is only a label, and the
     full text is one click away. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The rename field replaces the title button in place, so it has to occupy
   the same box — a field that resizes the row makes the list jump while you
   are reading which one you clicked. */
.side-convo-edit {
  flex: 1;
  min-width: 0;
  margin: 2px 4px 2px 6px;
  padding: 6px 8px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
}
.side-convo-edit:focus { outline: none; box-shadow: 0 0 0 3px rgba(var(--accent-rgb), .15); }

.side-convo-del {
  flex: none;
  width: 26px;
  height: 26px;
  margin-right: 5px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  cursor: pointer;
  /* Revealed on hover/focus rather than always shown: a delete button beside
     every row invites the accident it causes. */
  opacity: 0;
  transition: opacity .12s, color .12s;
}
.side-convo:hover .side-convo-del,
.side-convo-del:focus-visible { opacity: 1; }
.side-convo-del:hover { color: var(--danger); }

.side-empty {
  padding: 14px 12px;
  font-size: 13px;
  color: var(--muted);
  line-height: 1.5;
}
/* The matched message text under a search hit — the reason the thread is in
   the results, which the title alone often does not show. */
.side-snippet {
  padding: 0 10px 8px;
  font-size: 12px;
  color: var(--muted);
  line-height: 1.45;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.side-foot {
  border-top: 1px solid var(--line);
  padding: 8px;
  padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
}
/* Only the icon for the theme you would switch TO is shown — a control
   labelled "Dark mode" showing a moon is unambiguous; showing both is not. */
.side-icon-moon, .side-icon-sun { display: none; }
:root[data-theme="dark"] .side-icon-sun { display: inline-flex; }
:root:not([data-theme="dark"]) .side-icon-moon { display: inline-flex; }

/* --- the page beside it --- */
.chat-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  transition: padding-left .22s ease;
}

/* .dash-spacer is gone. It existed to push the right-hand controls away from
   the lockup, which is now the navigation lane's job — .dash-bar .app-nav
   takes the space between them, and .dash-bar-right ends the row on its own
   margin. An empty span whose only purpose was to be wide is not something to
   keep once something real occupies that space. */

/* --- desktop: an explicit toggle, not a hover trap ---
   It used to open on hover. That is fewer clicks and worse: the panel opened
   whenever the pointer crossed the left edge on its way somewhere else, and
   there was no way to keep it open while reading. State you cannot set is not
   state — it is a surprise.

   Now it is a toggle that stays where it is put, remembered in localStorage
   and stamped on <html> before first paint so it never flashes open on load.
   Because the choice is deliberate, the panel PUSHES the thread rather than
   overlaying it: reflow is acceptable in response to a click, and was not
   acceptable in response to the cursor drifting.

   Default open — a first-time visitor should meet the conversation list, not
   a column of unlabelled icons. */
@media (min-width: 900px) {
  .side {
    transform: none;
    width: 268px;
    overflow: hidden;
    transition: width .2s ease;
  }
  .chat-shell { padding-left: 268px; }

  :root[data-side="closed"] .side { width: var(--rail); }
  :root[data-side="closed"] .chat-shell { padding-left: var(--rail); }

  /* Collapsed: icons only, centred on the rail. display:none rather than a
     zero width, because there is no animation to keep smooth here and a
     hidden label must not be reachable by Tab. */
  :root[data-side="closed"] .side-item span,
  :root[data-side="closed"] .side-list,
  :root[data-side="closed"] .side-search { display: none; }
  :root[data-side="closed"] .side-actions { padding-left: 11px; padding-right: 11px; }
  :root[data-side="closed"] .side-item { justify-content: center; padding-left: 0; padding-right: 0; }

  /* The toggle lives in the PANEL, never in the bar — that is the difference
     between reaching for the sidebar and reaching across the header for it.
     The bar button stays for phones only, where the panel is off-canvas and
     therefore not reachable at all. */
  .side-open { display: none; }
  .side-toggle { display: inline-flex; }

  /* Collapsed the head holds only the toggle, so it is simply centred — and
     permanently visible rather than revealed on hover. With no mark to swap
     with, hiding it would leave an empty rail with no way out of it. */
  :root[data-side="closed"] .side-head { justify-content: center; }

  .side-scrim { display: none !important; }
}

@media (prefers-reduced-motion: reduce) {
  .side, .chat-shell { transition: none; }
}
@media (max-width: 899px) {
  /* The bar becomes the only way back to the sidebar, so it needs the room. */
  .dash-bar { gap: 8px; }
}

/* === components/signals.css === */
/* === Consolidated news intelligence ===
   One final-table view: impact, quality, market context and fundamental
   implications remain together. Colour means direction and nothing else.

   The page title row, the horizon selector and the metric strip are now the
   shared primitives (.page-head and .seg in workspace.css, .metric-strip in
   data.css) rather than four bespoke blocks here. What they replaced:
   .news-dash-head / .news-dash-intro (a display title plus a paragraph about
   the product), .mode-bar / .mode-tab (four two-line bordered buttons on their
   own row) and .bias (a bordered banner with its own scale bar). Together they
   held roughly 250px above the first row of data. */

/* --- status cards --- */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
  margin-bottom: 14px;
}
.stat {
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.stat-bull { border-left-color: var(--up); }
.stat-bear { border-left-color: var(--down); }
.stat-rejected { border-left-color: var(--danger); }
.stat-label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.stat-value { font-size: 24px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
.stat-note { font-size: 11.5px; color: var(--muted); }

/* --- the score block ---
   "negative_news | 0.98" is two encodings a reader has to decode. A direction
   word, a signed score out of ten and two labelled percentages say the same
   thing and need no key. */
.score-block { display: flex; flex-direction: column; gap: 1px; min-width: 15ch; }
.score-dir { font-size: 11.5px; font-weight: 750; letter-spacing: .05em; }
.score-impact { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.score-meta { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.score-block.dir-bull .score-dir, .score-block.dir-bull .score-impact { color: var(--up); }
.score-block.dir-bear .score-dir, .score-block.dir-bear .score-impact { color: var(--down); }
.score-block.dir-flat .score-dir, .score-block.dir-flat .score-impact { color: var(--muted); }

/* --- decision --- */
.decision {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  font-size: 11px;
  font-weight: 750;
  letter-spacing: .05em;
}
.decision-bull { background: var(--up); color: #fff; }
.decision-bear { background: var(--down); color: #fff; }
.decision-flat { background: var(--surface-2); color: var(--muted); }
.decision-rejected { background: var(--danger-bg); color: var(--danger); border: 1px solid var(--danger-line); }
.decision-why { display: block; margin-top: 3px; font-size: 11px; color: var(--muted); }

/* Risk-gate outcomes. BUY and SHORT reuse the direction tokens so a green
   chip means the same thing everywhere on the page. BLOCKED is neutral, not
   red: it usually means "not at this risk level", which is a setting the
   reader chose rather than something wrong with the trade. STAND DOWN IS red,
   because there the tape actively disagreed with the news. */
.decision-buy { background: var(--up); color: #fff; }
.decision-short { background: var(--down); color: #fff; }
.decision-blocked {
  background: var(--surface-2); color: var(--ink-2); border: 1px solid var(--line-strong);
}
.decision-standdown {
  background: var(--danger-bg); color: var(--danger); border: 1px solid var(--danger-line);
}

/* --- trade plan --- */
.plan {
  display: flex; flex-wrap: wrap; gap: 6px 10px; margin: 2px 0 6px;
}
.plan-cell {
  display: flex; flex-direction: column; gap: 1px;
  padding: 6px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--paper); min-width: 96px;
}
.plan-label {
  font-size: 10px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
}
.plan-value {
  font-size: 14px; font-weight: 650; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.plan-value.is-stop { color: var(--down); }
.plan-value.is-target { color: var(--up); }
.plan-blockers, .plan-warnings {
  margin: 0 0 6px; padding-left: 18px; font-size: 12.5px; line-height: 1.6;
}
.plan-blockers { color: var(--ink-2); }
.plan-warnings { color: var(--muted); }

/* Account box. Narrow on purpose — it takes a number, and a wide field
   invites the impression that more is being collected than one. */
.acct-input {
  width: 110px; padding: 6px 10px;
  border: 1px solid var(--line-strong); border-radius: var(--radius-md);
  background: var(--paper); color: var(--ink-2); font: inherit; font-size: 13px;
}
.acct-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Never a tick: there is no market data behind this column, and a tick would
   be a promise the table cannot keep. */
.gate-chip {
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: .04em;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--muted);
}

.signals-table td { vertical-align: top; }
.sig-title {
  border: 0; background: none; padding: 0; margin: 0;
  font: inherit; font-size: 14px; color: var(--ink-2);
  text-align: left; cursor: pointer; line-height: 1.45;
}
.sig-title:hover { color: var(--accent); text-decoration: underline; }
.sig-row.is-open .sig-title { color: var(--ink); font-weight: 600; }
.sig-row.is-open td { background: var(--surface-2); }

/* --- drawer --- */
.sig-drawer-row td { padding: 0; background: var(--surface-2); }
.drawer { padding: 14px 18px 18px; display: flex; flex-direction: column; gap: 6px; }
.drawer-heading {
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted); margin-top: 8px;
}
/* The count and its escape hatch on one line: the label says the list is a
   sample, so the way to see the rest belongs immediately beside it. */
.drawer-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.drawer-more {
  padding: 2px 10px; border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm); background: var(--paper);
  color: var(--ink-2); font: inherit; font-size: 11.5px; cursor: pointer;
}
.drawer-more:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.drawer-more:disabled { opacity: .6; cursor: default; }
.drawer-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.drawer-text { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--body); max-width: 90ch; }
.drawer-note { margin: 2px 0 0; font-size: 12px; color: var(--muted); }
.drawer-scores { display: flex; flex-wrap: wrap; gap: 6px; }
.drawer-score {
  display: flex; align-items: baseline; gap: 6px;
  padding: 4px 10px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--paper);
}
.drawer-score.is-selected { border-color: var(--accent); }
.drawer-score-label { font-size: 11px; color: var(--muted); }
.drawer-score-value { font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
.drawer-gates { display: flex; flex-direction: column; gap: 3px; }
.gate { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; }
.gate-name { min-width: 15ch; font-weight: 600; }
.gate-state { min-width: 12ch; font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.gate-pass .gate-state { color: var(--up); }
.gate-fail .gate-state { color: var(--muted); }
.gate-unavailable .gate-state, .gate-wait .gate-state { color: var(--accent-2-ink); }
.gate-why { color: var(--muted); }
.drawer-meta {
  display: grid; grid-template-columns: max-content 1fr; gap: 2px 14px;
  margin: 0; font-size: 12px; font-family: var(--font-mono);
}
.drawer-meta dt { color: var(--muted); }
.drawer-meta dd { margin: 0; color: var(--ink-2); overflow-wrap: anywhere; }
.drawer-link { font-size: 13px; color: var(--accent); margin-top: 8px; }

.dir-bull { color: var(--up); }
.dir-bear { color: var(--down); }
.dir-flat { color: var(--muted); }

/* --- price confirmation ---
   Four states, not two. "No reaction yet" is a live opportunity, "contradicted"
   is the market disagreeing, and collapsing them into pass/fail would hide the
   distinction that matters most. */
.gate-ok   { background: var(--surface-2); color: var(--up); }
.gate-bad  { background: var(--danger-bg); color: var(--danger); }
.gate-flat { background: var(--surface-2); color: var(--muted); }
.gate-na   { background: var(--surface-2); color: var(--muted); }
.price-ret {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}
.price-now {
  display: block;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Relative volume beside the price. Highlighted past 1.5x because that is
   where "unusual" starts meaning something — below it the number is context,
   above it it is a reason to look. */
.price-vol {
  display: block;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.price-vol.is-hot { color: var(--accent-2-ink); font-weight: 650; }

/* --- the compact form on the report page --- */
.mini-sig { display: block; font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.mini-meta { display: block; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.mini-meta.is-hot { color: var(--accent-2-ink); font-weight: 650; }
.mini-price { display: block; font-variant-numeric: tabular-nums; font-size: 13.5px; }
.dash-table .decision + .gate-chip { display: block; margin-top: 3px; }

/* ===== market bias =====
   The one line that answers "what is the tape saying". Given a fixed -10..+10
   track rather than a bar scaled to today's extremes: a chart that rescales
   makes every session look decisive, and the whole point is to show when it
   is not. */
.bias {
  margin: 0 0 12px;
  padding: 12px 16px 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.bias.dir-bull { border-left-color: var(--up); }
.bias.dir-bear { border-left-color: var(--down); }
.bias-head { display: flex; align-items: baseline; gap: 8px; }
.bias-label { font-size: 13px; font-weight: 750; letter-spacing: .06em; }
.bias.dir-bull .bias-label, .bias.dir-bull .bias-net { color: var(--up); }
.bias.dir-bear .bias-label, .bias.dir-bear .bias-net { color: var(--down); }
.bias.dir-flat .bias-label, .bias.dir-flat .bias-net { color: var(--muted); }
.bias-net { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
.bias-scale { font-size: 12px; color: var(--muted); }
.bias-track {
  position: relative;
  height: 6px;
  margin: 8px 0 8px;
  border-radius: 3px;
  background: linear-gradient(90deg,
    var(--down) 0%, var(--line-strong) 50%, var(--up) 100%);
  opacity: .55;
}
.bias-mid {
  position: absolute; left: 50%; top: -3px; bottom: -3px;
  width: 1px; background: var(--ink); opacity: .35;
}
.bias-pin {
  position: absolute; top: -4px; width: 3px; height: 14px;
  border-radius: 2px; background: var(--ink); transform: translateX(-1px);
}
.bias-foot { display: flex; flex-wrap: wrap; gap: 12px; font-size: 12px; }
.bias-breadth { font-variant-numeric: tabular-nums; font-weight: 600; }
.bias-agree { color: var(--muted); }

/* ===== symbol rows ===== */
.sym { font-size: 15px; }
.flow-counts { display: block; font-size: 13px; font-weight: 650; font-variant-numeric: tabular-nums; }
/* The reason on the collapsed row. A headline says what happened; the reason
   says why it moves the stock, and needing a click for that is the digging
   this screen exists to remove. */
.lead-reason {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* A <p> now, so the UA's 1em bottom margin has to go — it was adding a
     blank 12.5px to the bottom of every row in the table. */
  margin: 3px 0 0;
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.45;
}

/* ===== one article inside an expanded symbol ===== */
.article-card {
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.article-card:first-of-type { border-top: 0; }
.article-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.article-score { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; min-width: 3ch; }
.article-meta { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.article-block {
  font-size: 10.5px; font-weight: 650; letter-spacing: .04em;
  padding: 1px 6px; border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--muted);
}
.article-title { display: block; margin: 3px 0; font-size: 14px; color: var(--ink-2); }
a.article-title:hover { color: var(--accent); }
.article-card .drawer-scores { margin-top: 6px; }
.article-subject {
  padding: 1px 6px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 9.5px;
  font-weight: 750;
  letter-spacing: .05em;
}
.article-subject.is-primary { border-color: var(--up); color: var(--up); }
.drawer-rationale {
  max-width: 105ch;
  margin: 4px 0;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.55;
}
.news-intelligence-grid,
.news-fundamental-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  margin-top: 8px;
  background: var(--line);
}
.news-info-cell { min-width: 0; padding: 8px 10px; background: var(--paper); }
.news-info-cell span,
.news-info-cell strong { display: block; }
.news-info-cell span {
  color: var(--muted);
  font-size: 9.5px;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.news-info-cell strong {
  margin-top: 4px;
  color: var(--ink-2);
  font: 650 11.5px var(--font-mono);
  overflow-wrap: anywhere;
}
.article-audit { margin-top: 9px; }
.final-record { margin-top: 9px; border: 1px solid var(--line); border-radius: var(--radius-md); overflow: hidden; }
.final-record[hidden] { display: none; }
.final-record-group { padding: 12px 14px; border-top: 1px solid var(--line); background: var(--surface); }
.final-record-group:first-child { border-top: 0; }
.final-record-group h4 { margin: 0 0 8px; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; }
.final-record-grid {
  display: grid;
  grid-template-columns: minmax(145px, .35fr) minmax(0, 1fr);
  gap: 5px 14px;
  margin: 0;
  font-size: 11.5px;
}
.final-record-grid dt { color: var(--muted); }
.final-record-grid dd { margin: 0; color: var(--ink-2); overflow-wrap: anywhere; }
.final-record-grid a { color: var(--accent); }
.final-record-body summary { color: var(--accent); cursor: pointer; }
.final-record-body p { max-height: 360px; overflow: auto; white-space: pre-wrap; color: var(--body); line-height: 1.5; }
.final-record-empty { margin: 0; color: var(--muted); font-size: 11.5px; }

@media (max-width: 900px) {
  .news-intelligence-grid,
  .news-fundamental-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 560px) {
  .news-intelligence-grid,
  .news-fundamental-grid { grid-template-columns: 1fr; }
  .final-record-grid { grid-template-columns: 1fr; }
  .final-record-grid dd { padding-bottom: 5px; }
}

/* ===== filter bar ===== */
.filter-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0 0 8px;
}
.filter-search {
  position: relative;
  display: flex;
  align-items: center;
  gap: 7px;
  flex: 1;
  min-width: 240px;
  max-width: 460px;
  padding: 0 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--muted);
}
.filter-search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(var(--accent-rgb), .12); }
.filter-search input {
  flex: 1;
  min-width: 0;
  padding: 8px 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
}
/* Safari draws its own clear affordance on type=search and it collides with
   ours; ours is keyboard-reachable, theirs is not. */
.filter-search input::-webkit-search-decoration,
.filter-search input::-webkit-search-cancel-button { -webkit-appearance: none; }
.filter-clear {
  border: 0; background: none; padding: 2px 4px; cursor: pointer;
  color: var(--muted); font-size: 13px; line-height: 1;
}
.filter-clear:hover { color: var(--ink); }

.filter-chips { display: flex; gap: 4px; }
.filter-chip {
  padding: 6px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--paper);
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: border-color .14s, background .14s;
}
.filter-chip:hover { border-color: var(--accent); }
.filter-chip.is-active { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.filter-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Always visible, never only when filtering — a reader has to be able to see
   that rows are hidden without first noticing the filter. */
.filter-count { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Lookback selector. Sits with the filters rather than the mode tabs because
   it changes what is VISIBLE, not what the scores mean. */
.filter-window { display: flex; align-items: center; gap: 6px; }
.filter-window > span { font-size: 12px; color: var(--muted); }
.filter-window select {
  padding: 6px 10px; border: 1px solid var(--line-strong);
  border-radius: var(--radius-md); background: var(--paper);
  color: var(--ink-2); font: inherit; font-size: 13px; cursor: pointer;
}
.filter-window select:hover { border-color: var(--accent); }
.filter-window select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Sector chips on the social panel. These are SECTORS, not tickers — the
   rewritten schema gives "Defense Contractors", not LMT — so they are styled
   as labels rather than as the symbol chips on /signals, which a reader could
   otherwise mistake for something tradable. */
.sector-list { display: flex; flex-wrap: wrap; gap: 3px 5px; }
.sector {
  font-size: 11.5px;
  padding: 1px 6px 2px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--body);
}
.sector.is-bull { box-shadow: inset 2px 0 0 var(--up); }
.sector.is-bear { box-shadow: inset 2px 0 0 var(--down); }

/* ===== market intelligence: fitting the table to the screen =====
   One page, two stacked sections, and the point of the exercise is how much of
   the first table is on screen before anything is scrolled. Three things were
   spending that budget: the pane's own height cap, the gaps between the bands
   above it, and a lead story cell that ran to four lines. */
.market-app {
  /* 70vh is the right cap on a page whose table starts at the top. Here the
     chrome above the first row is about 26rem, so a 70vh pane hangs off the
     bottom of the screen and the reader scrolls the page to reach rows the
     pane would have scrolled for them. Floors at 360px so a short laptop still
     gets a usable table rather than a slit. */
  --pane-max: max(360px, calc(100vh - 21rem));
  --data-stack: 6px;
  --data-gap: 12px;
  --data-row-y: 7px;
}
@supports (height: 100dvh) {
  .market-app { --pane-max: max(360px, calc(100dvh - 21rem)); }
}
.market-app .panel { margin-top: 6px; margin-bottom: 10px; }
.market-app .filter-bar { gap: 8px 10px; margin-bottom: 6px; }
.market-app .filter-search { min-width: 200px; max-width: 320px; }

/* The lead story is the widest cell and the tallest: a headline, the expected
   move, and the reason it moves the stock. All three are information, so none
   of them is removed — the reason is capped at the two lines it almost always
   takes, and the rest of it is in the row's drawer along with every other
   story behind the symbol. */
.market-app .lead-reason {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
/* The expected move leads the reason line rather than owning one of its own. */
.lead-move { margin-right: 6px; color: var(--accent-2-ink); font-weight: 650; font-variant-numeric: tabular-nums; }

@media (max-width: 620px) {
  /* Same trade as the metric strip: eight controls stacked is four rows of
     chrome on a 390px screen, and the table gets what is left. One swipeable
     row keeps every control reachable and gives the rows back. */
  .filter-bar {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .filter-bar::-webkit-scrollbar { display: none; }
  .filter-bar > * { flex: 0 0 auto; }
  .filter-search { flex: 0 0 200px; min-width: 200px; }
  .filter-chips { flex: 0 0 auto; }
}

/* === components/smart-intraday.css === */
/* Smart Intraday — the six-tab workbench as one screen.
 *
 * Every colour comes from tokens.css, so both themes are covered by the token
 * block rather than by a second set of rules here. Direction (--up / --down)
 * is reserved for direction: agreement wears --accent, because five agreeing
 * rules under a SHORT tier must not read as bullish. */

.si-body { margin: 0; --si-gap: 6px; --si-pad: 9px; }

/* Soft fills and hairlines, as plain rgba.
 *
 * These were color-mix(), which is unsupported below Chrome 111 / Safari 16.2
 * / Firefox 113 — and an unsupported declaration is DROPPED, not approximated.
 * On those engines every agreement mark, gate chip and pill lost its fill and
 * its border, which is precisely the state this page encodes in colour. rgba
 * is universally supported and, derived from the same token hexes, identical
 * where color-mix worked.
 *
 * Restated per theme because rgba cannot follow a token the way color-mix
 * does; dark is driven by [data-theme] alone, matching tokens.css. */
.si-body, .si-fold2 {
  --si-up-bg: rgba(20, 107, 58, .13);      /* --up  #146B3A */
  --si-up-bd: rgba(20, 107, 58, .38);
  --si-dn-bg: rgba(179, 38, 30, .13);      /* --down #B3261E */
  --si-dn-bd: rgba(179, 38, 30, .34);
  --si-ac-bg: rgba(31, 58, 95, .10);       /* --accent #1F3A5F */
  --si-ac-bd: rgba(31, 58, 95, .34);
  --si-pin:   rgba(31, 58, 95, .09);
}
:root[data-theme="dark"] .si-body,
:root[data-theme="dark"] .si-fold2 {
  --si-up-bg: rgba(52, 196, 107, .16);     /* --up  #34C46B */
  --si-up-bd: rgba(52, 196, 107, .40);
  --si-dn-bg: rgba(255, 138, 138, .16);    /* --down #FF8A8A */
  --si-dn-bd: rgba(255, 138, 138, .36);
  --si-ac-bg: rgba(77, 170, 252, .14);     /* --accent #4DAAFC */
  --si-ac-bd: rgba(77, 170, 252, .36);
  --si-pin:   rgba(77, 170, 252, .12);
}


/* Fold 1 fills at least one viewport and grows instead of clipping. */
/* Seven children, seven tracks, each assigned EXPLICITLY.
 *
 * Implicit placement was wrong twice over: there were only five track sizes
 * for six children, so every section sat one track off — the verdict rail
 * took the spine's 1.06fr and the status bar was pushed past 100dvh, over the
 * content below it. And because a hidden error banner is display:none it
 * creates no track at all, the mapping SHIFTED AGAIN the moment an error
 * appeared or cleared. Naming the row on each child makes the layout the same
 * in both states. */
.si-screen {
  /* Content-sized rows, not leftover-sized ones.
   *
   * This was `height:100dvh` with `minmax(_, fr)` rows, which sizes every
   * section from whatever space the others left. Any growth anywhere — a
   * wrapped label, an extra gate chip, a larger browser font — squeezed a
   * different section, and overflow then hid the result, so the page kept
   * appearing empty in a new place each time it was fixed.
   *
   * Now every row is `auto`: sections are as tall as their content, nothing
   * can be squeezed by a sibling, and `min-height` still fills the viewport on
   * a normal screen. It scrolls when it genuinely does not fit, which is the
   * honest outcome — showing the data matters more than never scrolling. */
  /* Fold 1 ends at the bottom of the viewport, not below it. The shared bar
     publishes its measured height as --dash-bar-h; subtracting it is what
     keeps that true at every width, and is why no bar height is written
     here as a number. */
  min-height: calc(100vh - var(--dash-bar-h, 0px));
  min-height: calc(100dvh - var(--dash-bar-h, 0px));
  display: grid;
  grid-template-rows: auto auto auto auto auto;  /* head, error, call, main, status */
  /* Extra space collects at the END, it does not inflate every row.
   *
   * A grid whose content is shorter than its min-height distributes the
   * leftover across its auto tracks by default — so on a tall screen every
   * section grew a band of empty space, which is what kept appearing above the
   * spine and around the verdict rail. `start` packs the rows and leaves the
   * remainder below, where it belongs. */
  align-content: start;
  /* Items hug their track and their track hugs their content. Between them
     these forbid the two ways a grid can manufacture vertical space: a track
     taller than its item, and an item centred inside its track. */
  align-items: start;
}

/* No margin may separate the sections — the gap property is the only thing
   allowed to space them, so a UA or inherited margin cannot open a band that
   looks like a layout bug. */
.si-screen > * {
  margin: 0;
  gap: var(--si-gap);
  padding: var(--si-gap) var(--si-pad);
  font-variant-numeric: tabular-nums;
}
.si-head { grid-row: 1; }
.si-screen > .dash-error {
  grid-row: 2;
  /* A message, not a panel. An unhandled exception used to print its whole
     stack-shaped text across the top and push the dashboard down the page. */
  max-height: 64px;
  overflow-y: auto;
  margin: 0;
  padding: 7px 11px;
  font-size: 12.5px;
  line-height: 1.4;
}
.si-verdict { grid-row: 3; }
.si-spine-card { grid-row: 4; }
.si-status { grid-row: 5; }

/* ---------- head ---------- */
.si-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.si-title { margin: 0; font: 620 clamp(12px, 1.2vw, 15px)/1 var(--font-sans); letter-spacing: -.02em; color: var(--muted); white-space: nowrap; }
/* No sizing of its own: nav.css owns the shared bar, so this page renders it
   at exactly the height every other page does. An override here is how the
   earlier lookalike drifted. */
.si-cap { margin: 0 0 0 auto; font: 500 12px/1.4 var(--font-mono); font-variant-numeric: tabular-nums; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.si-symbol { font: 700 15px/1 var(--font-mono); color: var(--ink); letter-spacing: -.01em; }
.si-date { font: 600 12px var(--font-mono); padding: 4px 6px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); }
.si-ghost { appearance: none; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--body); font: 620 12px var(--font-mono); padding: 4px 9px; cursor: pointer; }
.si-ghost:hover { border-color: var(--line-strong); }
.si-picker input { width: clamp(150px, 18vw, 240px); min-height: 30px; }

/* The ☰ menu that used to hold this page's entire navigation is gone: the
   shared bar carries the same lane here as on every other page, so .si-menu,
   .si-menu-panel and .si-menu-foot had no markup left to style. */
.si-replay { display: inline-flex; align-items: center; gap: 5px; font: 600 12px/1.4 var(--font-mono); color: var(--muted); }
.si-replay input { font: inherit; padding: 3px 6px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); }

.si-tf { display: flex; gap: 2px; padding: 2px; border-radius: 7px; background: var(--surface-2); border: 1px solid var(--line); }
.si-tf button {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 2px 8px; border-radius: 5px;
  font: 620 12px/1.6 var(--font-mono); color: var(--muted);
}
.si-tf button.is-active { background: var(--surface); color: var(--ink); }
.si-tf button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

#si-symbol-status { font: 700 14px/1 var(--font-mono); color: var(--ink); letter-spacing: -.01em; }

/* ---------- card ---------- */
/* A section of the screen, not a card on it. The border, radius and fill are
   gone; the rule under the heading is what says where the section starts. */
.si-card {
  display: flex; flex-direction: column; justify-content: flex-start; align-items: stretch;
  min-height: 0; min-width: 0; overflow: hidden;
}
.si-card > h2 {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin: 0; padding: 2px 0 5px; border-bottom: 1px solid var(--line);
  font: 650 14px/1.4 var(--font-sans); color: var(--ink); flex: none;
}
.si-card > h2 span { font: 600 12px/1.5 var(--font-mono); font-variant-numeric: tabular-nums; color: var(--muted); letter-spacing: .03em; }
.si-card-in { padding: var(--si-gap) 0 0; min-height: 0; display: flex; flex-direction: column; }
/* Fill the grid track rather than the content, so the spine can measure the
   height it actually has and size its lanes to it. */
/* The spine is as tall as its lanes; only the time axis scrolls sideways. */
.si-spine-card > .si-card-in { flex: none; gap: var(--si-gap); }
.si-subhead + .si-tkt { margin-top: 2px; }
.si-spine-card .si-tiles { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); overflow: hidden; }
.si-scroll-y { overflow-y: auto; }
.si-scroll-x { overflow-x: auto; }

/* ---------- verdict rail ---------- */
.si-verdict {
  display: grid;
  grid-template-columns: minmax(150px, auto) minmax(120px, auto) minmax(0, 1fr);
  align-content: stretch;
  align-items: stretch;
  gap: 1px;
  background: var(--line);
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); overflow: hidden;
}
@media (max-width: 720px) { .si-verdict { grid-template-columns: 1fr; } }
/* --paper, not --surface: with the box gone these cells sit ON the page, and
   the 1px grid gap over the --line ground is what draws the dividers. */
.si-verdict > div { background: var(--paper); padding: 7px var(--si-pad); display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.si-gatecell { flex: 1 1 auto; min-height: 0; }
/* One line, always. This label grew to three wrapped lines once it started
   naming the last signal's exit, and the rail is sized by its tallest cell. */
#si-ticket-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The gates cell is the other thing that can wrap without limit. Cap it and
   let it scroll inside itself rather than push the spine off the screen. */
.si-gates { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.si-g {
  max-width: 100%;
  white-space: nowrap;
  line-height: 1.5;
}
/* 8.5px is not a small label, it is an unread one. The strip below is dense
   by design; density is bought with spacing and hairlines here, never by
   pushing a word under the size it can be read at. */
.si-lbl { margin: 0; font: 640 11px/1.25 var(--font-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.si-call { border-left: 4px solid var(--line-strong); }
.si-streak { font: 600 12px/1.3 var(--font-mono); font-variant-numeric: tabular-nums; font-style: normal; color: var(--muted); }
.si-call.is-buy { border-left-color: var(--up); }
.si-call.is-short { border-left-color: var(--down); }
.si-call-row { margin: 0; display: flex; align-items: baseline; gap: 8px; }
.si-call-row strong { font: 680 clamp(18px, 1.7vw, 24px)/1 var(--font-sans); letter-spacing: -.03em; color: var(--muted); }
.si-call.is-buy .si-call-row strong { color: var(--up); }
.si-call.is-short .si-call-row strong { color: var(--down); }
.si-call-row em { font: 600 12px/1.2 var(--font-mono); font-variant-numeric: tabular-nums; font-style: normal; color: var(--muted); }

.si-conf-row { margin: 0; display: flex; align-items: baseline; gap: 6px; }
.si-conf-row strong { font: 680 clamp(17px, 1.9vw, 24px)/1 var(--font-sans); letter-spacing: -.03em; color: var(--ink); }
.si-band { font: 640 11.5px/1.2 var(--font-mono); letter-spacing: .07em; color: var(--warning, var(--accent-2-ink)); }
.si-track { height: 4px; border-radius: 2px; background: var(--surface-2); overflow: hidden; }
.si-track i { display: block; height: 100%; width: 0; background: var(--ink-2); }

.si-tkt { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 76px), 1fr)); gap: 2px 8px; }
.si-tkt > div { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.si-tkt b { font: 640 clamp(13px, 1.05vw, 15px)/1.2 var(--font-mono); font-variant-numeric: tabular-nums; color: var(--ink); }
.si-tkt b.is-down { color: var(--down); }
.si-tkt-wide { grid-template-columns: repeat(auto-fit, minmax(min(100%, 108px), 1fr)); gap: 7px 10px; }

/* ---------- excursion rail ---------- */
/* Sits between the episode stats and the exit ladder. Full width, because the
   distance between worst and best IS the reading — a narrow rail would leave
   the two ends indistinguishable on the sessions where they are close. */
.si-exc { display: flex; flex-direction: column; gap: 3px; }
.si-exc-row {
  display: flex; justify-content: space-between; gap: 10px;
  font: 620 11px/1.3 var(--font-mono); letter-spacing: .06em; color: var(--muted);
}
.si-exc-track {
  position: relative; display: flex; height: 10px;
  border-radius: 3px; overflow: hidden; background: var(--surface-2);
}
.si-exc-track i { display: block; height: 100%; }
/* Muted, not solid: red and green already carry direction on this page, and a
   full-strength rail would read as a signal rather than as a measurement. */
.si-exc-track i.is-loss { background: var(--down); opacity: .28; }
.si-exc-track i.is-win { background: var(--up); opacity: .28; }
.si-exc-entry {
  position: absolute; z-index: 1; top: 0; bottom: 0; left: 0;
  display: block; width: 0; margin-left: -1px;
  border-left: 2px dashed var(--body); opacity: .72;
}
.si-exc-mark {
  position: absolute; z-index: 2; top: 0; bottom: 0; left: 0;
  display: block; width: 2px; margin-left: -1px;
  background: var(--ink); border-radius: 1px; text-decoration: none;
}
.si-exc-foot {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: baseline;
}
.si-exc-foot > span:last-child { text-align: right; }
.si-exc-entry-label { color: var(--body); letter-spacing: .02em; }

.si-gates { display: flex; flex-wrap: wrap; gap: 4px; align-content: center; }
.si-g {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 7px; border-radius: 100px;
  border: 1px solid var(--line-strong); background: var(--surface-2);
  font: 620 11.5px/1.6 var(--font-mono); color: var(--muted);
}
/* Not red.
 *
 * On this page red already MEANS something: it is the short/down direction, in
 * the tier band, the composite bars, the candles and the conflict marks. A
 * blocked gate is not a loss and not a direction — it is a condition that has
 * not been met — so borrowing the direction colour for it made two unrelated
 * facts look like the same fact. The glyph carries the state instead, which is
 * also why it survives colour-blindness and greyscale. */
.si-g.is-no { border-color: var(--line-strong); background: var(--surface-2); color: var(--body); }
.si-g.is-no::first-letter { color: var(--muted); }
.si-g.is-ok { border-color: var(--si-up-bd); background: var(--si-up-bg); color: var(--up); }
.si-execution-note {
  margin: 1px 0 0;
  font: 500 11px/1.35 var(--font-sans);
  color: var(--muted);
}

/* ---------- the spine ---------- */
.si-spine-scroll { overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; flex: none; }
.si-spine { display: grid; min-width: min-content; position: relative; outline: none; }
.si-spine:focus { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.si-spine:focus:not(:focus-visible) { outline: none; }
.si-spine:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

.si-ll {
  position: sticky; left: 0; z-index: 5; background: var(--surface);
  display: flex; align-items: center; padding-right: 4px;
  font: 600 11.5px/1.2 var(--font-mono); color: var(--muted); letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; border-right: 1px solid var(--line);
}
.si-ll b { color: var(--ink); font-weight: 640; }
.si-ll.is-sub { padding-left: 8px; font-size: 11px; }

.si-zero { grid-column: 2 / -1; align-self: center; height: 1px; background: var(--line-strong); z-index: 0; }
.si-psvg { grid-column: 2 / -1; z-index: 1; }
.si-psvg svg { display: block; }

.si-c { position: relative; z-index: 1; display: flex; }
.si-sc { align-items: center; justify-content: center; }
.si-sc i { position: absolute; left: 2px; right: 2px; border-radius: 2px 2px 0 0; background: var(--up); opacity: .72; }
.si-sc i.is-down { border-radius: 0 0 2px 2px; background: var(--down); }

.si-tc { align-items: center; justify-content: center; }
.si-tc { background: var(--surface-2); }
.si-tc.is-b { background: var(--si-up-bg); }
.si-tc.is-s { background: var(--si-dn-bg); }
.si-tc b {
  display: block; width: 100%; text-align: center;
  font: 700 11px/1 var(--font-mono); color: var(--muted);
}
.si-tc.is-b b { color: var(--up); }
.si-tc.is-s b { color: var(--down); }

/* Agreement is not direction, so it never wears the up/down hues.
   Filled = agrees, slashed outline = conflicts, bare outline = not populated —
   three states that stay apart without relying on colour. */
/* agreement: a small quiet dot — the expected state, so it stays background */
.si-dc.is-agree u { background: var(--accent); opacity: .42; }
/* conflict: bigger, square, full strength — the thing worth looking at */
.si-dc.is-conflict u { width: 8px; height: 8px; border-radius: 1.5px; background: var(--down); opacity: 1; }


/* Agreement: one bottom-anchored bar per bar, height = how many rules agree. */
.si-ag { align-items: flex-end; justify-content: center; }
.si-ag i { width: calc(100% - 4px); border-radius: 2px 2px 0 0; background: var(--accent); opacity: .5; }
.si-ag.is-conflict i { background: var(--down); opacity: .85; }
.si-ag.is-idle i { background: transparent; }

.si-vc { align-items: flex-end; justify-content: center; }
.si-vc i { width: calc(100% - 4px); background: var(--line-strong); border-radius: 2px 2px 0 0; opacity: .8; }
.si-vc.is-hot i { background: var(--accent-2); }
.si-ax { font: 600 11px/1.2 var(--font-mono); color: var(--muted); align-items: center; justify-content: center; white-space: nowrap; }

.si-hit {
  grid-row: 1 / -1; z-index: 4; appearance: none; -webkit-appearance: none;
  border: 0; padding: 0; margin: 0; background: transparent; cursor: crosshair;
  border-left: 1px solid transparent; border-right: 1px solid transparent;
}
.si-hit:hover { background: var(--si-pin); }
.si-hit.is-pinned { background: var(--si-pin); border-left-color: var(--accent); border-right-color: var(--accent); }
.si-hit.is-flip {
  border-left-color: var(--si-ac-bd);
  box-shadow: inset 3px 0 0 var(--si-ac-bd);
  background-image: linear-gradient(to right, var(--si-ac-bg), transparent 70%);
}
.si-hit.is-decision { box-shadow: inset -3px 0 0 var(--accent); }
.si-hit.is-flip.is-decision { box-shadow: inset 3px 0 0 var(--si-ac-bd), inset -3px 0 0 var(--accent); }
.si-hit:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.si-read { display: flex; flex-wrap: wrap; align-items: center; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); margin-top: var(--si-gap); flex: none; }
.si-read > div { padding: 3px 9px; display: flex; flex-direction: column; gap: 1px; min-width: 78px; }
.si-read > div + div { border-left: 1px solid var(--line); }
.si-read span { font: 620 11px/1.3 var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.si-read strong { font: 640 12.5px/1.2 var(--font-mono); color: var(--ink); }
.si-read strong.is-up { color: var(--up); }
.si-read strong.is-down { color: var(--down); }

/* One line. It is a key, not a paragraph. */
.si-rules-cell { padding: 3px 9px; display: flex; flex-direction: column; gap: 2px; border-left: 1px solid var(--line); }
.si-rules { display: flex; gap: 3px; }
.si-rules u {
  text-decoration: none; display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; padding: 1px 3px; border-radius: 3px;
  font: 700 11px/1.5 var(--font-mono); text-transform: uppercase;
  background: var(--surface); color: var(--muted); border: 1px solid var(--line);
}
.si-rules u.is-agree { background: var(--si-ac-bg); color: var(--accent); border-color: var(--si-ac-bd); }
.si-rules u.is-conflict { background: var(--si-dn-bg); color: var(--down); border-color: var(--si-dn-bd); }

.si-legend { display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 12px; margin-top: 4px; font: 500 11.5px/1.4 var(--font-sans); color: var(--muted); flex: none; white-space: nowrap; }
.si-legend b { display: inline-flex; align-items: center; gap: 4px; font-weight: 500; }
.si-sw { width: 6px; height: 10px; border-radius: 2px 2px 0 0; background: var(--accent); opacity: .5; }
.si-sw.is-conflict { width: 6px; height: 10px; border-radius: 2px 2px 0 0; background: var(--down); opacity: .85; }
.si-sw.is-band { width: 14px; height: 9px; border-radius: 2px; background: var(--accent-2); opacity: .3; }
.si-sw.is-tier { width: 14px; height: 9px; border-radius: 2px; background: var(--si-dn-bg); opacity: 1; }

/* The spine remains the complete history; this is its quick-read answer.
   It sits below the chart so a reader does not have to infer the selected
   action, failed gates and CPR path from several separate lanes. */
.si-catch {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 118px), 1fr));
  gap: 1px;
  margin-top: 4px;
  border: 1px solid var(--line);
  border-radius: 7px;
  overflow: hidden;
  background: var(--line);
}
.si-catch > div {
  min-width: 0;
  padding: 5px 9px;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.si-catch span {
  font: 620 11px/1.2 var(--font-mono);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
}
.si-catch strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 660 11.5px/1.3 var(--font-mono);
  color: var(--ink);
}
.si-catch strong.is-up { color: var(--up); }
.si-catch strong.is-down { color: var(--down); }
.si-catch strong.is-warn { color: var(--down); }

/* ---------- bottom ---------- */
.si-bottom { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); gap: var(--si-gap); align-items: stretch; }

.si-mx { width: 100%; border-collapse: collapse; font-size: 13px; font-variant-numeric: tabular-nums; }
.si-mx th, .si-mx td { text-align: left; padding: 5px 8px 5px 0; border-bottom: 1px solid var(--line); white-space: nowrap; }
/* Weight and colour say "header"; a filled bar is not needed, but the header
   does have to stay on screen while a long ladder scrolls under it. */
.si-mx thead th {
  position: sticky; top: 0; z-index: 2; background: var(--paper);
  font: 700 11px/1.4 var(--font-mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); border-bottom: 1px solid var(--line-strong);
}
.si-mx tbody tr:last-child td { border-bottom: 0; }
.si-mx .is-rule { color: var(--ink); font-weight: 560; }
.si-mx-wide td, .si-mx-wide th { padding: 5px 9px; }
/* Fits instead of scrolling: fixed layout shares the width evenly and cells
   wrap, so twelve columns stop forcing a sideways scrollbar under a page whose
   whole point is that it is one view. */
.si-mx-fit { table-layout: fixed; width: 100%; font-size: 12px; }
.si-mx-fit th, .si-mx-fit td { padding: 4px 6px; white-space: normal; word-break: break-word; }
.si-mx-fit thead th { font-size: 10.5px; letter-spacing: .04em; }
/* Wrapping fits the columns but lets a long value grow a row two or three
   lines tall. Two lines is the cap; past that the cell clips and the full
   value stays available on hover. */
.si-mx-fit td {
  max-height: 30px;
  overflow: hidden;
  line-height: 1.25;
}
.si-mx-fit .si-pill { font-size: 11px; padding: 1px 4px; }
.si-mx-fit .sub { font-size: 11px; }
.si-mx-fit td:first-child { white-space: nowrap; }
.si-subhead {
  caption-side: top; text-align: left; padding: 2px 0 5px;
  font: 620 11.5px/1.2 var(--font-mono); letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
}
.si-mx .sub { display: block; font: 500 11.5px/1.3 var(--font-mono); color: var(--muted); }
.si-mx td.num { text-align: right; font-variant-numeric: tabular-nums; }

.si-pill { display: inline-flex; padding: 1px 6px; border-radius: 100px; font: 620 11px/1.6 var(--font-mono); border: 1px solid var(--line-strong); color: var(--muted); background: var(--surface-2); }
.si-pill.is-agree { color: var(--accent); background: var(--si-ac-bg); border-color: var(--si-ac-bd); }
.si-pill.is-conflict { color: var(--down); background: var(--si-dn-bg); border-color: var(--si-dn-bd); }
.si-pill.is-ok { color: var(--up); background: var(--si-up-bg); border-color: var(--si-up-bd); }

.si-ml { display: flex; gap: 1px; align-items: center; }
.si-ml i { width: 2px; height: 10px; border-radius: 1px; background: var(--line-strong); }
.si-ml i.is-agree { background: var(--accent); }
.si-ml i.is-conflict { background: var(--down); }
.si-ml i.is-future { opacity: .22; }

/* Content-sized rows. A tile contains label, value, explanation and sometimes
   a 15/22px chart; the former fixed 62px row could not contain those four
   things, so flexbox shrank the explanation and chart until they appeared
   missing. The row now has a compact floor but grows for wrapped notes, and
   every tile aligns from the top instead of centring a differently clipped
   fragment. */
.si-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 130px), 1fr)); grid-auto-rows: minmax(78px, auto); gap: 1px; background: var(--line); align-items: stretch; }
.si-tile { background: var(--surface); padding: 7px 9px; display: flex; flex-direction: column; gap: 2px; justify-content: flex-start; min-width: 0; min-height: 78px; }
.si-tile span { font: 620 11px/1.3 var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.si-tile strong { font: 640 clamp(12px, 1.1vw, 15px)/1.15 var(--font-mono); color: var(--ink); }
.si-tile strong.is-up { color: var(--up); }
.si-tile strong.is-down { color: var(--down); }
.si-tile em { font: 500 11px/1.35 var(--font-sans); font-style: normal; color: var(--muted); overflow-wrap: anywhere; }
.si-tile span, .si-tile strong, .si-tile em, .si-tile svg { flex: none; }
.si-tile svg { display: block; max-width: 100%; margin-top: auto; }

/* ---------- status ---------- */
.si-status { display: flex; flex-wrap: wrap; align-items: center; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); overflow: hidden; }
.si-status > div { padding: 5px 11px; display: flex; align-items: baseline; gap: 6px; }
.si-status > div + div { border-left: 1px solid var(--line); }
.si-status span { font: 620 11px/1.4 var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.si-status b { font: 640 11px/1.4 var(--font-mono); color: var(--ink); }
.si-status b.is-ok { color: var(--up); }
.si-status b.is-warn { color: var(--down); }
.si-more {
  margin-left: auto; display: inline-flex; align-items: center;
  padding: 5px 11px; border-left: 1px solid var(--line);
  font: 640 11.5px/1.4 var(--font-mono); letter-spacing: .07em;
  color: var(--accent); text-decoration: none;
}
.si-more:hover { background: var(--si-ac-bg); }

/* ---------- fold 2 ---------- */
.si-fold2 { padding: 0 var(--si-pad) var(--si-pad); display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); gap: var(--si-gap); align-items: start; font-variant-numeric: tabular-nums; }
.si-f2head { display: flex; flex-direction: column; gap: 3px; }
.si-f2head h2 { margin: 0; font-size: 18px; color: var(--ink); }
.si-f2head p { margin: 0; font-size: 13px; color: var(--muted); }
.si-f2grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); gap: var(--si-gap); align-items: start; }
.si-fold2 .si-card-in { gap: 10px; }
.si-fine { font: 500 11px/1.5 var(--font-sans); color: var(--muted); margin: 0; }
.si-fine-pad { padding: 0 12px 11px; }

.si-res { display: inline-flex; padding: 1px 6px; border-radius: 100px; font: 620 11px/1.6 var(--font-mono); border: 1px solid var(--line-strong); color: var(--muted); background: var(--surface-2); }
.si-res.is-win { color: var(--up); background: var(--si-up-bg); border-color: var(--si-up-bd); }
.si-res.is-loss { color: var(--down); background: var(--si-dn-bg); border-color: var(--si-dn-bd); }
.si-res.is-open { color: var(--accent); background: var(--si-ac-bg); border-color: var(--si-ac-bd); }

/* The page itself scrolls at compact sizes. No artificial tile-grid height is
   needed: content-sized rows keep every note and mini-chart in normal flow. */

/* === components/social-intelligence.css === */
/* === Market Intelligence — Trump / Truth Social section === */

/* The shared app workspace owns monitor width and safe-area gutters; this file
   only distributes that width within the social-intelligence table.

   WHAT USED TO BE HERE
   --------------------
   .source-tabs / .source-tab   a tab strip that HID one of the two sections.
                                Both are on the page now, stacked, and the
                                shared .seg.is-jump control scrolls between
                                them — nothing is behind a click.
   .social-view-head            a second page-scale title ("Trump / Truth
                                Social monitor") directly under a control that
                                already said Trump / Truth Social, its own
                                eyebrow, and a subtitle that listed the column
                                headers.
   .social-attention            a bordered banner holding one number.
   .social-freshness            a line saying "no market-related post in this
                                window", one row above the muted-rows line that
                                says the same thing with the counts.

   All four are now one .metric-strip row. The honesty line they duplicated is
   the one that survived, because it is the one a reader cannot infer.        */

.social-filter-bar { margin-top: 0; }
/* Widths chosen so that at a laptop width every cell is at most TWO lines.
   That is what sets the row height, and the row height is what decides how
   much of the table is on screen — the summary gets whatever the fixed
   columns do not need, because it is the cell most likely to wrap.
   table-layout: fixed from the table's own minimum width upward, because auto
   layout reallocates these: it was handing the score column 314px it did not
   need and leaving policy context 150px, where "Speculative · Days · Low"
   wrapped and made every row a line taller. */
.social-table { min-width: 1180px; }
@media (min-width: 1180px) {
  .social-table { width: 100%; table-layout: fixed; }
}
.social-table th:nth-child(1) { width: 96px; }
.social-table th:nth-child(2) { width: 82px; }
.social-table th:nth-child(3) { width: 142px; }
.social-table th:nth-child(4) { width: 284px; }
.social-table th:nth-child(5) { width: 208px; }
.social-table th:nth-child(6) { width: 172px; }
.social-row td { vertical-align: top; }

.social-impact { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
.social-impact > strong { color: var(--ink); font-size: 16px; font-variant-numeric: tabular-nums; }
.social-impact > span { color: var(--muted); font-size: 11.5px; white-space: nowrap; }
/* Colour is meaning: it marks an impact that is loud, and nothing else. */
.social-impact.impact-high > strong,
.social-impact.impact-extreme > strong { color: var(--warning); }

/* "SCENARIO ONLY" was a badge on every row. It is true of the column, not of
   the row, so it is stated once in the column header and the sixty badges and
   the line each one occupied are gone. */
.social-scenario { color: var(--ink-2); }
.social-scenario strong,
.social-scenario > span { display: block; }
.social-scenario strong { font: 700 14px var(--font-mono); font-variant-numeric: tabular-nums; }
.social-scenario > span { margin-top: 1px; color: var(--muted); font-size: 11.5px; }
.social-context { min-width: 150px; }
.social-tags { display: flex; flex-wrap: wrap; gap: 2px 3px; }
.social-context .mini-meta { display: block; margin-top: 3px; }

/* Six numbers used to be six bordered, padded, backgrounded boxes in a 2x3
   grid — around 90px of a 133px row, for six values of four characters each.
   The same six at the SAME size, as label/value pairs on a shared line, are
   two lines. The label type went UP (8.5px -> 10px); only the furniture went. */
.score-run {
  display: flex;
  flex-wrap: wrap;
  gap: 1px 9px;
  align-items: baseline;
}
.score-pair { display: inline-flex; align-items: baseline; gap: 3px; white-space: nowrap; }
.score-pair i {
  color: var(--muted);
  font-size: 10px;
  font-style: normal;
  letter-spacing: .03em;
  text-transform: uppercase;
}
.score-pair b { color: var(--ink-2); font: 650 11px var(--font-mono); font-variant-numeric: tabular-nums; }

.social-badge {
  display: inline-block;
  margin: 0 4px 3px 0;
  padding: 2px 6px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1.35;
}
.social-badge.is-relevant { border-color: var(--warning); color: var(--warning); }
.social-badge.is-sector { font-weight: 550; letter-spacing: 0; text-transform: none; }

.social-assets { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px; }
/* What the cap left out. The drawer lists every one of them. */
.social-more { color: var(--muted); font: 650 10.5px var(--font-mono); white-space: nowrap; }
.social-asset {
  padding: 2px 6px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink-2);
  font: 650 10.5px var(--font-mono);
  cursor: pointer;
}
.social-asset:hover { border-color: var(--accent); color: var(--accent); }
.social-asset.is-etf { color: var(--accent); }
.social-none { color: var(--muted); font-size: 11px; }

/* "View full details ->" was a second line under every summary — sixty lines
   of affordance. A caret in the gutter costs none, and the whole summary is
   the target. */
.social-summary-toggle {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.social-summary-caret { flex: 0 0 auto; color: var(--accent); font-size: 11px; line-height: 1.55; }
.social-summary-toggle strong {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  color: var(--ink-2);
  font-size: 12.5px;
  font-weight: 550;
  line-height: 1.5;
}
.social-summary-toggle:hover strong { color: var(--accent); text-decoration: underline; }
.social-summary-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.social-drawer-summary {
  max-width: 115ch;
  margin: 0 0 5px;
  color: var(--ink);
  font-size: 14px;
  font-weight: 650;
  line-height: 1.5;
}
.social-full-post {
  max-width: 100ch;
  margin: 4px 0 9px;
  color: var(--ink-2);
  font-size: clamp(15.5px, calc(14px + .15vw), 17px);
  line-height: 1.72;
  white-space: pre-wrap;
}
.social-drawer .drawer-more { align-self: flex-start; text-decoration: none; }
.social-detail-section { margin-top: 8px; }
.social-detail-section h4,
.social-post-heading { margin: 0 0 6px; color: var(--muted); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; }
.social-detail-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(140px, 1fr));
  gap: 5px;
  max-width: 1180px;
}
.social-detail-item { padding: 7px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--paper); }
.social-detail-item span,
.social-detail-item strong { display: block; }
.social-detail-item span { margin-bottom: 2px; color: var(--muted); font-size: 9px; letter-spacing: .04em; text-transform: uppercase; }
.social-detail-item strong { color: var(--ink-2); font-size: 11px; font-weight: 600; line-height: 1.45; overflow-wrap: anywhere; }
.social-post-heading { margin-top: 13px; font-size: 11.5px; }

/* On a wide monitor, keep the naturally compact fields bounded and assign all
   additional room to the synopsis. This avoids both giant score cards and a
   narrow summary surrounded by unused page margins. Below this breakpoint the
   existing 1280px minimum remains and the panel scrolls horizontally. */
@media (min-width: 1500px) {
  .market-app .social-table { min-width: 0; }
  .market-app .social-table th:nth-child(1) { width: 108px; }
  .market-app .social-table th:nth-child(2) { width: 92px; }
  .market-app .social-table th:nth-child(3) { width: 124px; }
  .market-app .social-table th:nth-child(4) { width: 252px; }
  .market-app .social-table th:nth-child(5) { width: 208px; }
  .market-app .social-table th:nth-child(6) { width: 200px; }
  .market-app .social-drawer-summary,
  .market-app .social-detail-grid { max-width: none; }
  .market-app .social-detail-grid {
    grid-template-columns: repeat(6, minmax(140px, 1fr));
  }
}

@media (max-width: 980px) {
  .social-detail-grid { grid-template-columns: repeat(2, minmax(140px, 1fr)); }
}

@media (max-width: 620px) {
  .social-detail-grid { grid-template-columns: 1fr; }
}

/* === components/spy-options.css === */
/* SPY Options page (/spy-options): scripts/spy-options.js and the session
   chart, scripts/spy-chart.js.
   Page tokens only, plus one chart hue. --chart-leaders was checked with the
   dataviz palette validator against both card surfaces: #2A78D6 on #FCFAF5
   (light) and #3D8FE6 on #1F1F1F (dark) pass lightness, chroma and 3:1
   contrast. Up/down reuse --up/--down; their colour-blind separation is at the
   floor (ΔE 7.3), so bars also carry direction by sitting above or below zero,
   and every value is repeated in the tooltip and the table. */

:root { --chart-leaders: #2A78D6; }
:root[data-theme="dark"] { --chart-leaders: #3D8FE6; }

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

/* ---- now ---------------------------------------------------------------- */

.spy-now { padding: 16px 20px 8px; border-bottom: 1px solid var(--line); }

.spy-now-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 18px;
  margin-bottom: 14px;
}

.spy-verdict { font-size: 24px; font-weight: 700; color: var(--ink); line-height: 1.2; font-variant-numeric: tabular-nums; }
.spy-verdict.is-flat { color: var(--ink-2); }
.spy-verdict.is-exit { color: var(--warning, var(--ink)); }
.spy-verdict-detail { color: var(--ink-2); font-size: 16px; }

.spy-facts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 10px 18px;
}
.spy-fact { min-width: 0; }
.spy-fact-label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }
/* Live prices on a 5-second refresh, in a proportional sans face: without
   tabular figures a 1 is narrower than a 7 and the whole row shifts as the
   bid ticks. */
.spy-fact-value { color: var(--ink); font-weight: 700; font-size: 17px; font-variant-numeric: tabular-nums; }
.spy-fact-note { color: var(--muted); font-size: 13px; overflow-wrap: anywhere; }

/* ---- chart -------------------------------------------------------------- */

.spy-chart { position: relative; padding: 4px 8px 4px; border-bottom: 1px solid var(--line); background: var(--surface); }
.spy-chart svg { display: block; width: 100%; height: auto; max-width: none; font-family: var(--font-mono); }
.spy-chart-bar { display: flex; justify-content: flex-end; padding: 6px 12px 0; }
.spy-chart:fullscreen { padding: 12px 16px; overflow: auto; }
.spy-chart:fullscreen .spy-chart-bar { padding-top: 0; }

.spy-panel-title { fill: var(--ink-2); font-size: 14px; font-weight: 600; }
.spy-tick { fill: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
.spy-legend { fill: var(--ink-2); font-size: 13px; }
.spy-annotation { fill: var(--ink); font-size: 13px; font-weight: 600; paint-order: stroke; stroke: var(--surface); stroke-width: 3px; }
.spy-status { fill: var(--muted); font-size: 13px; font-weight: 600; }
.spy-status.is-live { fill: var(--up); }
.spy-status.is-delayed { fill: var(--warning, var(--ink)); }
.spy-now-line { stroke: var(--line-strong); stroke-width: 1; }
.spy-last { fill: var(--ink); stroke: var(--surface); stroke-width: 2; }
.spy-last-label { fill: var(--ink); font-size: 13px; font-weight: 700; paint-order: stroke; stroke: var(--surface); stroke-width: 3px; }

.spy-grid { stroke: var(--line); stroke-width: 1; }
.spy-zero { stroke: var(--line-strong); stroke-width: 1; }
.spy-axis { stroke: var(--line-strong); stroke-width: 1; }

.spy-line-price { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; }
.spy-line-spy { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; }
.spy-line-leaders { fill: none; stroke: var(--chart-leaders); stroke-width: 2; stroke-dasharray: 6 4; stroke-linejoin: round; }
.spy-gap { fill: var(--chart-leaders); fill-opacity: 0.12; stroke: none; }

.spy-bar-up { fill: var(--up); }
.spy-bar-down { fill: var(--down); }
.spy-bar-flow { fill: var(--chart-leaders); fill-opacity: 0.75; }

.spy-setup-long { fill: var(--up); fill-opacity: 0.035; }
.spy-setup-short { fill: var(--down); fill-opacity: 0.035; }
.spy-hold-long { fill: var(--up); fill-opacity: 0.06; }
.spy-hold-short { fill: var(--down); fill-opacity: 0.06; }

.spy-guide { stroke-width: 1; stroke-dasharray: 3 3; }
.spy-guide.is-long { stroke: var(--up); }
.spy-guide.is-short { stroke: var(--down); }
.spy-mark { stroke: var(--surface); stroke-width: 2; }
.spy-mark.is-long { fill: var(--up); }
.spy-mark.is-short { fill: var(--down); }
.spy-exit { fill: var(--surface); stroke: var(--ink); stroke-width: 2; }
.spy-flip-up { fill: var(--up); stroke: var(--surface); stroke-width: 2; }
.spy-flip-down { fill: var(--down); stroke: var(--surface); stroke-width: 2; }

.spy-cross { stroke: var(--ink-2); stroke-width: 1; pointer-events: none; }
.spy-hit { fill: transparent; cursor: crosshair; outline: none; }
.spy-hit:focus-visible { stroke: var(--accent); stroke-width: 2; }

.spy-tooltip {
  position: absolute;
  z-index: 2;
  min-width: 190px;
  padding: 8px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: 0 6px 18px rgba(var(--ink-rgb), 0.12);
  font: 13px/1.5 var(--font-mono);
  pointer-events: none;
}
.spy-tooltip-time { color: var(--ink); font-weight: 700; margin-bottom: 2px; }
.spy-tooltip-row { display: flex; justify-content: space-between; gap: 14px; color: var(--muted); }
.spy-tooltip-value { color: var(--ink); font-variant-numeric: tabular-nums; }

/* ---- chart marks for research actions ------------------------------------ */

.spy-watch { fill: var(--surface); stroke-width: 2; stroke-dasharray: 2 2; }
.spy-watch.is-long { stroke: var(--up); }
.spy-watch.is-short { stroke: var(--down); }
.spy-watch:not(.is-long):not(.is-short) { stroke: var(--muted); }
.spy-warn { fill: var(--warning, var(--accent-2)); }

/* ---- trade ticket ---------------------------------------------------------- */

.spy-ticket {
  margin: 4px 0 14px;
  padding: 12px 14px;
  border: 1px solid var(--line-strong);
  border-left: 5px solid var(--ink-2);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.spy-ticket.is-call { border-left-color: var(--up); }
.spy-ticket.is-put { border-left-color: var(--down); }
.spy-ticket-main { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; margin-bottom: 8px; }
.spy-ticket-verb { font-weight: 700; font-size: 19px; color: var(--ink); }
.spy-ticket.is-call .spy-ticket-verb { color: var(--up); }
.spy-ticket.is-put .spy-ticket-verb { color: var(--down); }
.spy-ticket-contract { font-weight: 700; font-size: 19px; color: var(--ink); }
.spy-ticket-facts { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 8px 16px; }
.spy-ticket-fact { display: flex; flex-direction: column; min-width: 0; }
.spy-ticket-value { color: var(--ink); font: 600 14px/1.4 var(--font-mono); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.spy-mono { font-family: var(--font-mono); }
.spy-trades-total { margin: 10px 0 6px; color: var(--ink-2); font-weight: 600; }

/* ---- decision banner ----------------------------------------------------- */

.spy-decision-panel { padding: 16px 20px 12px; }
.spy-decision-panel .spy-now-head { align-items: center; }
.spy-asof { margin-left: auto; color: var(--muted); font: 12px/1.4 var(--font-mono); }

.spy-action-big {
  display: inline-block;
  min-width: 5.5ch;
  padding: 4px 12px;
  border-radius: var(--radius-sm);
  font: 700 22px/1.2 var(--font-mono);
  letter-spacing: 0.04em;
  text-align: center;
  background: var(--surface-2);
  color: var(--ink);
}
.spy-action-big.spy-action-buy { background: var(--up); color: var(--surface); }
.spy-action-big.spy-action-short { background: var(--down); color: var(--surface); }
.spy-action-big.spy-action-exit { background: var(--ink); color: var(--surface); }
.spy-action-big.spy-action-warn { background: var(--warning, var(--accent-2)); color: var(--paper); }
.spy-action-big.spy-action-watch { outline: 2px dashed var(--warning, var(--accent-2)); }
.spy-action-big.spy-action-hold { outline: 2px solid var(--ink-2); }

/* The action word in tables and the confirmation strip. */
.spy-action { font-weight: 700; font-family: var(--font-mono); }
.spy-action-buy { color: var(--up); }
.spy-action-short { color: var(--down); }
.spy-action-exit { color: var(--ink); }
.spy-action-warn { color: var(--warning, var(--ink)); }
.spy-action-watch { color: var(--warning, var(--ink)); font-weight: 600; }
.spy-action-hold { color: var(--ink-2); }
.spy-action-wait { color: var(--muted); font-weight: 400; }

.spy-confirm {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  font: 13px/1.4 var(--font-mono);
}
.spy-confirm-label { color: var(--muted); text-transform: uppercase; font-size: 11px; letter-spacing: 0.06em; }
.spy-confirm-item {
  display: inline-flex;
  gap: 8px;
  align-items: baseline;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.spy-confirm-tf { font-weight: 700; color: var(--ink); }

/* ---- section summary: latest bar by dashboard group ----------------------- */

.spy-summary-wrap { padding: 0 0 14px; border-bottom: 1px solid var(--line); }
.spy-summary-caption { margin: 0 0 8px; color: var(--muted); font: 12px/1.4 var(--font-mono); }
.spy-summary { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 10px; }
/* A group of values, not a card: one hairline on the left instead of a box. */
.spy-card { padding: 0 0 0 12px; border-left: 1px solid var(--line); min-width: 0; }
.spy-card-title { margin-bottom: 6px; color: var(--ink); font-weight: 700; font-size: 14px; }
.spy-card-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 2px 0; font-size: 14px; }
.spy-card-label { color: var(--muted); flex: 0 0 auto; }
.spy-card-value { color: var(--ink); font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; overflow-wrap: anywhere; }
.spy-chips { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px; }
.spy-chip { padding: 1px 6px; border: 1px solid var(--line-strong); border-radius: 999px; font: 12px/1.5 var(--font-mono); font-variant-numeric: tabular-nums; background: var(--surface); }

/* ---- tables -------------------------------------------------------------- */

/* Grouped headers: a band naming each column group, pinned above the column
   names; both rows stay put while the box scrolls. */
.exec-table.spy-grouped thead tr.spy-band-row th {
  top: 0;
  height: 30px;
  padding: 4px 16px;
  border-bottom: 1px solid var(--line);
  color: var(--ink-2);
  font-size: 11px;
  letter-spacing: 0.04em;
  background: var(--surface-2);
}
.exec-table.spy-grouped thead tr + tr th { top: 30px; }
.exec-table.spy-grouped thead tr.spy-band-row th:first-child { z-index: 3; }
.exec-table .spy-group-start { border-left: 1px solid var(--line-strong); }
.exec-table thead .spy-band + .spy-band { border-left: 1px solid var(--line-strong); }

.spy-legend { margin: 0 0 8px; }
.exec-table .spy-long-text { max-width: 360px; overflow: hidden; text-overflow: ellipsis; }
.exec-table .spy-flag { color: var(--warning, var(--ink)); }
.exec-table .spy-flag-serious { color: var(--down); font-weight: 700; }

/* A decision bar is marked on its frozen time cell, so the mark survives
   horizontal scrolling. */
.exec-table tr.spy-row-buy td:first-child { box-shadow: inset 4px 0 0 var(--up); }
.exec-table tr.spy-row-short td:first-child { box-shadow: inset 4px 0 0 var(--down); }
.exec-table tr.spy-row-exit td:first-child { box-shadow: inset 4px 0 0 var(--ink); }
.exec-table tr.spy-row-win td:first-child { box-shadow: inset 4px 0 0 var(--up); }
.exec-table tr.spy-row-loss td:first-child { box-shadow: inset 4px 0 0 var(--down); }
.exec-table tr.spy-row-open td:first-child { box-shadow: inset 4px 0 0 var(--accent); }
.exec-table tr.spy-row-warn td:first-child,
.exec-table tr.spy-row-watch td:first-child { box-shadow: inset 4px 0 0 var(--warning, var(--accent-2)); }

.filter-window.is-disabled select, .exec-auto input:disabled { cursor: not-allowed; }

@media (max-width: 640px) {
  .spy-now { padding: 14px 14px 6px; }
  .spy-verdict { font-size: 20px; }
  .spy-decision-panel { padding: 12px 14px; }
  .spy-asof { margin-left: 0; }
}

/* ---- history --------------------------------------------------------------- */

.spy-note { margin: 8px 0; color: var(--warning, var(--ink)); font-size: 14px; }
.spy-history-facts { margin-bottom: 12px; }
.spy-history-cards { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
.spy-mini { width: 100%; border-collapse: collapse; font: 13px/1.5 var(--font-mono); }
.spy-mini th { color: var(--muted); font-weight: 600; font-size: 11px; text-align: left; padding: 2px 6px; border-bottom: 1px solid var(--line); }
.spy-mini td { padding: 3px 6px; border-bottom: 1px solid var(--line); }
.spy-mini th.exec-num, .spy-mini td.exec-num { text-align: right; font-variant-numeric: tabular-nums; }
.spy-mini tr:last-child td { border-bottom: 0; }
.spy-equity-list { display: grid; gap: 6px; }
.spy-equity-row { display: grid; grid-template-columns: 10ch 1fr 10ch 14ch; align-items: center; gap: 8px; font: 13px/1.4 var(--font-mono); font-variant-numeric: tabular-nums; }
.spy-equity-date { color: var(--ink-2); }
.spy-equity-track { height: 10px; background: var(--surface); border: 1px solid var(--line); border-radius: 999px; overflow: hidden; }
.spy-equity-fill { display: block; height: 100%; border-radius: 999px; }
.spy-equity-fill.is-up { background: var(--up); }
.spy-equity-fill.is-down { background: var(--down); }
.spy-equity-value { text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; }
.spy-equity-note { text-align: right; font-size: 12px; }
.spy-pnl-up { fill: var(--up); }
.spy-pnl-down { fill: var(--down); }
@media (max-width: 640px) {
  .spy-equity-row { grid-template-columns: 9ch 1fr 9ch; }
  .spy-equity-note { display: none; }
}

/* History: the day table sits under the facts, above the signal list. */
.spy-summary-wrap > .spy-grouped { margin-top: 12px; }
/* The summary tables are NOT in a scroll box of their own — .spy-summary-wrap
   has no overflow — so a sticky header here pins to the PAGE and floats over
   whatever is above it. It was written as `.exec-table.spy-grouped`, but
   groupedTable() emits `exec-table spy-table`, so the override never matched
   and the bug was live. Matched on the wrapper alone now, which covers every
   table it can host. */
.spy-summary-wrap .exec-table thead th { position: static; }

.spy-history-line { margin: 0 0 10px; color: var(--ink); font: 600 14px/1.5 var(--font-mono); font-variant-numeric: tabular-nums; white-space: pre-wrap; }

/* ---- compact layout ---------------------------------------------------------- */

.spy-page .page-head { margin-bottom: 10px; }
.spy-page .spy-decision-panel { padding: 8px 14px; margin-bottom: 10px; }
.spy-compact-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 0; }
.spy-compact-head .spy-action-big { font-size: 16px; padding: 3px 10px; min-width: 0; }
.spy-compact-head .spy-verdict-detail { font-size: 15px; margin-right: 6px; }
.spy-compact-head .spy-confirm-item { padding: 2px 8px; font: 12px/1.4 var(--font-mono); }
.spy-compact-head .spy-asof { margin-left: auto; }
.spy-page .spy-ticket { margin: 8px 0 2px; padding: 8px 12px; }
.spy-page .metric-strip { margin-bottom: 10px; }
/* 19px was this page's tile value; the strip keeps it rather than dropping
   to the 24px default, because eight metrics share this row. */
.spy-page .metric-v { font-size: 19px; }
.spy-page .metric-note { font-size: 11.5px; }
.spy-page .filter-bar { margin-bottom: 8px; }
.spy-page .exec-tabs { margin-bottom: 8px; }
.spy-page .exec-tab { padding: 4px 10px; font-size: 14px; }
.spy-page .exec-table td { padding: 5px 12px; }
.spy-page .exec-table th { padding: 6px 12px; }
.spy-page .exec-table th[aria-sort] { padding: 0; }
.spy-page .exec-table th .th-sort { padding: 6px 12px; }

/* The Signals tile is a switch. */
.spy-stat-toggle { font: inherit; text-align: left; cursor: pointer; color: inherit; }
.spy-stat-toggle:hover .metric-v { text-decoration: underline; text-underline-offset: 3px; }
/* Pressed state is the value, not a ring: the strip has no box to ring. */
.spy-stat-toggle.is-on .metric-v { color: var(--accent); }
.spy-stat-toggle.is-on .metric-k { color: var(--accent); }
.spy-signals-toggle.is-disabled { opacity: 0.45; }

/* ---- trade plan ------------------------------------------------------------ */

.exec-table .spy-now-badge { font-weight: 700; }
.exec-table .spy-now-badge.is-buy { color: var(--up); }
.exec-table .spy-now-badge.is-sell { color: var(--down); }
.exec-table .spy-now-badge.is-hold { color: var(--accent); }
.exec-table .spy-now-badge.is-stale { color: var(--warning, var(--ink)); }
.exec-table tr.spy-plan-live td { background: rgba(var(--accent-rgb), 0.07); }
.exec-table tr.spy-plan-live td:first-child { background: var(--surface); }

/* The section's question now rides on the title row, so it drops the padding
   it carried as a row of its own inside the panel. */
.spy-page .page-head > .exec-tab-question { padding: 0; margin: 0; font-size: 14px; }
/* The panels are containers for the data, not cards around it. */
.spy-page .panel { border: 0; border-radius: 0; background: none; padding: 0; box-shadow: none; }
.spy-page .panel > .panel-body { padding: 0; }
.spy-page .spy-decision-panel { padding: 0 0 10px; border-bottom: 1px solid var(--line); }
/* This page stacks six full-width blocks before the first row, so the space
   BETWEEN them is worth as much as the space inside them. */
.spy-page .panel { margin-top: 0; margin-bottom: 8px; }
.spy-page .metric-strip { padding: 7px 0; margin-bottom: 8px; }
.spy-page .metric-note { line-height: 1.3; }
.spy-page .filter-bar { margin-bottom: 6px; }
.spy-page .exec-tabs { margin-bottom: 6px; }
.spy-summary > .spy-card:first-child { border-left: 0; padding-left: 0; }

/* === components/workspace.css === */
/* -------------------------------------------------------------------------
   Authenticated application workspace

   This is the default responsive contract for every current and future app
   page. Add `app-shell` to the body when using the authenticated .dash-bar;
   `app-workspace` on main is an explicit marker and fallback for embedded
   shells. A conventional direct `.dash-bar ~ main` inherits the contract even
   if a future page author forgets the main marker.

   Marketing pages deliberately keep `.container`'s readable prose measure.
   Research/data pages use the browser width because their tables and grids
   benefit from it. Inner prose already carries its own `ch`-based measure.
   ------------------------------------------------------------------------- */

.app-shell {
  --app-gutter: clamp(16px, 2vw, 40px);
}

.app-shell main,
.app-workspace,
body > .dash-bar ~ main {
  width: 100%;
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-left: var(--app-gutter, clamp(16px, 2vw, 40px));
  padding-right: var(--app-gutter, clamp(16px, 2vw, 40px));
}

/* App chrome follows the same x-axis as the workspace and clears iPhone
   notches in both Safari and standalone/PWA display modes. */
.app-shell .dash-bar,
body > .dash-bar {
  padding-top: calc(8px + env(safe-area-inset-top, 0px));
  padding-left: max(var(--app-gutter, clamp(16px, 2vw, 40px)), env(safe-area-inset-left, 0px));
  padding-right: max(var(--app-gutter, clamp(16px, 2vw, 40px)), env(safe-area-inset-right, 0px));
}

.app-nav {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}
.app-nav .btn { white-space: nowrap; }
.app-nav .btn[aria-current="page"] {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--paper);
  box-shadow:
    0 1px 2px rgba(var(--ink-rgb), .16),
    0 6px 18px -10px rgba(var(--ink-rgb), .42);
}
.app-nav .btn[aria-current="page"]:hover { background: #000; border-color: #000; }

/* ---------------------------------------------------------------------------
   The navigation lane inside the one-lane app bar

   Elsewhere this same include is a column in the smart-intraday menu and the
   list of "pages your token covers" on /403, and there the outlined buttons
   are right. In the bar it is chrome: one type size, plain text, and a single
   soft chip on the page you are actually looking at.

   `flex: 1 1 auto` with `min-width: 0` makes the lane exactly the space left
   between the brand and the right-hand controls, whatever the content — which
   is what app-nav.js measures against. `overflow: hidden` is the no-script
   floor: if the script never runs, the destinations that do not fit are
   clipped inside the lane instead of shoving the account off the screen.
   --------------------------------------------------------------------------- */
.dash-bar .app-nav {
  flex: 1 1 auto;
  min-width: 0;
  gap: 2px;
  overflow: hidden;
}
/* Deliberately NOT positioned: the overflow menu is absolutely positioned
   against .dash-bar, and only an unpositioned ancestor lets it escape the
   `overflow: hidden` above. */
.dash-bar .app-nav-overflow { position: static; flex: 0 0 auto; }

.dash-bar .app-nav .btn[aria-current="page"] {
  background: rgba(var(--ink-rgb), 0.075);
  border-color: transparent;
  color: var(--ink);
  font-weight: 600;
  box-shadow: none;
}
.dash-bar .app-nav .btn[aria-current="page"]:hover {
  background: rgba(var(--ink-rgb), 0.11);
  border-color: transparent;
  color: var(--ink);
}

/* "More" holds whatever the lane could not take. It is never the page you are
   on — app-nav.js fits that link first — so it is always safe to leave shut. */
.dash-bar .app-nav-more { gap: 4px; }
.dash-bar .app-nav-more svg { opacity: 0.7; }
.dash-bar .app-nav-more[aria-expanded="true"] {
  background: rgba(var(--ink-rgb), 0.06);
  color: var(--ink);
}
.dash-bar .app-nav-more[aria-expanded="true"] svg { transform: rotate(180deg); }

.dash-bar .app-nav-more-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  min-width: 190px;
  padding: 6px;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: var(--surface);
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, .35);
}
.dash-bar .app-nav-more-menu .btn {
  justify-content: flex-start;
  text-align: left;
  padding: 7px 10px;
}

/* smart-intraday now carries the lane like every other page, and its grid is
   sized to the viewport rather than to its content. Without this the bar would
   simply be added to a full viewport and the page would gain a scrollbar of
   exactly the bar's height. app-nav.js measures the bar and publishes
   --dash-bar-h, because it is 53px on a laptop, taller on a phone and taller
   again inside an installed app where the safe-area inset is added to it; the
   53px fallback is the desktop value for the moment before script runs. */
.si-body > .dash-bar ~ .si-screen {
  min-height: calc(100vh - var(--dash-bar-h, 53px));
  min-height: calc(100dvh - var(--dash-bar-h, 53px));
}

@supports (padding: max(0px)) {
  .app-shell main,
  .app-workspace,
  body > .dash-bar ~ main {
    padding-left: max(var(--app-gutter, 16px), env(safe-area-inset-left, 0px));
    padding-right: max(var(--app-gutter, 16px), env(safe-area-inset-right, 0px));
  }
}

/* Every dense result surface owns its overflow. The document itself never
   becomes wider than the phone, while tables remain complete and scrollable. */
.app-shell .table-wrap,
.app-shell .chat-data-wrap,
.app-shell .chat-text table,
body > .dash-bar ~ main .table-wrap {
  max-width: 100%;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

@media (max-width: 760px) {
  .app-shell { --app-gutter: 14px; }

  /* One lane is not honest on a phone: eleven destinations cannot share a
     390px line with a brand and an account without becoming unreadable. So
     the phone gets a DEFINED two-row bar — a stable identity row (brand, the
     theme control, who this is, the way out) and one swipeable lane of
     destinations under it — rather than the unpredictable three-to-four rows
     it used to wrap into. app-nav.js leaves every destination in the lane at
     this width and scrolls the current one into view, because a swipe is
     already the natural gesture here and a menu would be a second tap. */
  .app-shell .dash-bar,
  body > .dash-bar {
    flex-wrap: wrap;
    gap: 4px;
    min-height: 0;
    padding-top: calc(8px + env(safe-area-inset-top, 0px));
    padding-bottom: 4px;
  }

  .app-shell .dash-bar .dash-bar-right,
  body > .dash-bar .dash-bar-right {
    order: 1;
    flex: 0 0 auto;
    gap: 4px;
    flex-wrap: nowrap;
  }
  .app-shell .dash-bar .app-nav,
  body > .dash-bar .app-nav {
    order: 2;
    flex: 1 0 100%;
    width: 100%;
    gap: 2px;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .app-shell .dash-bar .app-nav::-webkit-scrollbar,
  body > .dash-bar .app-nav::-webkit-scrollbar { display: none; }
  .app-shell .dash-bar .app-nav > *,
  body > .dash-bar .app-nav > * { flex: 0 0 auto; }
  /* A lane that simply stops mid-word reads as broken. When there IS more to
     the right, the last few pixels fade — so the cut is visibly deliberate and
     an invitation to swipe. app-nav.js sets the class only when it overflows,
     so a four-destination reader token never sees a fade over nothing. */
  .app-shell .dash-bar .app-nav.is-scrollable,
  body > .dash-bar .app-nav.is-scrollable {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 40px), transparent 100%);
    mask-image: linear-gradient(90deg, #000 calc(100% - 40px), transparent 100%);
  }
  .app-shell .dash-bar .btn,
  .app-shell .dash-bar button,
  body > .dash-bar .btn,
  body > .dash-bar button { min-height: 42px; }
  .app-shell .dash-bar .app-nav .btn,
  body > .dash-bar .app-nav .btn { padding: 6px 10px; }
  /* Still a fixed width, but a thumb-sized one: 30px tall enough to press is
     not 30px wide enough to hit. */
  .app-shell .dash-bar .theme-btn,
  body > .dash-bar .theme-btn { width: 44px; }

  /* Safari zooms the whole page when focusing a control below 16px. Component
     styles intentionally use denser desktop type, so the mobile shell owns
     this accessibility correction centrally. */
  .app-shell input,
  .app-shell select,
  .app-shell textarea,
  body > .dash-bar ~ main input,
  body > .dash-bar ~ main select,
  body > .dash-bar ~ main textarea { font-size: 16px; }

  .app-shell .panel,
  .app-shell .intel-panel,
  .app-shell .spy-card { max-width: 100%; }
}

@media (max-width: 430px) {
  .app-shell { --app-gutter: 12px; }
  .app-shell .dash-bar .btn,
  .app-shell .dash-bar button,
  body > .dash-bar .btn,
  body > .dash-bar button { min-height: 44px; }
  /* The identity row has to hold brand, theme, account and sign-out on one
     line at 390px, so the account gives up the width first — it is the one
     thing there that is read rather than pressed. */
  .app-shell .dash-bar .dash-account,
  body > .dash-bar .dash-account { max-width: 11ch; }
}


/* =========================================================================
   Page shell for a research page — one title, one line, one row of controls.

   THE SHAPE THIS REPLACES
   -----------------------
   An eyebrow repeating the title beneath it, a display heading, a paragraph
   explaining the design, then a second heading for the section, then a second
   paragraph explaining that. Four bands of chrome before the first number.

   One .page-head, then .app-section per stacked section, is the whole
   vocabulary. Anything a reader cannot act on does not get a band.
   ========================================================================= */

/* ----- the page title row -------------------------------------------------
   <header class="page-head">
     <h1>Market intelligence</h1>
     <p class="page-head-note">one line of context, or nothing</p>
     <div class="page-head-tools">…jump bar, as-of stamp, Refresh…</div>
   </header>

   Title, context and controls share ONE baseline. The note is optional and it
   is for the reader, never for the author: "what this page is for" is a note,
   "why we built it this way" is not.                                        */
.page-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin: 0 0 10px;
}
.page-head h1 {
  margin: 0;
  /* Down from clamp(24px, 2.6vw, 30px). A display size is decoration on a
     report; this is still unmistakably the page title and is now the same
     height as the controls beside it, which is what buys the row. */
  font-size: 24px;
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.page-head-note {
  margin: 0;
  max-width: 62ch;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
}
.page-head-tools {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-left: auto;
}

/* ----- a stacked section --------------------------------------------------
   Sections stack in ONE scrolling view rather than hiding behind tabs. A
   hairline and a heading separate them; nothing is behind a click.

   <section class="app-section" id="news">
     <header class="app-section-head">
       <h2>Company news</h2>
       <p class="app-section-note">one line, or nothing</p>
       <div class="app-section-tools">…segmented control, count…</div>
     </header>
     …metric strip, filter bar, panel…
   </section>                                                                */
.app-section { margin: 0 0 18px; }
/* The app bar is sticky, so an anchor jump would otherwise land the section
   heading underneath it. --dash-bar-h is published by the bar; the fallback
   covers a page that has not adopted it yet. */
.app-section { scroll-margin-top: calc(var(--dash-bar-h, 160px) + 10px); }
.app-section + .app-section { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--line-strong); }
.app-section-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin: 0 0 8px;
}
.app-section-head h2 {
  margin: 0;
  font-size: 17px;
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.app-section-note { margin: 0; color: var(--muted); font-size: 12.5px; line-height: 1.45; }
.app-section-tools { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-left: auto; }

/* ----- segmented control --------------------------------------------------
   One control, several mutually exclusive positions. Used for a real choice
   (which horizon the scores mean) and, as .seg.is-jump, for moving between
   the stacked sections of one page.

   The active position is read from the ARIA state the control already has to
   carry — aria-selected on a tab, aria-current on a jump link — so there is
   no class that can disagree with what a screen reader is told. .is-active is
   honoured too, for the older markup that sets it.                          */
.seg {
  display: inline-flex;
  gap: 3px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-2);
}
.seg-item {
  /* Label and qualifier on one baseline. Stacked, four of these made the row
     they sit in 15px taller for no more information; the qualifier keeps its
     own size, it just stops claiming a line. .seg.is-stacked restores the
     two-line form where a label is long enough to need it. */
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 12px;
  border: 0;
  border-radius: calc(var(--radius-md) - 3px);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  font-weight: 650;
  line-height: 1.25;
  text-align: left;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.seg-item:hover { color: var(--ink); }
.seg-item small { font-size: 11px; font-weight: 500; opacity: .78; font-variant-numeric: tabular-nums; }
.seg.is-stacked .seg-item { flex-direction: column; align-items: flex-start; gap: 1px; }
.seg-item.is-active,
.seg-item[aria-selected="true"],
.seg-item[aria-current="true"],
.seg-item[aria-current="page"] { background: var(--ink); color: var(--paper); }
.seg-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (max-width: 620px) {
  /* A segmented control that overflows its row is worse than one that fills
     it. Full width, equal shares. */
  .seg { display: flex; width: 100%; }
  .seg-item { flex: 1; justify-content: center; padding-inline: 8px; text-align: center; }
  .page-head-tools,
  .app-section-tools { margin-left: 0; width: 100%; }
}

/* === sections/cta.css === */
.cta-band {
  padding: clamp(64px, 8vw, 110px) 0;
  text-align: center;
  border-top: 1px solid var(--line);
  background:
    radial-gradient(900px 500px at 50% 0%, rgba(var(--accent-2-rgb), 0.10), transparent 60%),
    radial-gradient(1100px 600px at 50% 100%, rgba(var(--accent-rgb), 0.06), transparent 65%),
    var(--paper);
  position: relative;
  overflow: hidden;
}
.cta-band h2 {
  font-size: clamp(28px, 4.6vw, 64px);
  line-height: 1.08;
  letter-spacing: -0.03em;
  font-weight: 600;
  margin: 0 0 24px;
  text-wrap: balance;
  --grad: linear-gradient(180deg, var(--ink) 0%, var(--accent) 100%);
}
@media (min-width: 900px) {
  .cta-band h2 { line-height: 1; letter-spacing: -0.035em; }
}
.cta-band p {
  font-size: clamp(16px, 1.5vw, 18px);
  color: var(--body);
  max-width: 560px;
  margin: 0 auto clamp(24px, 3vw, 36px);
  line-height: 1.6;
}
.cta-band .ctas {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
}

@media (max-width: 480px) {
  .cta-band .ctas .btn { flex: 1 1 100%; }
}

/* === sections/features.css === */
.features {
  display: grid;
  /* Fluid: 3 up on desktop, 2 up on tablets, 1 up on phones — without needing
     a breakpoint for every step in between. */
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 1px;
  background: var(--line);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.feature {
  background: var(--paper);
  padding: clamp(24px, 2.6vw, 32px) clamp(20px, 2.2vw, 28px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  position: relative;
  min-width: 0;
  transition: background-color .25s ease, transform .25s ease;
}
.feature::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-2) 100%);
  opacity: 0;
  transition: opacity .25s ease;
}
.feature:hover {
  background: var(--surface);
}
.feature:hover::before { opacity: 1; }

.feature-num {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--accent-2-ink);
  text-transform: uppercase;
}
.feature h3 {
  font-size: clamp(20px, 1.9vw, 24px);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.2;
  margin: 0;
  color: var(--ink);
}
.feature p {
  font-size: clamp(14px, 1.2vw, 15px);
  color: var(--body);
  line-height: 1.6;
  margin: 0;
}
.feature-stat {
  margin-top: 4px;
  padding: 14px 16px;
  border-left: 2px solid var(--accent);
  background: rgba(var(--accent-rgb), 0.04);
  border-radius: 0 6px 6px 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: relative;
}
.feature-stat .num {
  font-family: var(--font-mono);
  font-size: clamp(22px, 2.2vw, 26px);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
  line-height: 1.1;
}
.feature-stat .caption {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.45;
}

/* === sections/hero.css === */
.hero {
  padding: clamp(56px, 8vw, 120px) 0 clamp(56px, 6.5vw, 96px);
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.hero .container { position: relative; z-index: 2; }

.hero-bg {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* longhands for Safari < 14.1 */
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.hero-bg::before {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  inset: 0;
  background:
    /* warm bronze glow, top-right */
    radial-gradient(900px 500px at 82% 14%, rgba(var(--accent-2-rgb), 0.18) 0%, transparent 62%),
    /* deep blue glow, bottom-left */
    radial-gradient(1100px 600px at 8% 92%, rgba(var(--accent-rgb), 0.14) 0%, transparent 65%),
    /* very subtle ink wash so text contrasts */
    radial-gradient(1200px 600px at 50% 30%, rgba(var(--ink-rgb), 0.04) 0%, transparent 60%),
    /* fade to paper at the bottom */
    linear-gradient(180deg, transparent 0%, rgba(var(--paper-rgb), 0.6) 70%, var(--paper) 100%);
}
.hero-bg svg {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  inset: 0;
  width: 100%;
  height: 100%;
}
.hero-bg .grid-overlay {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  inset: 0;
  background-image:
    linear-gradient(rgba(var(--ink-rgb), 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--ink-rgb), 0.05) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(ellipse 80% 70% at 50% 30%, #000 30%, transparent 80%);
  mask-image: radial-gradient(ellipse 80% 70% at 50% 30%, #000 30%, transparent 80%);
}
/* No mask support → the grid would run edge to edge as a hard rectangle.
   Fade it back instead so it still reads as texture. */
@supports not ((mask-image: radial-gradient(#000, transparent)) or (-webkit-mask-image: radial-gradient(#000, transparent))) {
  .hero-bg .grid-overlay { opacity: 0.35; }
}

.hero-bg .node { fill: var(--accent); }
.hero-bg .node-glow { fill: var(--accent); opacity: 0.12; }
.hero-bg .edge { stroke: rgba(var(--ink-rgb), 0.18); stroke-width: 1; fill: none; }
.hero-bg .edge-strong { stroke: rgba(var(--accent-rgb), 0.45); stroke-width: 1.2; fill: none; }

/* Travelling pulses.
   `offset-path` landed in Firefox 72 and Safari 16. Without it the circles
   have no cx/cy and pile up as three bronze dots in the top-left corner of
   the hero — so they stay hidden unless the engine can actually move them. */
.hero-bg .pulse { fill: var(--accent-2); opacity: 0; }

@supports (offset-path: path('M 0 0 L 1 1')) {
  @keyframes pulse-travel-1 {
    0%   { offset-distance: 0%;   opacity: 0; }
    8%   { opacity: 1; }
    92%  { opacity: 1; }
    100% { offset-distance: 100%; opacity: 0; }
  }
  .hero-bg .pulse-1 {
    offset-path: path('M 220 180 L 480 240 L 720 180 L 980 320 L 1240 220');
    animation: pulse-travel-1 6s linear infinite;
  }
  .hero-bg .pulse-2 {
    offset-path: path('M 100 420 L 360 360 L 620 480 L 880 420 L 1180 540');
    animation: pulse-travel-1 8s linear infinite;
    animation-delay: 1.5s;
  }
  .hero-bg .pulse-3 {
    offset-path: path('M 1320 80 L 1080 220 L 820 140 L 560 280 L 280 220');
    animation: pulse-travel-1 9s linear infinite;
    animation-delay: 3s;
  }
}
@media (prefers-reduced-motion: reduce) {
  .hero-bg .pulse { animation: none; opacity: 0; }
}

.h1 {
  font-size: clamp(32px, 7vw, 96px);
  line-height: 1.02;
  letter-spacing: -0.035em;
  font-weight: 600;
  margin: 20px 0 20px;
  max-width: 18ch;
  text-wrap: balance;
  --grad: linear-gradient(180deg, var(--ink) 0%, #1a2236 100%);
}
.h1 .accent {
  --grad: linear-gradient(90deg, var(--accent) 0%, #305680 70%, var(--accent-2) 100%);
}
@media (min-width: 900px) {
  .h1 { line-height: 0.98; letter-spacing: -0.04em; max-width: 1100px; }
}

.lede {
  font-size: clamp(16px, 1.5vw, 19px);
  color: var(--body);
  line-height: 1.6;
  max-width: 620px;
  margin: 0 0 clamp(24px, 3vw, 36px);
}
.hero-ctas {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}
.hero-meta {
  margin-top: clamp(32px, 4.5vw, 56px);
  display: flex;
  gap: clamp(24px, 4vw, 48px);
  flex-wrap: wrap;
}
.hero-meta div { display: flex; flex-direction: column; gap: 4px; }
.hero-meta .label {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}
.hero-meta .value { font-size: 15px; color: var(--ink); font-weight: 500; }

@media (max-width: 640px) {
  /* Let the headline find its own break points instead of forcing one that
     overflows a 360px viewport. */
  .h1 br { display: none; }
  .h1 { max-width: none; }
  .hero-ctas .btn { flex: 1 1 auto; min-width: 148px; }
  .hero-meta { gap: 20px 28px; }
}

/* === sections/logos.css === */
.logos {
  padding: 56px 0 40px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--surface-2);
  position: relative;
}
.logos::before,
.logos::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: 1px;
}
.logos::before {
  top: -1px;
  background: linear-gradient(90deg, transparent, rgba(var(--accent-2-rgb), 0.35), transparent);
}
.logos::after {
  bottom: -1px;
  background: linear-gradient(90deg, transparent, rgba(var(--accent-2-rgb), 0.35), transparent);
}
.logos-label {
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
  text-align: center;
  margin-bottom: 28px;
}
.logos-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 56px;
}
.logos-row span {
  font-family: var(--font-sans);
  font-weight: 500;
  color: #9CA3AF;
  font-size: 18px;
  transition: color .2s, transform .2s;
}
.logos-row span:hover {
  color: var(--ink);
  transform: translateY(-1px);
}

/* === sections/preview.css === */
.preview {
  padding: clamp(56px, 8vw, 120px) 0 0;
  background:
    radial-gradient(800px 400px at 50% 100%, rgba(var(--accent-rgb), 0.05), transparent 60%),
    var(--paper);
}
.preview-frame {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 8px;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.6) inset,
    0 30px 80px -20px rgba(var(--ink-rgb), 0.18),
    0 8px 24px -12px rgba(var(--accent-rgb), 0.20);
}
.preview-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
}
.preview-bar .dot {
  width: 10px;
  height: 10px;
  flex: none;
  border-radius: 50%;
  background: var(--line-strong);
}
.preview-bar .dot:nth-child(1) { background: #E89A9A; }
.preview-bar .dot:nth-child(2) { background: var(--accent-2); opacity: 0.65; }
.preview-bar .dot:nth-child(3) { background: #9CC3A0; }
.preview-bar .url {
  margin-left: 12px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.preview-body {
  padding: 0;
  /* min-height, not height: at narrow widths the rows wrap to two lines and a
     fixed 520px box spills its content out through the frame. */
  min-height: 520px;
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
}
.preview-side {
  background: linear-gradient(180deg, var(--paper), var(--surface-2));
  border-right: 1px solid var(--line);
  padding: 20px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.preview-side-label {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
  padding: 8px;
}
.preview-side-item {
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: #374151;
  display: flex;
  align-items: center;
  gap: 10px;
}
.preview-side-item.active {
  background: rgba(var(--accent-rgb), 0.10);
  color: var(--accent);
  font-weight: 600;
}
.preview-side-item::before {
  content: "";
  width: 14px;
  height: 14px;
  flex: none;
  border: 1.5px solid currentColor;
  border-radius: 3px;
  opacity: 0.5;
}
.preview-main {
  padding: clamp(18px, 3vw, 32px);
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0; /* let the grid track shrink instead of forcing overflow */
}
.preview-h {
  font-size: clamp(18px, 2vw, 22px);
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0;
  color: var(--ink);
}
.preview-meta {
  font-size: 12px;
  color: var(--muted);
  font-family: var(--font-mono);
}
.agent-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  transition: border-color .2s ease, box-shadow .2s ease;
}
.agent-row:hover {
  border-color: rgba(var(--accent-rgb), 0.4);
  box-shadow: 0 6px 20px -10px rgba(var(--accent-rgb), 0.25);
}
.agent-row .left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.agent-row .left > svg { flex: none; }
.agent-row .name { font-size: 14px; font-weight: 500; color: var(--ink); }
.agent-row .desc { font-size: 12px; color: var(--muted); margin-top: 2px; }
.agent-row .pill {
  flex: none;
  font-size: 11px;
  padding: 4px 10px;
  border-radius: 100px;
  background: rgba(var(--accent-2-rgb), 0.12);
  color: var(--accent-2-ink);
  font-weight: 600;
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.agent-row .pill.live {
  background: var(--accent);
  color: #fff;
}

@media (max-width: 900px) {
  .preview-body { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  .preview-side { display: none; }
}

@media (max-width: 560px) {
  .preview-frame { padding: 6px; }
  .preview-bar .url { display: none; }
  .agent-row {
    align-items: flex-start;
    flex-direction: column;
    gap: 10px;
  }
  .agent-row .pill { align-self: flex-start; }
  /* The "+ New agent" chip and the heading stop fitting side by side */
  .preview-main > div:first-child { flex-wrap: wrap; gap: 12px; }
}

/* === sections/quote.css === */
.dark {
  background:
    radial-gradient(900px 500px at 85% 10%, rgba(var(--accent-2-rgb), 0.12), transparent 60%),
    radial-gradient(1200px 700px at 5% 90%, rgba(var(--accent-rgb), 0.22), transparent 65%),
    var(--ink);
  color: #fff;
  padding: clamp(64px, 10vw, 140px) 0;
  position: relative;
  overflow: hidden;
}
.dark::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(var(--accent-2-rgb), 0.55), transparent);
}
.dark::after {
  content: "";
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), 0.6), transparent);
}
.dark .eyebrow { color: var(--accent-2); }
.dark .eyebrow::before {
  background: linear-gradient(to right, var(--accent-2), rgba(var(--accent-2-rgb), 0));
}
.dark h2 {
  font-size: clamp(26px, 5.5vw, 72px);
  line-height: 1.12;
  letter-spacing: -0.025em;
  font-weight: 500;
  margin: 24px 0 0;
  max-width: 1080px;
  text-wrap: balance;
}
@media (min-width: 900px) {
  .dark h2 { line-height: 1.05; letter-spacing: -0.03em; }
}
.dark .author-line {
  margin-top: clamp(24px, 3vw, 36px);
  font-size: 13px;
  color: rgba(255, 255, 255, 0.7);
  letter-spacing: 0.01em;
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: clamp(28px, 3.5vw, 48px);
  padding-top: clamp(40px, 6vw, 80px);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  margin-top: clamp(40px, 6vw, 80px);
}
.stat { min-width: 0; }
.stat .num {
  font-size: clamp(38px, 4.4vw, 56px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.05;
  --grad: linear-gradient(135deg, #ffffff 0%, var(--accent-2) 100%);
}
.stat .lab {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.68);
  margin-top: 12px;
  max-width: 220px;
  line-height: 1.45;
}

/* === sections/security.css === */
.security {
  padding: var(--section-y) 0;
  background:
    radial-gradient(900px 500px at 100% 0%, rgba(var(--accent-rgb), 0.05), transparent 60%),
    radial-gradient(700px 400px at 0% 100%, rgba(var(--accent-2-rgb), 0.06), transparent 60%),
    var(--paper);
  border-top: 1px solid var(--line);
  position: relative;
}
.security-grid {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: clamp(32px, 5vw, 80px);
  align-items: start;
}
.security-grid > div { min-width: 0; }
.security-grid h2 {
  font-size: clamp(30px, 5vw, 56px);
  line-height: 1.06;
  letter-spacing: -0.03em;
  font-weight: 600;
  margin: 16px 0 24px;
  text-wrap: balance;
}
.security-grid > div > p {
  font-size: clamp(15px, 1.4vw, 17px);
  color: var(--body);
  line-height: 1.6;
  max-width: 520px;
}
.compliance-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: 0 12px 36px -20px rgba(var(--ink-rgb), 0.18);
}
.compliance-cell {
  background: var(--surface);
  padding: clamp(22px, 2.4vw, 30px) clamp(20px, 2.1vw, 26px);
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: relative;
  min-width: 0;
  transition: background-color .2s ease;
}
.compliance-cell:hover { background: linear-gradient(180deg, var(--surface), var(--surface-2)); }
.compliance-cell .badge {
  width: 40px;
  height: 40px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--accent);
  color: var(--accent);
  background: rgba(var(--accent-rgb), 0.04);
  border-radius: var(--radius-md);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  font-family: var(--font-mono);
  margin-bottom: 8px;
}
.compliance-cell:nth-child(2n) .badge {
  border-color: var(--accent-2);
  color: var(--accent-2-ink);
  background: rgba(var(--accent-2-rgb), 0.06);
}
.compliance-cell .title { font-size: 14px; font-weight: 600; color: var(--ink); }
.compliance-cell .desc { font-size: 13px; color: var(--muted); line-height: 1.55; }

.security-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 32px;
}
.security-list .row {
  display: flex;
  gap: 14px;
  padding: 16px 0;
  border-top: 1px solid var(--line);
  font-size: 14px;
}
.security-list .row:first-child { border-top: none; }
.security-list .row .k {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--accent);
  width: 140px;
  flex: none;
  padding-top: 3px;
}
.security-list .row .v { color: var(--ink); line-height: 1.55; min-width: 0; }

@media (max-width: 900px) {
  .security-grid { grid-template-columns: minmax(0, 1fr); gap: 32px; }
}

@media (max-width: 520px) {
  /* A 140px key column leaves too little room for the value on a phone. */
  .security-list .row { flex-direction: column; gap: 6px; padding: 14px 0; }
  .security-list .row .k { width: auto; padding-top: 0; }
}
