/* Market Intel terminal — base styles.
   The :root block below is the palette of record; [data-theme="light"]
   overrides it wholesale. The JS builds inline styles as strings, and those
   strings reference these custom properties (SVG colour attributes were
   rewritten to style form for the same reason), so both themes repaint from
   here alone. Do not reintroduce colour literals in the JS.

   Direction: professional terminal — amber carries interaction and
   selection, green/red are reserved for signed values, orange is attention.
   No neon, no decorative motion. Text and border tiers are rgba(var(--ink),α),
   where --ink is an RGB triplet. */

:root {
  color-scheme: dark;
  /* surfaces */
  --bg: #0B0B0A;            /* page and shell */
  --bg-rgb: 11, 11, 10;
  --panel: #131311;         /* cards, inputs, table headers */
  --panel-hover: #1A1A17;   /* element hover */
  --edge: #2A2A26;          /* scrollbar thumb, strong dividers */
  /* text — warm off-white; tiers use rgba(var(--ink),α) */
  --text: #EAE6DC;
  --ink: 234, 230, 220;
  /* Text tiers. Four, and only four: the JS used to carry fourteen text
     alphas, nine of them inside a span of 0.23 with neighbours 0.02 apart —
     a span nobody can read as a hierarchy. These four are ~1.35x apart in
     contrast, which is the smallest step that stays legible as a step.
     --ink-4 is the AA floor: nothing weaker than this may carry text.
     The light theme gets its own alphas, not these. See the light block. */
  --ink-1: rgba(var(--ink), .85);   /* 11.4:1 on --bg — emphasis inside body text */
  --ink-2: rgba(var(--ink), .7);    /*  7.9:1 — secondary text, active labels */
  --ink-3: rgba(var(--ink), .6);    /*  6.1:1 — the workhorse: labels, cells */
  --ink-4: rgba(var(--ink), .55);   /*  5.2:1 — weakest tier that still passes AA */
  /* Border tiers. Fifteen alphas collapsed to four; seven of the old ones
     sat between .04 and .10, i.e. below the threshold where a hairline
     changes at all. Same values in both themes: borders are not text.
     The fourth tier is not in the review's draft and is here because the
     count found a role, not just a number: every one of the twelve .35
     borders sits on a text input or a file-download control, and those are
     the one border in the app you are meant to see. Folding them into
     --line-1 would have made every form field in the terminal look
     disabled. */
  --line-edge: rgba(var(--ink), .35); /* input and control edges */
  --line-1: rgba(var(--ink), .16);  /* chip and button outlines */
  --line-2: rgba(var(--ink), .09);  /* the default hairline: panels, rows */
  --line-3: rgba(var(--ink), .06);  /* faintest rule inside a panel */
  /* roles */
  --accent: #D9A648;        /* interactive: links, active nav, buttons, selection */
  --accent-rgb: 217, 166, 72;
  --accent-bright: #E5C079; /* link hover */
  --accent-bg-hover: #E2B45C;
  --on-accent: #0B0B0A;     /* text on an accent-filled control */
  --pos: #69B47E;           /* signed values up, BUY, live */
  --pos-rgb: 105, 180, 126;
  --neg: #D95F52;           /* signed values down, SELL, errors */
  --neg-rgb: 217, 95, 82;
  --neg-soft: #DE8A7A;
  /* The soft twin of --pos, mirroring --neg-soft above. It exists because
     the weaker of two signed readings used to be drawn by taking opacity
     off the strong one, and rgba(var(--pos-rgb),.75) composites to 2.97:1
     on the light ground. A second stepped colour separates the two
     strengths without going under AA: 9.12:1 dark, 4.56:1 light. */
  --pos-soft: #8CC29A;
  --warn: #DE7E36;          /* attention: flags, waiting, high severity */
  --warn-rgb: 222, 126, 54;
  --info: #6E9BC8;          /* neutral reference lines, third series */
  --info-rgb: 110, 155, 200;
  /* categorical series beyond the roles */
  --muted: #98978E;
  --cat-violet: #AC93C6;
  --cat-teal: #86BDB2;
  --cat-gold: #D4B878;
  --cat-blue2: #93A5C6;
  /* Chart series slots. Five, in fixed order, never cycled — see
     charts.js::SERIEN_FARBEN. They are NOT the --cat-* tokens: those were
     picked by eye and fail the computable palette checks (two of them are
     the same colour under protanopia, and one reads as gray). These were
     stepped in OKLCH against the validator of the dataviz method until all
     five checks pass, on both chart grounds and in both themes:
       lightness band L 0.48-0.67 · chroma >= 0.10 · adjacent-pair CVD dE
       >= 8 (min over protan/deutan) · normal-vision floor dE >= 15 ·
       contrast >= 3:1 vs surface.
     Dark, surface #131311: worst adjacent dE 10.6 (protan, s2 vs s1),
     normal-vision floor 15.9. Same result against #0B0B0A.
     Green and red are deliberately absent: this file reserves them for
     signed values (line 9), which is exactly the rule the old series list
     broke by putting --pos in slot 3. Five slots and no sixth because the
     two reserved families leave too few hues that stay apart under
     deuteranopia; a sixth series folds into "other" instead of cycling. */
  --s1: #B58545;   /* amber   */
  --s2: #2CA494;   /* teal    */
  --s3: #855793;   /* violet  */
  --s4: #6690CF;   /* blue    */
  --s5: #A65A69;   /* rose    */
  /* overlay chrome */
  --scrim: rgba(8, 8, 7, .72);
  --shadow-25: rgba(0, 0, 0, .25);
  --shadow-35: rgba(0, 0, 0, .35);
  --shadow-45: rgba(0, 0, 0, .45);
  --shadow-55: rgba(0, 0, 0, .55);
  --shadow-60: rgba(0, 0, 0, .6);
  /* shape */
  --r-control: 4px;         /* buttons, chips, inputs */
  --r-panel: 6px;           /* cards, overlays, tooltips */
  /* type */
  --font-ui: 'IBM Plex Sans', -apple-system, sans-serif;
  --font-mono: 'IBM Plex Mono', monospace;
  /* The type ladder. Six steps, because the JS carried twenty-one sizes and
     twelve of them ran from 9.5px to 15px in half-pixel increments — 92 % of
     all sizes inside a 5.5px band. Three of those steps (10.5, 11, 11.5)
     held 591 declarations across one single pixel, which is a continuum, not
     a hierarchy: no reader can name the difference, so it carries none.
     Every step here is at least 12 % from its neighbour, which is about the
     smallest ratio that still reads as a step at these sizes.
     The floor rises rather than falls: nothing is below 11px afterwards, and
     the 247 declarations that used to sit under it got bigger. --t-lead is
     16px on purpose — that is the inherited default of this document, so
     explicit and inherited text finally agree. */
  --t-micro: 11px;    /* mono micro-labels, table heads, chips, counters */
  --t-small: 12.5px;  /* dense cells and sub-lines */
  --t-body: 14px;     /* prose and table cells */
  --t-lead: 16px;     /* card titles and lead sentences */
  --t-head: 21px;     /* page titles, and the figure of a KPI tile */
  --t-hero: 26px;     /* the one figure a page is about */
  --t-display: clamp(28px, 2.5vw, 40px); /* overview introduction */
  /* The tracking ladder. Four steps, because the JS carried 260 letter-spacing
     declarations in fourteen values, five of them (.12 .13 .14 .15 .16em)
     inside a span of 0.04em with 167 call sites between them. At --t-micro,
     where nearly all of them sit, 0.01em is 0.11px of space between two
     letters: the whole span from .12 to .16 is 0.44px per gap, so those five
     values could not be telling anyone anything apart.
     Every step here is at least 0.04em from its neighbour, which at 11px is
     about half a pixel per gap and the point where a label starts to read as
     deliberately tracked rather than merely set.
     The negative step is gone rather than kept. Seventeen headings ran
     -0.01em and one hero -0.015em; at --t-head that is 0.21px per gap, and
     across the longest title on the site it adds up to under ten pixels of
     line width -- an amount nobody can attribute to tracking. A step that
     cannot be seen is the finding this ladder is undoing, so display type
     sets flat like everything else. */
  --ls-flat: 0;             /* running text, and a run inside a tracked label */
  --ls-caps: .08em;         /* buttons, chips, links: caps that stay compact */
  --ls-caps-strong: .14em;  /* the micro-label in caps: the workhorse */
  --ls-caps-max: .18em;     /* a section eyebrow, and the one row of dots */
  /* The leading ladder. Four steps, because the JS carried 181 line-height
     declarations in fifteen values, one of them in px. 1.5 and 1.6 alone held
     107 of them, 0.067 apart.
     Every step here is at least 1.10x its neighbour -- a finer ratio than the
     12 % of the type ladder and much finer than the 1.33x of the spacing one,
     and that is not an inconsistency: leading accumulates. Type is judged
     against the glyph beside it and a gap against nothing, but the third line
     of a paragraph sits three steps down, so 10 % of leading is already a
     third of a line of displacement by then.
     --lh-snug takes both 1.45 and 1.5, --lh-prose both 1.55 and 1.6: the file
     did keep a real distinction between a dense block and a paragraph, and
     the ladder keeps it. What it drops is the second spelling of each. */
  --lh-solid: 1.15;   /* a figure or a single glyph in its own box */
  --lh-tight: 1.3;    /* headings and two-line clamped titles */
  --lh-snug: 1.45;    /* dense blocks, table cells, sub-lines */
  --lh-prose: 1.6;    /* paragraphs, notes, anything meant to be read */
  /* The spacing ladder. Seven steps, because the JS carried 483 padding
     shorthands in 133 different value chains, 87 of which occurred once or
     twice, plus 361 gaps in 28 values and 605 margins in 36 — and no base
     unit underneath any of it: every whole number from 1 to 18 appeared as a
     padding component, so 9px sat next to 10px sat next to 11px with 150
     call sites spread across two pixels. Nobody can see that difference, so
     it carried no meaning; it only made two cards next to each other
     different heights for no reason anyone could name.
     Every step here is at least 1.33x its neighbour (2/4 and 4/8 are 2x),
     which is roughly where a spacing difference starts to read as a
     decision rather than as noise. Spacing needs a wider ratio than type:
     a glyph is measured against its neighbours on the same line, a gap is
     measured against nothing.
     Where a value sat exactly between two steps (3, 6, 10, 14, 20 — 522
     call sites) it went up, not down. PR #123 raised the type ladder and
     deliberately left the padding alone, so since then every card has been
     tighter than it was drawn; rounding the ties up puts the ratio back.
     Values above the ladder are alignment, not rhythm, and stay numbers:
     the 108px and 222px indents sit under a grid column of exactly that
     width. */
  --sp-1: 2px;    /* inside a badge or a chip: an inset, not a gap */
  --sp-2: 4px;    /* a figure and the line under it */
  --sp-3: 8px;    /* between things that belong to one another */
  --sp-4: 12px;   /* inside a control, a dense cell, a table row */
  --sp-5: 16px;   /* the inside of a card */
  --sp-6: 24px;   /* the page gutter, and the space between sections */
  --sp-7: 32px;   /* the foot of a page and the tail under a long table */
}

/* Light: warm paper, same roles two to three shades darker so every pairing
   keeps its contrast (amber text needs ~#8A651C on this ground, not #D9A648).

   --ink is pure black here, and that is deliberate. The text tiers are written
   as rgba(var(--ink), α) with the SAME α in both themes, but α does not mean
   the same thing on the two grounds: compositing a light ink onto a dark bg
   gains contrast fast, a dark ink onto a light bg gains it slowly. With the
   old warm #201E19 the two most-used tiers both failed WCAG AA on this
   ground — .60 landed at 4.23 and .55 at 3.65 against #F4F1EA. Pure black is
   the only value that lifts .55 over 4.5 (it reaches 4.62); #080705 gets to
   4.36, #0C0B08 to 4.23. The warmth of the theme lives in --bg and --text,
   not in the low-alpha tiers, so nothing reads colder for it.
   Measured per route, share of text nodes failing AA: markets 58.7% -> 0.0,
   flow 47.7% -> 0.0, alerts 45.6% -> 0.0, live runs 60.3% -> 2.0,
   whale 67.8% -> 7.4. The .5 and .45 call sites that remained after that pass
   are gone too; every text node of the rendered app now clears AA in both
   themes. Do not restore a lifted --ink here without re-measuring. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #F4F1EA;
  --bg-rgb: 244, 241, 234;
  --panel: #FBFAF6;
  --panel-hover: #EAE6DB;
  --edge: #CCC7B9;
  --text: #201E19;
  --ink: 0, 0, 0;
  /* Text tiers, light. Own alphas, and that is the whole point: the same α
     buys different contrast on the two grounds. These four were solved for
     the contrast of their dark twins against --bg, so a tier means the same
     strength in both themes instead of the same number:
       dark .85 = 11.44:1  ->  light .8  = 11.66:1
       dark .70 =  7.94:1  ->  light .7  =  8.06:1
       dark .60 =  6.05:1  ->  light .62 =  5.96:1
       dark .55 =  5.23:1  ->  light .58 =  5.15:1
     Worst case of the whole ladder is --ink-4 on --panel-hover: 4.99:1 light,
     5.07:1 dark. The review's draft ladder (.90/.72/.64/.62) predates the
     pure-black --ink above; measured against it, its tiers 3 and 4 land
     0.46 apart in contrast, which is no tier at all. Re-measure before
     touching these. */
  --ink-1: rgba(var(--ink), .8);
  --ink-2: rgba(var(--ink), .7);
  --ink-3: rgba(var(--ink), .62);
  --ink-4: rgba(var(--ink), .58);
  --accent: #8A651C;
  --accent-rgb: 138, 101, 28;
  --accent-bright: #6E5116;
  --accent-bg-hover: #9C7421;
  --on-accent: #FBFAF6;
  /* The role colours below carry >= 4.6:1 against --bg (the page ground), not
     just against --panel. Many labels sit directly on --bg, and at the old
     values --cat-gold (3.95), --cat-teal (4.15), --muted (4.27),
     --cat-blue2 (4.39), --pos (4.47) and --cat-rose (4.48) all failed there.
     --pos was the expensive one: it colours every positive number in the app. */
  --pos: #2D7B4D;
  --pos-rgb: 45, 123, 77;
  --neg: #B23B2E;
  --neg-rgb: 178, 59, 46;
  --neg-soft: #9C5C4F;
  --pos-soft: #54755F;
  --warn: #A55914;
  --warn-rgb: 165, 89, 20;
  --info: #3E6796;
  --info-rgb: 62, 103, 150;
  --muted: #716D60;
  --cat-violet: #77609A;
  --cat-teal: #3B776B;
  --cat-gold: #826A2C;
  --cat-blue2: #596E90;
  /* Chart series slots, light. Same five entities, own steps — a dark
     palette flipped onto paper is not a light palette. Validator on
     surface #FBFAF6: all five checks pass, worst adjacent dE 12.2
     (deutan, s4 vs s3), normal-vision floor 18.2. Same against #F4F1EA. */
  --s1: #826000;
  --s2: #14938D;
  --s3: #723D77;
  --s4: #3D7DB1;
  --s5: #7F3B59;
  --scrim: rgba(45, 42, 35, .45);
  --shadow-25: rgba(70, 62, 48, .14);
  --shadow-35: rgba(70, 62, 48, .18);
  --shadow-45: rgba(70, 62, 48, .22);
  --shadow-55: rgba(70, 62, 48, .25);
  --shadow-60: rgba(70, 62, 48, .28);
}

html, body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--font-ui); }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-bright); }
* { box-sizing: content-box; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--edge); border-radius: var(--r-control); }

/* The detail drawer slides in once per open — state reveal, not decoration. */
@keyframes panelIn { from { opacity: 0; transform: translateX(10px) } to { opacity: 1; transform: translateX(0) } }

/* The tape, live (overview hero): a print slides in exactly when it arrived
   with the latest poll. The first render marks nothing as new, so the
   landing never plays a load show — motion here always means the market
   just did something. */
@keyframes tapeIn { from { opacity: 0; transform: translateY(-5px) } to { opacity: 1; transform: none } }
.tape-in { animation: tapeIn .3s ease-out both; }

/* Market wire (overview): the marquee itself is driven by ambient.js; here
   lives only the one-shot flash a price cell plays when the 30 s poll moved
   it. Reduced motion collapses both to their end state. */
@keyframes wireFlash { from { background: var(--wire-flash) } to { background: transparent } }
.wire-up { --wire-flash: rgba(var(--pos-rgb), .3); animation: wireFlash .9s ease-out both; }
.wire-dn { --wire-flash: rgba(var(--neg-rgb), .3); animation: wireFlash .9s ease-out both; }


.shell { display: flex; height: 100vh; width: 100%; min-width: 1280px; overflow: hidden; }
.sidebar { width: 232px; box-sizing: border-box; flex: none; border-right: 1px solid var(--line-2); display: flex; flex-direction: column; padding: var(--sp-6) var(--sp-4) var(--sp-5); overflow-y: auto; background: var(--panel); scrollbar-width: thin; }
.maincol { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-5); padding: var(--sp-4) var(--sp-6); border-bottom: 1px solid var(--line-2); flex: none; min-height: 28px; }
.content-wrap { flex: 1; min-height: 0; display: flex; position: relative; }
.content { flex: 1; min-width: 1010px; overflow-y: auto; }

input { font-family: inherit; }

/* Focus. Every control used to set `outline:none` inline, so a keyboard user
   moving through the page saw nothing move — the ring is the only thing
   telling them where they are. The accent keeps ≥4.5:1 on both grounds, and
   :focus-visible keeps it off the mouse. Do not reintroduce `outline:none`;
   if a ring crops badly, give the element room, not an override. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-control);
}

/* The sidebar entries are links now; they keep the terminal's flat look. */
.sidebar a, .sidebar a:hover { color: inherit; text-decoration: none; }
.sidebar > * { flex-shrink: 0; }
.nav-group { margin-bottom: var(--sp-3); }
.nav-group-label { font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing:var(--ls-caps); padding: var(--sp-2) var(--sp-4); color: var(--ink-4); }
.sidebar-footer { overflow-wrap: anywhere; }

/* Overview: an editorial introduction followed by a dense evidence table.
   Keep the established palette and spacing scale across both themes. */
.overview-hero { position: relative; overflow: hidden; display: grid; grid-template-columns: minmax(0, 1fr) 272px; gap: var(--sp-7); align-items: center; padding: var(--sp-7) var(--sp-6); border-bottom: 1px solid var(--line-2); }
.overview-hero > canvas { position: absolute; inset: 0; width: 100%; height: 100%; opacity: .12; pointer-events: none; }
.overview-intro { position: relative; min-width: 0; max-width: 800px; }
.overview-eyebrow { font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing:var(--ls-caps); text-transform: uppercase; color: var(--ink-3); }
.content .overview-intro h1 { font-weight: 600; line-height:var(--lh-tight); margin: var(--sp-5) 0; text-wrap: balance; }
.overview-intro h1 span { color: var(--accent); }
.overview-deck { font-size: var(--t-body); color: var(--ink-2); line-height:var(--lh-prose); margin: 0; max-width: 680px; }
.overview-paths { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); margin-top: var(--sp-6); }
.overview-paths > div:hover { border-color: var(--line-edge) !important; }
.research-summary { position: relative; padding: var(--sp-6); background: var(--panel); border: 1px solid var(--line-1); border-top: 2px solid var(--accent); border-radius: var(--r-panel); }
.research-total { display: flex; align-items: center; gap: var(--sp-5); margin: var(--sp-5) 0; }
.research-total strong { font-family: var(--font-mono); font-size: 48px; line-height:var(--lh-solid); font-weight: 500; }
.research-total > span { font-size: var(--t-small); color: var(--ink-2); line-height:var(--lh-prose); }
.research-verdicts { margin: 0; }
.research-verdicts > div { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-4); padding: var(--sp-4) 0; border-top: 1px solid var(--line-2); }
.research-verdicts dt, .research-verdicts dd { margin: 0; }
.research-verdicts dt { font-size: var(--t-small); text-transform: capitalize; }
.research-verdicts dd { font-family: var(--font-mono); font-size: var(--t-body); font-variant-numeric: tabular-nums; }
.research-source { margin-top: var(--sp-4); color: var(--ink-3); font-family: var(--font-mono); font-size: var(--t-micro); line-height:var(--lh-prose); }
.study-row { display: grid; grid-template-columns: 24px minmax(0, 1fr) 136px minmax(160px, 190px) 144px; gap: var(--sp-4); align-items: center; padding: var(--sp-5) var(--sp-6); border-bottom: 1px solid var(--line-2); }
.study-row > * { min-width: 0; }
.study-row > :last-child { line-height:var(--lh-prose); overflow-wrap: anywhere; }
.study-head { padding-top: var(--sp-4); padding-bottom: var(--sp-4); background: var(--panel); }
.study-number { font-variant-numeric: tabular-nums; }
.study-unit { display: block; margin-top: var(--sp-1); line-height:var(--lh-snug); }

/* Skip link: the sidebar is 24 tab stops deep and repeats on every route, so
   the first Tab offers a way past it. Off-screen until focused, never hidden
   with display:none — that would take it out of the tab order entirely. */
.skip {
  position: fixed; left: 8px; top: -60px; z-index: 100;
  background: var(--accent); color: var(--on-accent); padding: var(--sp-3) var(--sp-5); border-radius: var(--r-control);
  font-size: var(--t-body); font-weight: 600; text-decoration: none; transition: top .12s ease;
}
.skip:focus { top: 8px; color: var(--on-accent); }

/* <main> takes focus from the skip link but is not a control: no ring. */
#main:focus, #main:focus-visible { outline: none; }

/* h1 to h4 are the document outline; the look stays the design's.

   Every heading in this app sets its own size and margin inline, so
   neutralising the user-agent defaults here is what makes a promotion free:
   a title div that becomes an <h2> would otherwise grow by half and gain
   0.83em of margin above and below. font-weight was already handled when the
   research surface still had one real heading; size and margin were not, and
   without them the outline could only be fixed at the cost of the layout.
   Measured after the promotion, both themes: identical computed colour,
   size, weight and box for every promoted node. */
.content h1, .content h2, .content h3, .content h4 {
  font-size: inherit;
  font-weight: inherit;
  margin: 0;
}

/* The one remaining animation is the drawer's state reveal; anyone who has
   asked their system for less simply gets the end state. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* Hover utilities. There were eleven of these, named after their alpha:
   .hv-bd20 and .hv-bd22 differed by 0.02, .hv-bd30 and .hv-bd32 by 0.02, and
   .hv-bd35w was .hv-bd35 plus the body of .hv-white. A name that spells out a
   number is not a name — nobody can tell those pairs apart on screen, so they
   carried no distinction. Three edge levels, and the text-brightening is its
   own class again, to be combined where it is wanted. */
.hv-el:not([aria-current="page"]):hover { background: var(--panel-hover) !important; }
.hv-panel:hover { background: var(--panel-hover) !important; }
.hv-edge:hover { border-color: rgba(var(--ink), .22) !important; }
.hv-edge-strong:hover { border-color: rgba(var(--ink), .32) !important; color: var(--text) !important; }
.hv-edge-max:hover { border-color: rgba(var(--ink), .35) !important; }
.hv-white:hover { color: var(--text) !important; }
.hv-accent:hover { color: var(--accent) !important; }
.hv-accentbg:hover { background: var(--accent-bg-hover) !important; }

/* Shared interaction states. Only compact controls compress on press;
   tables and navigation stay still so labels remain easy to track. */
[data-act], button, input, select, textarea, a {
  transition: background-color .16s ease, border-color .16s ease, color .16s ease, box-shadow .16s ease, transform .16s ease;
}
.ui-choice { border: 1px solid transparent; font-weight: 600; user-select: none; }
.ui-choice[aria-pressed="false"]:is(:hover, :focus-visible),
.ui-button:not(.ui-button-primary):not([aria-disabled="true"]):is(:hover, :focus-visible),
:is(.hv-edge, .hv-edge-strong, .hv-edge-max):not([aria-disabled="true"]):is(:hover, :focus-visible) {
  background: var(--panel-hover) !important; border-color: var(--line-edge) !important; color: var(--text) !important;
}
.ui-choice[aria-pressed="true"]:is(:hover, :focus-visible),
.ui-button-primary:not([aria-disabled="true"]):is(:hover, :focus-visible) { background: var(--accent-bg-hover) !important; }
:is(.ui-choice, .ui-button, .help-trigger):not([aria-disabled="true"]):active { transform: scale(.98); }
.ui-button[aria-disabled="true"] { cursor: not-allowed !important; opacity: .6; }
.ui-button[aria-disabled="true"]:hover { background: none !important; box-shadow: none; }
[role="switch"]:not([aria-disabled="true"]):hover { box-shadow: 0 0 0 2px var(--line-edge); }
.nav-group a:not([aria-current="page"]):is(:hover, :focus-visible) { box-shadow: inset 2px 0 var(--accent); }
.nav-group a:is(:hover, :focus-visible) > span:first-child { color: var(--text) !important; }
.overview-paths > [data-act]:is(:hover, :focus-visible) { transform: translateY(-2px); border-color: var(--accent) !important; box-shadow: 0 4px 12px var(--shadow-25); }
.study-row[data-act]:is(:hover, :focus-visible), [role="row"][data-act]:is(:hover, :focus-visible) { background: var(--panel-hover) !important; box-shadow: inset 2px 0 var(--accent); }
[role="columnheader"][data-act]:is(:hover, :focus-visible) { color: var(--accent) !important; text-decoration: underline; text-underline-offset: 4px; }
[aria-sort="ascending"]::after { content: ' ↑'; }
[aria-sort="descending"]::after { content: ' ↓'; }
.content a[href]:hover, .sidebar-footer a[href]:hover { text-decoration: underline; text-underline-offset: 3px; }
input:not([type="checkbox"]):not([type="radio"]):focus-visible, textarea:focus-visible, select:focus-visible {
  border-color: var(--accent) !important; box-shadow: 0 0 0 3px rgba(var(--accent-rgb), .12);
}

/* Optional context stays near the list without hiding its first rows. */
.market-context { margin-top: var(--sp-5); border: 1px solid var(--line-2); border-radius: var(--r-panel); }
.market-context > summary { display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-4) var(--sp-5); cursor: pointer; list-style: none; font-size: var(--t-small); font-weight: 600; }
.market-context > summary::-webkit-details-marker { display: none; }
.market-context > summary::after { content: '+'; margin-left: auto; color: var(--accent); font-family: var(--font-mono); font-size: var(--t-body); }
.market-context[open] > summary::after { content: '−'; }
.market-context > summary:is(:hover, :focus-visible) { background: var(--panel-hover); }
.market-context .context-hint { color: var(--ink-3); font-weight: 400; }
.market-context-body { padding: 0 var(--sp-5) var(--sp-5); border-top: 1px solid var(--line-2); }
.context-note { margin: var(--sp-4) 0; color: var(--ink-3); font-size: var(--t-small); line-height:var(--lh-prose); }
.market-empty { padding: var(--sp-7) var(--sp-6); text-align: center; }
.market-empty h2 { margin: 0; }
.market-empty p { margin: var(--sp-3) 0 var(--sp-5); color: var(--ink-3); font-size: var(--t-small); line-height:var(--lh-prose); }
.empty-actions { display: flex; justify-content: center; gap: var(--sp-3); }
.ux-button { padding: var(--sp-3) var(--sp-4); background: var(--panel); border: 1px solid var(--line-edge); border-radius: var(--r-control); color: var(--text); font-family: var(--font-ui); font-size: var(--t-small); line-height:var(--lh-snug); cursor: pointer; }
.ux-button:is(:hover, :focus-visible) { background: var(--panel-hover); border-color: var(--accent); }
.search-dialog { display: flex; flex-direction: column; max-height: calc(86vh - 24px); }
.search-dialog > :not(.search-results) { flex-shrink: 0; }
.search-results { min-height: 0; max-height: 380px; overflow-y: auto; }
.search-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-5); padding: var(--sp-5) var(--sp-6); border-bottom: 1px solid var(--line-2); }
.search-heading label { font-size: var(--t-lead); font-weight: 600; }
.search-heading p { margin: var(--sp-2) 0 0; color: var(--ink-3); font-size: var(--t-small); line-height:var(--lh-prose); }

/* Help is deliberately opt-in: definitions, not a tooltip on every button. */
.help-trigger { display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; width: 24px; height: 24px; padding: 0; margin: calc(-1 * var(--sp-2)) 0 calc(-1 * var(--sp-2)) var(--sp-2); border: 0; border-radius: var(--r-control); background: transparent; color: var(--ink-3); cursor: help; }
.help-trigger > span { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; border: 1px solid currentColor; border-radius: 50%; font-family: var(--font-ui); font-size: var(--t-micro); font-weight: 600; line-height:var(--lh-solid); text-transform: none; letter-spacing:var(--ls-flat); }
.help-trigger > span::before { content: 'i'; }
.help-trigger:is(:hover, :focus-visible, [aria-expanded="true"]) { color: var(--accent); background: rgba(var(--accent-rgb), .1); }
.help-popover { position: fixed; inset: auto; margin: 0; box-sizing: border-box; width: 304px; max-width: calc(100vw - 16px); padding: var(--sp-5); background: var(--panel); border: 1px solid var(--line-edge); border-radius: var(--r-panel); box-shadow: 0 8px 24px var(--shadow-35); color: var(--text); font-family: var(--font-ui); font-size: var(--t-small); line-height:var(--lh-prose); letter-spacing:var(--ls-flat); text-transform: none; }
.help-popover strong { display: block; font-size: var(--t-body); font-weight: 600; }
.help-popover strong::first-letter { text-transform: uppercase; }
.help-popover p { color: var(--ink-2); margin: var(--sp-3) 0 0; }

@media (prefers-reduced-motion: reduce) {
  .overview-paths > [data-act]:is(:hover, :focus-visible),
  :is(.ui-choice, .ui-button, .help-trigger):active { transform: none; }
}

/* Treemap tiles (wallet page): hover names the tile without moving it — the
   figures come up in the floating card #tip that app.js positions next to
   the pointer. Light ground darkens instead of brightening. */
.tm-tile { transition: filter .12s ease; }
.tm-tile:hover { filter: brightness(1.15); z-index: 5; }
:root[data-theme="light"] .tm-tile:hover { filter: brightness(.94); }

/* The label on a treemap tile brings its own ground.

   The tile fill carries information — hue is the sign of the result, depth is
   the result against the stake (treemap.js::pnlIntensity, .28 to .78 linear) —
   so a label may not change it. But no single ink clears AA over that range.
   Measured across the whole scale, both signs, both themes: the app's --text
   falls to 3.07:1 on the strongest green of the dark theme, pure white to
   3.83:1 there, and pure black to 1.50:1 on the weakest red. On green, white
   holds AA up to ratio .849 and black only starts passing at .816, so a
   per-tile ink flip would have to be threaded through a window 0.033 wide; on
   red there is no flip to make at all, because white holds to the end of the
   scale and black never passes anywhere on it. A threshold that has to be
   re-derived per hue and then hit within three hundredths is not a rule, it is
   a coincidence.

   So the label gets a veil of the page colour instead: it darkens on the dark
   theme and lightens on the light one, which is exactly the direction each
   ground needs, and it leaves 55 % of the fill showing through. Worst case
   over the whole scale: 6.85:1 for --text, 5.47:1 for --ink-1. The veil is the
   page ground and not a shadow on purpose — var(--shadow-35) is what the
   outcome chip used to carry, and on paper it darkened a tile that was already
   heading for the ink, which is how that chip landed at 3.76:1. */
.tm-label { background: rgba(var(--bg-rgb), .45); border-radius: var(--r-control); align-self: flex-start; }
#tip { position: fixed; z-index: 60; pointer-events: none; display: none; max-width: 320px; background: rgba(var(--bg-rgb), .96); border: 1px solid var(--line-1); border-radius: var(--r-panel); padding: var(--sp-4) var(--sp-5); box-shadow: 0 12px 32px var(--shadow-55); }
#tip.on { display: block; }

/* Desktop-only is a decision here, not an oversight: .shell never goes below
   1280px because the markets and tape tables genuinely need the columns.
   Below that a phone gets a sideways-scrolling page, which reads as a broken
   site unless it says otherwise — so it says so. */
.narrow-note { display: none; }
.narrow-fallback { display: none; }

/* Below 768px the note above could not help: the shell lays out at 1316px, the
   phone zooms the whole page to about 0.29, and at that scale the note's own
   text renders at roughly 3 device pixels. A warning nobody can read is not a
   warning. So on a phone the shell is taken out of the layout entirely, which
   removes what forces the zoom, and a readable page stands in its place. The
   terminal is still reachable from there for anyone who wants it anyway. */
@media (max-width: 767px) {
  .shell, .narrow-note { display: none; }
  .narrow-fallback {
    display: block; box-sizing: border-box; max-width: 34rem;
    margin: 0 auto; padding: var(--sp-6) var(--sp-5) var(--sp-7);
  }
}
:root[data-narrow-override="1"] .shell { display: flex; }
:root[data-narrow-override="1"] .narrow-fallback { display: none; }
.narrow-fallback h1 { font-size: 2rem; line-height:var(--lh-tight); margin: var(--sp-5) 0 0; font-weight: 600; text-wrap: balance; }
.narrow-fallback .kicker { font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing:var(--ls-caps-strong); text-transform: uppercase; color: var(--ink-3); margin: 0; }
.narrow-fallback p { line-height:var(--lh-prose); color: var(--ink-2); }
.narrow-fallback .facts { font-family: var(--font-mono); font-size: var(--t-small); color: var(--ink-3); }
.narrow-fallback ul { padding-left: 1.1rem; color: var(--ink-2); line-height:var(--lh-prose); }
.narrow-fallback .links { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin-top: var(--sp-6); }
.narrow-fallback .links a, .narrow-fallback button {
  font-family: var(--font-mono); font-size: var(--t-small); color: var(--ink-2);
  background: none; border: 1px solid var(--line-1); border-radius: var(--r-control);
  padding: var(--sp-3) var(--sp-4); text-decoration: none; cursor: pointer;
}
.narrow-fallback .note { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--ink-4); line-height:var(--lh-prose); }
.narrow-fallback .facts { border-left: 2px solid var(--accent); padding-left: var(--sp-4); }
.narrow-fallback .facts:empty { display: none; }
.narrow-fallback button { background: var(--accent); color: var(--on-accent); border-color: var(--accent); font-weight: 600; }

@media (min-width: 768px) and (max-width: 1279px) {
  .narrow-note {
    /* Keep the notice in flow so it cannot cover the status/theme controls. */
    display: block; position: sticky; left: 0; bottom: 0; z-index: 80;
    box-sizing: border-box; background: rgba(var(--bg-rgb), .97);
    border-bottom: 1px solid var(--line-1); color: var(--ink-2);
    font-family: var(--font-mono); font-size: var(--t-small); line-height:var(--lh-snug);
    padding: var(--sp-4) var(--sp-5); text-align: center;
  }
  .narrow-note b { color: var(--accent); font-weight: 500; }
}
