/* Macro Musings terminal.
 *
 * Plain CSS custom properties, no framework, no build step.
 *
 * DARK BY DEFAULT, AND LIGHT ON REQUEST. Until Phase 18 this said "dark
 * only, deliberately so": the palette is lifted from the owner's GEX
 * dashboard (--page #0d0d0d, --surface #1a1a19, --ink-2 #c3c2b7) so the two
 * products look like one house, and a terminal that changes colour with the
 * operating system is two designs to maintain. The owner asked for themes
 * (2026-09-28). The answer keeps both halves of the old reasoning: dark is
 * still the default, and the light theme is an EXPLICIT choice --
 * <html data-theme="light">, set by the THEME code or the toggle and
 * remembered (theme.js) -- never a prefers-color-scheme branch. It
 * redefines the custom properties below and nothing else, so there is one
 * design with two palettes, not two designs. EVERY colour is a token here:
 * api/tests/test_terminal_css.py fails a colour written anywhere else.
 *
 * THE LIGHT THEME HAS ITS OWN FRESHNESS PALETTE. The dark one is tuned for
 * a near-black page -- --stale #fab219 on white is unreadable. In BOTH themes
 * each state colour is checked in api/tests/test_terminal_css.py at 4.5:1 or
 * better against every background a badge sits on, each chart stroke at 3:1,
 * and the words around them at 4.5:1. The dark palette was not held at first:
 * "absent" read 3.35:1 on a raised card, --ink-3 3.0, the command line's
 * placeholder 2.5; the owner asked for it fixed (2026-09-29), and each was
 * lifted the least that clears the bar, hue kept. The filled button has its
 * own --accent-fill, because white on the link-text accent was 3.6:1.
 * NOTHING IS DIMMED WITH OPACITY but a disabled control: opacity lowers the
 * contrast of every word inside an element, below where any token check can
 * see it -- a locked panel card (.82) put "fresh" at 3.7:1 and a passed
 * calendar row (.72) its link at 3.2:1. A dimmed thing takes a token of its
 * own instead, and the token is checked (review of phase 18).
 *
 * THE .state-* AND .line-* CLASSES ARE NOT DECORATION. There is exactly one of
 * each per server-side freshness state (api/app/health.py ORDER), and
 * api/tests/test_frontend_states.py asserts both sets EQUAL that list -- adding
 * a seventh state server-side turns CI red rather than shipping a card whose
 * warning is silently missing.
 *
 * The exported PNG keeps its own fixed light style (export.js): an image
 * that changes with the reader's theme is not the same image twice.
 */
:root {
  color-scheme: dark;

  --page:    #0d0d0d;
  --surface: #1a1a19;
  --chart-ground: #ffffff;   /* the ground of the owner's frozen oil charts: a light page in EVERY theme (kinds-oil.js); same value in both blocks */
  --raised:  #212120;
  --rail:    #0b0b0b;
  --field:   #0f0f0e;
  --ink:     #ffffff;
  --ink-2:   #c3c2b7;
  --ink-3:   #898989;
  --muted:   #8b8983;
  --placeholder: #8b8983;
  --grid:    #2c2c2a;
  --rule:    #2a2a28;
  --border:  rgba(255, 255, 255, 0.10);
  --border-2: rgba(255, 255, 255, 0.16);
  --hover:   rgba(255, 255, 255, 0.03);
  --head-bg: rgba(255, 255, 255, 0.015);
  --notice-bg: rgba(255, 255, 255, 0.02);
  /* THE WORKBOOK'S WASHES (phase 20): NBER recession bands, and the quadrant
   * fills that sit behind a chart (Q2 and Q4 use --up-wash and --down-wash). */
  --band: rgba(148, 163, 184, 0.16);
  --q1-wash: rgba(94, 196, 214, 0.12);
  --q3-wash: rgba(255, 176, 0, 0.12);

  --accent:  #3f8be6;
  --accent-ink: #ffffff;
  --accent-fill: #1d73db;
  --accent-line: rgba(57, 135, 229, 0.45);
  --accent-wash: rgba(57, 135, 229, 0.08);
  --focus-ring: rgba(57, 135, 229, 0.18);
  --glow-1: rgba(57, 135, 229, 0.10);
  --glow-2: rgba(250, 178, 25, 0.06);
  --shadow: 0 24px 60px -24px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(0, 0, 0, 0.4);
  --dim: rgba(0, 0, 0, 0.62);

  --warn:     #d9a441;
  --bad-ink:  #f0a396;
  --bad-line: rgba(208, 59, 59, 0.55);
  --bad-bg:   rgba(208, 59, 59, 0.10);
  --good-ink:  #a9d6a9;
  --good-line: rgba(12, 163, 12, 0.45);
  --good-bg:   rgba(12, 163, 12, 0.09);

  /* Freshness. `fresh` is intentionally NOT green: a green badge on every card
   * trains the eye to ignore badges, and the whole point of this furniture is
   * that the eye notices when one changes. Fresh is quiet; everything else
   * escalates. */
  --fresh:    #729172;
  --due:      #b8a44a;
  --stale:    #fab219;
  --degraded: #fab219;
  --overdue:  #e08a3c;
  --broken:   #da6565;

  /* DIRECTION, never freshness (phase 18b). Until now a rise was drawn in
   * --fresh and a fall in --overdue, so a falling yield read as "overdue".
   * Up and down have tokens of their own, each checked at 4.5:1 on every
   * surface and on its own wash (api/tests/test_terminal_css.py), and every
   * change also PRINTS its sign and a glyph (card.js::changePill): the
   * colour only repeats what the words say. --key is the keycap's ink.
   * The price chart draws its candles in the same --up / --down (a candle
   * never relies on them alone -- rising is hollow, falling filled). */
  --up:        #4cc38a;
  --down:      #f2766b;
  --up-wash:   rgba(76, 195, 138, 0.12);
  --down-wash: rgba(242, 118, 107, 0.12);
  --key:       #ffb000;
  /* PHASE 22 (W22-D): the world bond table's amber -- a yield, a region heading. The Bloomberg screen it follows prints both in
   * amber on black; this is the keycap's amber, held at 4.5:1 as text on every surface (api/tests/test_terminal_css.py). */
  --amber:     #ffb000;

  /* THE PRICE CHART'S OWN MARKS (phase 18b, pricechart.js / charttools.js),
   * each checked at 3:1 as a stroke on every surface in both themes
   * (api/tests/test_pricechart_css.py). The moving averages take one colour
   * per window (the EMA is dashed), away from the freshness hues the price
   * line is drawn in. */
  --ma-20:     #5ec4d6;
  --ma-50:     #b58cf0;
  --ma-200:    #e889c9;
  /* PHASE 22 (A2): THE SIX MOVING AVERAGES, one colour each, all drawn solid (the owner: "dotted lines as EMA is ugly").
   * The three simple averages, then the three exponential ones; every pair at least 30 apart in CIELAB distance and each
   * at 3:1 as a stroke on every surface (api/tests/test_pricechart_css.py). A line is never named by colour alone: its
   * button, the readout and the warm-up words say "SMA 21" / "EMA 100".
   * SMA slot 1 is MAGENTA, not amber: amber is the price line's own colour when its data is stale (--stale), and the two were the
   * same colour on the light page and 1.5 apart on the dark one (review R22-2 m2). No average may sit near a freshness colour. */
  --sma-1:     #e040e0;
  --sma-2:     #5ec4d6;
  --sma-3:     #b58cf0;
  --ema-1:     #ff7eb6;
  --ema-2:     #a8d44a;
  --ema-3:     #e6e6e6;
  --rsi:       #9fa8ff;
  --volume:    #8d949e;

  /* THE MARKETS HOME's chart lines (phase 20, normalized.js): six series
   * colours for Normalized Performance, the same hues the price chart's marks
   * use (each already checked at 3:1 as a stroke on every surface in both
   * themes, api/tests/test_pricechart_css.py) -- blue, amber, teal, violet,
   * pink, grey. A line is never identified by colour alone: its chip and its
   * end label carry the name. */
  --np-1:      #3f8be6;
  --np-2:      #ffb000;
  --np-3:      #5ec4d6;
  --np-4:      #b58cf0;
  --np-5:      #e889c9;
  --np-6:      #8d949e;
  /* PHASE 23: the compare chart holds eight lines (api/app/boards.py MAX_CHECKED): a lime, and the page's own ink -- the
   * black line of the owner's cross-asset scorecard (SPY), white on this page. Each at 3:1 as a stroke on every surface, and
   * every pair of the eight at least 18 apart in CIELAB distance (api/tests/test_markets_compare_css.py). */
  --np-7:      #a8d44a;
  --np-8:      #f0f0ee;
  /* PHASE 24 (W24-C): THE EQUITIES PAGES hold up to twenty-two lines (api/app/boards.py MAX_EQUITY_LINES), one colour each, in a
   * fixed order a fund keeps whatever is hidden. Twenty-two colours cannot all be told apart by eye and the page never asks the
   * reader to: every line has a chip and a pill with its ticker, and the reading names every line. What is held is 3:1 as a stroke on
   * every surface (4.5:1 on this dark page's), no two colours alike, and neighbours in the order well apart
   * (api/tests/test_equities_css.py). */
  --eq-1:   #1b9b83;
  --eq-2:   #827bed;
  --eq-3:   #c67080;
  --eq-4:   #b76ad4;
  --eq-5:   #c57458;
  --eq-6:   #2894bb;
  --eq-7:   #b97c3d;
  --eq-8:   #a670e1;
  --eq-9:   #d66771;
  --eq-10:  #0f91d3;
  --eq-11:  #049e50;
  --eq-12:  #a778ba;
  --eq-13:  #ee5451;
  --eq-14:  #d160b0;
  --eq-15:  #309e2c;
  --eq-16:  #3d8ae9;
  --eq-17:  #d1668f;
  --eq-18:  #e3612c;
  --eq-19:  #bd729e;
  --eq-20:  #b97d0d;
  --eq-21:  #af71c7;
  --eq-22:  #119c6c;
  /* The colour of the fund a rule is drawing: the class `eq-N` (in the equities block at the end) sets it to `--eq-N`; this is the default,
   * slot 1's. Defined here once and not in the light block: it is a var(), not a colour, and resolves in whichever theme is on. */
  --eqc:    var(--eq-1);
  /* PHASE 25 (W25-K): the colour of the year a seasonal-band line is drawing: the classes `sb-now`, `sb-avg` and `sb-yN` (the block above the Equities
   * pages) set it to a token. Defined here once, as `--eqc` is: it is a var(), not a colour, and resolves in whichever theme is on. */
  --sbc:    var(--accent);

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --chart-h: 900;   /* the frozen oil charts' frame height in px: a class `h<px>` sets it per chart (the PHASE 25 block) */
  --oil-w: 1150;    /* the width every frozen oil chart is drawn at, in px, then scaled (kinds-oil.js W0; tools/oil_frame_heights.py INNER_WIDTH) */
  --oil-k: 0.3;     /* the scale it is shown at until the box has been measured; kinds-oil.js scaleFor writes the real one on the box */
  /* THE OWNER, 2026-10-15: "for the dark mode website, the charts should be black and white ... same UI". His pages are white, by his own
   * rule; in this (the dark, default) theme the frame is inverted and turned back through half a circle of hue: white becomes this page's
   * surface, black text becomes near-white, greys stay grey, and the line colours keep their hues (lighter). The light block says `none`. */
  --oil-frame-filter: invert(0.9) hue-rotate(180deg);

  /* PHASE 21 (W-ESI, polished 2026-10-09): the sentiment dashboard's colours (kinds-esi.js), here in the theme's own
   * block and nowhere else. The five BANDS name a reading (Extreme Fear ... Extreme Greed); the eleven STEPS colour a
   * component card's score, one per ten points, red through amber to green. Each is a text colour as well as a mark, so
   * each holds 4.5:1 on every surface in BOTH themes (api/tests/test_phase21_css.py). */
  --esi-fear-x: #e25f5f; --esi-fear: #ec835a; --esi-neutral: #fab219; --esi-greed: #5cb23a; --esi-greed-x: #22c722;
  --esi-s0: #db6666; --esi-s1: #d66c51; --esi-s2: #d48149; --esi-s3: #d49d49; --esi-s4: #d4b949; --esi-s5: #d4d449;
  --esi-s6: #b9d449; --esi-s7: #9dd449; --esi-s8: #81d449; --esi-s9: #65d449; --esi-s10: #49d449;

  /* PHASE 21 (W-E): the two-factor QR code. A code is DARK ON LIGHT in every theme, so these three are the same in both
   * blocks on purpose: a scanner reads a light module on a dark ground badly or not at all (the code draws its own
   * quiet zone in --qr-paper, and its frame takes the same paper). */
  --qr-paper: #ffffff; --qr-ink: #000000; --qr-edge: #d0d5dd;
}

/* THE LIGHT THEME: the same tokens, redefined. Nothing below this block
 * knows which theme it is in. */
:root[data-theme="light"] {
  color-scheme: light;

  --page:    #f6f6f3;
  --surface: #ffffff;
  --chart-ground: #ffffff;
  --oil-frame-filter: none;   /* the light theme shows the owner's pages exactly as he built them */
  --raised:  #fbfbf9;
  --rail:    #efefeb;
  --field:   #ffffff;
  --ink:     #141413;
  --ink-2:   #3f3e39;
  --ink-3:   #6b6a64;
  --muted:   #5f5e58;
  --placeholder: #72716b;
  --grid:    #dcdbd5;
  --rule:    #dcdbd5;
  --border:  rgba(0, 0, 0, 0.10);
  --border-2: rgba(0, 0, 0, 0.18);
  --hover:   rgba(0, 0, 0, 0.04);
  --head-bg: rgba(0, 0, 0, 0.02);
  --notice-bg: rgba(0, 0, 0, 0.02);
  --band: rgba(100, 116, 139, 0.16);
  --q1-wash: rgba(30, 140, 160, 0.12);
  --q3-wash: rgba(180, 110, 0, 0.12);

  --accent:  #1d5fb4;
  --accent-ink: #ffffff;
  --accent-fill: #1d5fb4;
  --accent-line: rgba(29, 95, 180, 0.45);
  --accent-wash: rgba(29, 95, 180, 0.08);
  --focus-ring: rgba(29, 95, 180, 0.22);
  --glow-1: rgba(29, 95, 180, 0.08);
  --glow-2: rgba(160, 110, 0, 0.05);
  --shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(0, 0, 0, 0.06);
  --dim: rgba(20, 24, 32, 0.5);

  --warn:     #8a5200;
  --bad-ink:  #9e1f1f;
  --bad-line: rgba(180, 35, 35, 0.45);
  --bad-bg:   rgba(180, 35, 35, 0.07);
  --good-ink:  #1f6b1f;
  --good-line: rgba(31, 107, 31, 0.40);
  --good-bg:   rgba(31, 107, 31, 0.07);

  /* Each at 4.5:1 or better on --page, --surface, --raised and --rail
   * (api/tests/test_terminal_css.py). */
  --fresh:    #3f6a3f;
  --due:      #6b5a00;
  --stale:    #8a5200;
  --degraded: #8a5200;
  --overdue:  #a3470c;
  --broken:   #b3261e;

  /* --down is NOT --broken's red: a fall is not a fault.
   * LIGHTENED IN CHROMA, NOT IN CONTRAST (2026-10-15, the owner, after he switched to the light theme: "can't see the colours"): the three were darker
   * than the 4.5:1 bar needs (5.5, 5.2 and 6.3 on the worst surface) and read as brown, dark green and brick. Each is now the most saturated colour of
   * its hue that still clears 4.5:1 on every surface and, for up and down, on its own wash (test_terminal_css.py): #007720, #cc0a0a, #b24d00. */
  --up:        #007720;
  --down:      #cc0a0a;
  --up-wash:   rgba(0, 119, 32, 0.10);
  --down-wash: rgba(204, 10, 10, 0.08);
  --key:       #8a5200;
  --amber:     #b24d00;

  /* The price chart's marks: see the dark block. */
  --ma-20:     #0b7285;
  --ma-50:     #6f42c1;
  --ma-200:    #a61e6e;
  /* The six moving averages: see the dark block. */
  --sma-1:     #6a1f6a;
  --sma-2:     #0b7285;
  --sma-3:     #6f42c1;
  --ema-1:     #c2185b;
  --ema-2:     #5a6b00;
  --ema-3:     #3a3a38;
  --rsi:       #3b4cc0;
  --volume:    #62666d;

  /* The Markets home's chart lines: see the dark block. */
  --np-1:      #1d5fb4;
  --np-2:      #8a5200;
  --np-3:      #0b7285;
  --np-4:      #6f42c1;
  --np-5:      #a61e6e;
  --np-6:      #62666d;
  --np-7:      #5a6b00;
  --np-8:      #171716;
  /* The Equities pages' twenty-two line colours: see the dark block. */
  --eq-1:   #0d8c76;
  --eq-2:   #766ddf;
  --eq-3:   #b76374;
  --eq-4:   #a95cc6;
  --eq-5:   #b7674b;
  --eq-6:   #1086ae;
  --eq-7:   #ab6f2f;
  --eq-8:   #9862d3;
  --eq-9:   #c75965;
  --eq-10:  #0e83c0;
  --eq-11:  #008f48;
  --eq-12:  #996aac;
  --eq-13:  #de4545;
  --eq-14:  #c253a3;
  --eq-15:  #1e901a;
  --eq-16:  #2f7cdb;
  --eq-17:  #c25983;
  --eq-18:  #d4531a;
  --eq-19:  #ae6491;
  --eq-20:  #a7720e;
  --eq-21:  #a264b9;
  --eq-22:  #128d62;

  /* The sentiment dashboard: see the dark block. Darkened until each holds 4.5:1 on the light page, a card and the rail
   * (the middle of the scale is an olive, as --due is: yellow cannot be read as text on white). */
  --esi-fear-x: #c93030; --esi-fear: #b24c21; --esi-neutral: #936100; --esi-greed: #37781f; --esi-greed-x: #0a7c0a;
  --esi-s0: #9d2525; --esi-s1: #9d3d25; --esi-s2: #9d5525; --esi-s3: #8c6121; --esi-s4: #7c691d; --esi-s5: #6b6b19;
  --esi-s6: #5e701a; --esi-s7: #50741b; --esi-s8: #41781c; --esi-s9: #2e781c; --esi-s10: #1d7c1d;

  /* The QR code: the same in both themes, see the dark block. */
  --qr-paper: #ffffff; --qr-ink: #000000; --qr-edge: #d0d5dd;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

/* THE TYPE (phase 18b). One sans for everything a reader reads, numbers
 * included -- tabular and lining, so digits line up down a table and a
 * board without the typewriter look of monospace. Monospace is kept for
 * what IS code: the command line, tickers, a source's own code in a
 * footnote (<code>), keycaps (kbd) and the clocks' digits. The scale, on a
 * 4 px grid: labels 11/600, meta 12, body and tables 13, card body 14,
 * card title 15/600, a screen's title 18/600, a key reading 28/600. No
 * capitals-and-tracking anywhere but the freshness badge and a keycap,
 * where the shouting is the point (api/tests/test_terminal_css.py pins
 * both lists). */
body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font: 13px/1.45 var(--sans);
  font-variant-numeric: tabular-nums lining-nums;
  -webkit-font-smoothing: antialiased;
}
code, kbd { font-family: var(--mono); font-size: 0.92em; }

a { color: var(--accent); text-decoration-color: var(--accent-line); }
a:hover { text-decoration-color: currentColor; }
/* A link that leaves the terminal says so after its words (screens and
 * cards add the arrow as text, " ↗", so it survives a copy and a reader). */

/* =========================================================== the sign-in */
.gate {
  min-height: 100dvh;
  display: grid;
  grid-template-columns: 1fr;
  place-items: center;
  padding: 24px;
  position: relative;
  overflow: hidden;
}

/* A faint yield-curve motif behind the card. Drawn from real shapes rather
 * than noise, and kept at 6% so it reads as texture, never as data -- a
 * decorative chart a reader could mistake for a real one would be exactly the
 * dishonesty this product is against. */
.gate::before {
  content: "";
  position: absolute; inset: -10% -5% auto -5%; height: 70%;
  background:
    radial-gradient(60% 50% at 20% 0%, var(--glow-1), transparent 70%),
    radial-gradient(50% 60% at 85% 10%, var(--glow-2), transparent 70%);
  pointer-events: none;
}

.gate-card {
  position: relative;
  width: 100%; max-width: 400px;
  background: linear-gradient(180deg, var(--raised), var(--surface));
  border: 1px solid var(--border-2);
  border-radius: 14px;
  padding: 32px 30px 26px;
  box-shadow: var(--shadow);
}

.wordmark {
  font-size: 13px; font-weight: 600;
  color: var(--ink-2);
  margin-bottom: 18px;
  display: flex; align-items: center; gap: 12px;
}
/* The logo beside the name (brand.js): two images, the one drawn for the theme is shown. */
.brand-logo { width: 48px; height: 48px; flex: none; }
.brand-logo.for-light { display: none; }
:root[data-theme="light"] .brand-logo.for-dark { display: none; }
:root[data-theme="light"] .brand-logo.for-light { display: block; }
.rail .brand-logo { width: 28px; height: 28px; }
.gate-card h1 {
  font-size: 27px; line-height: 1.15; margin: 0 0 10px;
  font-weight: 680;
}
.gate-card .lede { color: var(--ink-2); font-size: 14.5px; margin: 0 0 24px; }

.field { margin-bottom: 14px; }
label {
  display: block; font-size: 11px;
  color: var(--muted); margin-bottom: 7px; font-weight: 600;
}
input[type=email] {
  width: 100%; padding: 12px 13px;
  font: 15px/1.2 var(--sans);
  background: var(--field); color: var(--ink);
  border: 1px solid var(--border-2); border-radius: 9px;
  transition: border-color .12s ease, box-shadow .12s ease;
}
input[type=email]::placeholder { color: var(--placeholder); }
input[type=email]:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}

button {
  font: 600 14.5px var(--sans); padding: 12px 18px; border-radius: 9px;
  border: 1px solid transparent; background: var(--accent-fill); color: var(--accent-ink);
  cursor: pointer; width: 100%;
  transition: filter .12s ease;
}
button:hover:not([disabled]) { filter: brightness(1.12); }
button[disabled] { opacity: .5; cursor: default; }
button.quiet {
  width: auto; background: transparent; color: var(--ink-2);
  border-color: var(--border-2); font-weight: 500; padding: 7px 13px; font-size: 13px;
}
button.quiet:hover { color: var(--ink); border-color: var(--ink-2); }

.fine {
  color: var(--muted); font-size: 12.5px; line-height: 1.5;
  margin: 18px 0 0; padding-top: 16px; border-top: 1px solid var(--border);
}

.notice {
  font-size: 13.5px; padding: 11px 13px; border-radius: 9px;
  border: 1px solid var(--border-2); margin-top: 14px;
  background: var(--notice-bg); color: var(--ink-2);
}
.notice.bad  { color: var(--bad-ink); border-color: var(--bad-line); background: var(--bad-bg); }
.notice.good { color: var(--good-ink); border-color: var(--good-line); background: var(--good-bg); }

/* ============================================================== the shell */
.shell { display: grid; grid-template-columns: 216px 1fr; min-height: 100dvh; }

.rail {
  border-right: 1px solid var(--border);
  background: var(--rail);
  padding: 20px 0 16px;
  display: flex; flex-direction: column; gap: 1px;
  position: sticky; top: 0; height: 100dvh;
  /* A sticky box does not move with the page, so a rail taller than the window must scroll inside itself: with
   * "Market dashboards" open (what a reader who has chosen nothing finds) eight links, Status and Sign out among
   * them, sat below the bottom edge of a 768 px window and nothing could reach them. */
  overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
}
.rail .wordmark { padding: 0 20px 20px; margin: 0; }
.rail a {
  display: block; padding: 9px 20px; color: var(--ink-2);
  text-decoration: none; font-size: 14px;
  border-left: 2px solid transparent;
}
.rail a:hover { color: var(--ink); background: var(--hover); }
.rail a[aria-current="page"] {
  color: var(--ink); border-left-color: var(--accent);
  background: var(--accent-wash); font-weight: 600;
}
/* Phase 13: a dashboard the index lists as not yet available. Named, not
 * linked, and visibly not a link. */
.rail .rail-unavailable {
  display: block; padding: 9px 20px; color: var(--ink-3);
  font-size: 13px; border-left: 2px solid transparent; cursor: default;
}
/* THE RAIL'S SHAPE (phase 20). The dashboards hang from "Dashboards", one
 * step in and a size smaller; the four screens at the foot -- the newsletter,
 * the freshness words, the status, the help -- are set apart by a rule and are
 * quieter. A link is still a link at every size: the same colours, the same
 * current-page mark (a rule on the left and a wash, never colour alone). */
.rail a.rail-sub { padding: 6px 20px 6px 34px; font-size: 13px; }
.rail .rail-unavailable.rail-sub { padding: 6px 20px 6px 34px; font-size: 12.5px; }
.rail a.rail-quiet { padding: 6px 20px; font-size: 13px; color: var(--ink-3); }
.rail a.rail-quiet:hover { color: var(--ink); }
.rail a.rail-quiet[aria-current="page"] { color: var(--ink); }
.rail .rail-rule { height: 0; margin: 8px 20px; border-top: 1px solid var(--border); }
.rail .spacer { flex: 1; }
.rail .foot { padding: 14px 20px 0; border-top: 1px solid var(--border); margin-top: 14px; }
/* The rail's links, in the box a phone folds away behind the menu button
 * (terminal.js, the phone rule below). On a wide screen they always show and
 * the button is not drawn. */
.rail .rail-links { display: flex; flex-direction: column; gap: 1px; }
.rail .rail-toggle { display: none; }
.rail .rail-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The page uses the WHOLE window, at every width and every zoom (phase 20).
 * It used to cap one pane at a reading width, because a chart was a viewBox
 * at width 100% and grew with the window -- twice its height, and its ticks,
 * at 2560 px -- which left a zoomed-out page "stopping 78% of the way across"
 * (the owner, 2026-10-07). The cause was the chart, and fit.js fixed it: each
 * chart is drawn at the width it is shown at, between 220 and 340 px tall. */
.main { padding: 26px 30px 72px; min-width: 0; }

h1 { font-size: 18px; line-height: 1.3; margin: 0 0 4px; font-weight: 600; }
h2 { font-size: 15px; line-height: 1.3; margin: 0; font-weight: 600; }
h3 { font-size: 13px; margin: 12px 0 4px; font-weight: 600; }
.sub { color: var(--muted); font-size: 13px; line-height: 1.5; margin: 0 0 12px; }

/* =============================================================== the cards */
/* THE CARD'S ANATOMY (phase 18b, card.js): a HEADER -- the title, cut with
 * an ellipsis rather than wrapped; an empty slot kept for a future "lamp"
 * explainer; the freshness badge; a "⋯" menu holding the exports -- then
 * the KEY READING (value, unit, a change pill, "as of" in words), the chart,
 * and a FOOTNOTE (the source and its own code, the window, the
 * disclosure). No overflow:hidden on the card: the menu opens over what is
 * below it, and a clipped menu is a menu that cannot be used. */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 8px; margin-bottom: 12px;
}
.pad { padding: 12px 14px; }
.card-head {
  display: flex; align-items: center; gap: 8px; min-height: 36px;
  padding: 4px 6px 4px 14px; border-bottom: 1px solid var(--border);
  background: var(--head-bg); border-radius: 8px 8px 0 0;
}
.card-head .grow { flex: 1; min-width: 0; }
.card-head h2 {
  font-size: 15px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* On a phone the title shares its row with the badge and used to be cut off with no way to read the rest;
 * it wraps (the whole title is also on the heading's title attribute). */
@media (max-width: 480px) {
  .card-head h2 { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
}
/* The lamp's place, held and empty (card.js::cardHead): the explainer is
 * the next phase, and a header that already has room for it will not
 * shift every card when it lands. */
.card-lamp:empty { display: none; }
.card-note { color: var(--muted); font-size: 12px; margin: 8px 0 0; }

/* THE ⋯ MENU: a <details>, so it opens with a click, Enter or Space and
 * needs no script to be reachable. Every item is the same control -- the
 * CSV is a link (the server's own file), PNG and Copy link are buttons --
 * and all three LOOK the same, which an anchor beside two bordered
 * buttons did not (the owner's screenshot, 2026-09-29). */
.card-menu { position: relative; flex: none; }
.card-menu > summary {
  list-style: none; cursor: pointer; user-select: none;
  min-width: 28px; height: 28px; padding: 0 6px; border-radius: 6px;
  display: grid; place-items: center;
  color: var(--ink-2); font-size: 16px; line-height: 1;
}
.card-menu > summary::-webkit-details-marker { display: none; }
.card-menu > summary:hover { background: var(--hover); color: var(--ink); }
.card-menu > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.card-menu[open] > summary { background: var(--hover); color: var(--ink); }
.card-menu .menu {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 6;
  display: grid; min-width: 156px; padding: 4px;
  background: var(--raised); border: 1px solid var(--border-2); border-radius: 8px;
  box-shadow: var(--shadow);
}
.menu-item, button.menu-item {
  display: block; width: 100%; text-align: left;
  padding: 6px 10px; border: 0; border-radius: 5px;
  background: transparent; color: var(--ink-2);
  font: 13px/1.3 var(--sans); text-decoration: none; cursor: pointer;
}
.menu-item:hover, .menu-item:focus-visible, button.menu-item:hover:not([disabled]) {
  background: var(--hover); color: var(--ink); filter: none;
}

/* CHART ACTIONS (chartactions.js, Phase 28; the owner, 2026-10-16): ENLARGE and DOWNLOAD in a chart card's header. The button's two words
 * are both in the markup and the card's own `data-enlarged` says which is shown, so a poll never has to know the state. Under 520 px the
 * words go and the icons stay (each has a name for a screen reader). */
.card-tools { display: inline-flex; align-items: center; gap: 4px; flex: none; }
.chart-tool { display: inline-flex; align-items: center; height: 28px; padding: 0 9px; font-size: 12.5px; white-space: nowrap; }
.chart-tool .tool-open, .chart-tool .tool-close { display: inline-flex; align-items: center; gap: 5px; }
.chart-tool .tool-icon { font-size: 14px; line-height: 1; }
.chart-tool .tool-close { display: none; }
[data-enlarged="true"] .chart-enlarge .tool-open { display: none; }
[data-enlarged="true"] .chart-enlarge .tool-close { display: inline-flex; }
@media (max-width: 520px) { .chart-tool .tool-word { display: none; } .chart-tool { min-width: 36px; height: 36px; justify-content: center; } }
/* THE OIL & GAS HUB (phase 28; the owner, 2026-10-16): a page of tiles, one for each page it opens, and a way back above each of those pages. */
.market-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; margin-top: 12px; }
.market-tile {
  width: 100%; height: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; text-align: left;
  padding: 14px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; color: var(--ink); cursor: pointer; font: inherit;
}
.market-tile:hover { border-color: var(--accent-line); background: var(--hover); filter: none; }
.market-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.market-tile-title { font: 600 15px var(--sans); }
.market-tile-sum { font: 13px/1.4 var(--sans); color: var(--muted); }
.market-child > .board-chart-bar { margin: 0 0 8px; }
.curves-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.curves-top .curves-picks { flex: 1; min-width: 0; }
/* ENLARGED: the card fills the screen over a dimmed page; the dim and the page's lock exist only while the card does (:has), so a card the
 * page takes away takes them with it. A chart drawn for the width it is shown at is drawn again, taller (fit.js ENLARGED_FIT). */
[data-enlarged="true"] {
  position: fixed; inset: 2.5vh 2.5vw; z-index: 70; margin: 0; overflow: auto;
  background: var(--surface); border: 1px solid var(--border-2); border-radius: 10px; box-shadow: var(--shadow);
}
body:has([data-enlarged="true"]) { overflow: hidden; }
body:has([data-enlarged="true"])::after { content: ""; position: fixed; inset: 0; z-index: 60; background: var(--dim); }
[data-enlarged="true"] .pricechart { height: max(360px, calc(100vh - 320px)); }
[data-enlarged="true"] .pricechart.panes-1, [data-enlarged="true"] .pricechart.panes-2 { height: max(360px, calc(100vh - 320px)); }

/* THE KEY READING: the number first, big, in the sans at tabular width. */
.reading { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; }
.value { font-size: 28px; font-weight: 600; line-height: 1.15; }
.unit { color: var(--muted); font-size: 13px; font-weight: 400; }
.asof { color: var(--muted); font-size: 12px; }

/* THE CHANGE PILL. The sign and the glyph are TEXT (card.js::changePill),
 * so a pill in greyscale still says which way; the colour repeats it. */
.pill {
  display: inline-flex; align-items: baseline; gap: 4px;
  padding: 1px 8px; border-radius: 999px;
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.pill .span { font-weight: 400; }
.change.up { color: var(--up); }
.change.down { color: var(--down); }
.change.flat { color: var(--ink-2); }
.change.none { color: var(--muted); }
.pill.change.up { background: var(--up-wash); }
.pill.change.down { background: var(--down-wash); }
.pill.change.flat, .pill.change.none { background: var(--hover); }
/* A change in a table cell: the same colours, the same rule -- the sign is
 * printed in the cell (render.mjs::colourOnly holds both to it). */
.sign-up { color: var(--up); }
.sign-down { color: var(--down); }
.sign-flat { color: var(--ink-2); }

/* THE FOOTNOTE: who published it and under what code, then the window,
 * then the disclosure -- quiet, and still words. */
.card-foot {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px;
  margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: 12px; color: var(--ink-3);
}
.card-foot code { color: var(--ink-3); }
.card-foot .card-note { margin: 0; flex-basis: 100%; }

/* Enrolment: the setup key and the recovery codes. Monospace and widely
 * spaced, because these get transcribed by hand and a misread 0/O or 1/l costs
 * someone their account. (The one tracking kept outside a badge or a key:
 * api/tests/test_terminal_css.py lists it by name.) */
.secret {
  font-family: var(--mono); font-size: 17px; letter-spacing: .1em;
  background: var(--field); border: 1px solid var(--border-2); border-radius: 9px;
  padding: 12px 13px; word-break: break-all; user-select: all;
}
.codes {
  display: grid; grid-template-columns: 1fr 1fr; gap: 7px;
  margin: 4px 0 2px;
}
.codes code {
  font-family: var(--mono); font-size: 14px; letter-spacing: .06em;
  background: var(--field); border: 1px solid var(--border); border-radius: 7px;
  padding: 8px 10px; text-align: center; user-select: all;
}
.check {
  display: flex; align-items: center; gap: 9px;
  font-size: 13px;
  color: var(--ink-2); font-weight: 400; margin: 14px 0 12px;
}
.check input { width: 16px; height: 16px; accent-color: var(--accent); }

input[type=password], input[type=text] {
  width: 100%; padding: 12px 13px; font: 15px/1.2 var(--sans);
  background: var(--field); color: var(--ink);
  border: 1px solid var(--border-2); border-radius: 9px;
  transition: border-color .12s ease, box-shadow .12s ease;
}
input[type=password]::placeholder, input[type=text]::placeholder { color: var(--placeholder); }
input[type=password]:focus, input[type=text]:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}

.meta { font-size: 12px; color: var(--muted); }

/* ====================================================== the freshness badge */
.badge {
  font-size: 10.5px; letter-spacing: .06em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 5px;
  border: 1px solid currentColor; white-space: nowrap; font-weight: 600;
  flex: none;
}
/* NEVER COLOUR ALONE. Every badge prints its state as TEXT too, so the
 * distinction survives greyscale, colour-blindness and a printed page. */
/* TIMELINESS: colour -- the one thing a badge says (phase 20). The source's
 * qualifier, its dotted underline and its dashed outline, are gone: "this is
 * annoying (absent broken source), plz remove that", the owner, 2026-10-07
 * (docs/FRESHNESS-AXIS-FINDING.md, section 10). What the server said about
 * the source rides in the badge's data-source; no rule here draws it
 * (api/tests/test_state_vocabulary.py holds that). Nothing held is not an
 * alarm, so it is the neutral grey of the page's own quiet words, and its
 * chip says NO DATA -- no red, no dashes. */
.state-fresh    { color: var(--fresh); }
.state-due      { color: var(--due); }
.state-stale    { color: var(--stale); }
.state-overdue  { color: var(--overdue); }
.state-absent   { color: var(--muted); }
.badge.provisional { font-style: italic; }

/* ================================================================ the chart */
.chart { display: block; width: 100%; height: auto; }
/* A chart box (fit.js): the chart drawn at the width it is in, and the
 * zero-height ruler that is measured so that redrawing the chart can never
 * change what is observed. */
.chart-fit { width: 100%; min-width: 0; }
.chart-fit-ruler { width: 100%; height: 0; overflow: hidden; }
.chart .axis { stroke: var(--grid); stroke-width: 1; }
.chart .tick { fill: var(--muted); font-family: var(--sans); font-size: 10px; }
/* The last value's tag on the right edge (chart.js): the reading the card
 * headlines, where the eye ends up. The high and low labels stay the ticks'
 * quiet colour. */
.chart .tick.last-value { fill: var(--ink); font-size: 10.5px; font-weight: 600; }
.chart .last-dot { fill: var(--ink); }
.chart .line { fill: none; stroke: var(--accent); stroke-width: 1.4;
               stroke-linejoin: round; stroke-linecap: round; }
/* One stroke per freshness state, matching the badge beside it. No stroke is
 * dashed: colour is how old, and that is all a line says (phase 20; the
 * source's dash went with its badge qualifier). */
.chart .line-fresh    { stroke: var(--accent); }
.chart .line-due      { stroke: var(--due); }
.chart .line-stale    { stroke: var(--stale); }
.chart .line-overdue  { stroke: var(--overdue); }
.chart .line-absent   { stroke: var(--ink-3); }

/* THE RAIL ON A PHONE is a bar across the top -- the viewport, not a pane,
 * decides this, so it stays a media query. Sign out STAYS: until Phase 18
 * the whole foot was hidden and a phone had no way to sign out. The links
 * wait behind the menu button, and open onto a row of their own below the
 * bar: at 375 px all twenty-odd of them sat above the content and pushed it
 * below the fold (the real-browser check of phase 18b, defect 3). */
@media (max-width: 760px) {
  .shell { grid-template-columns: 1fr; }
  .rail { position: static; height: auto; flex-direction: row; flex-wrap: wrap;
          align-items: center; border-right: 0; border-bottom: 1px solid var(--border);
          padding: 10px 12px; overflow: visible; }
  .rail .wordmark { padding: 0 14px 0 6px; }
  .rail a { border-left: 0; border-radius: 7px; padding: 7px 12px; }
  .rail a[aria-current="page"] { border-left: 0; }
  .rail .spacer { display: none; }
  .rail .foot { display: flex; align-items: center; gap: 8px; margin: 0 0 0 auto;
                padding: 0; border-top: 0; }
  .rail .foot .meta, .rail .foot .notice { display: none; }
  .rail .foot .signout { margin-top: 0; }
  .rail .rail-toggle { display: inline-flex; align-items: center; }
  .rail .rail-links { order: 5; flex-basis: 100%; flex-direction: row; flex-wrap: wrap; padding-top: 8px; }
  .rail[data-menu="closed"] .rail-links { display: none; }
  .main { padding: 18px 14px 48px; }
}

/* ---- phase 10: the track record and the locked panel set ---- */
.chart.track .bar-portfolio { fill: var(--ink); }
/* The benchmark's bar in a token of its own, not --ink-2 at 55% opacity:
 * that was 2.9:1 on the light page, under the 3:1 a graphic needs. */
.chart.track .bar-benchmark { fill: var(--ink-3); }
.chart.track .axis.zero { stroke: var(--ink-2); stroke-width: 1; }
.legend { font-size: 12px; color: var(--ink-2); margin-bottom: 6px; }
.legend .swatch { display: inline-block; width: 10px; height: 10px; vertical-align: -1px; }
.legend .swatch.bar-portfolio { background: var(--ink); }
.legend .swatch.bar-benchmark { background: var(--ink-3); }
.card-note.refused { border-left: 3px solid var(--warn); padding-left: 10px; }
/* A refresh that did not happen (card.js::refreshNote). The words carry it;
 * the rule only sets it apart from the card's own notes. */
.card-note.delayed { border-left: 3px solid var(--border-2); padding-left: 10px; }
.dashboards .dashboard { margin-top: 18px; }
.dashboards .dashboard:first-child { margin-top: 4px; }
.dashboards .dashboard > .sub { margin: 2px 0 8px; max-width: 72ch; }
/* The heading a group of dashboards stands under (2026-10-17, the owner: macro dashboards, and vol and sentiment dashboards): a small label, not a
 * heading level of its own, so the dashboards' own headings keep theirs. */
.dashboards .dashboards-group { margin: 26px 0 0; font: 600 13px var(--sans); color: var(--muted); }
.dashboards .dashboards-group:first-child { margin-top: 4px; }
.dashboards .dashboards-group + .dashboard { margin-top: 8px; }
/* A dashboard's name is a link to it, in the page's ink -- as a table's links
 * are -- and underlined on hover or focus. */
.dashboards .dashboard > h2 a { color: var(--ink); text-decoration: none; }
.dashboards .dashboard > h2 a:hover, .dashboards .dashboard > h2 a:focus-visible {
  text-decoration: underline; text-decoration-color: currentColor;
}
/* A dashboard with nothing to show yet: named, and said so, in the page's
 * quiet words. Not a fault, so not the colour of one. */
.dashboards .dashboard.unavailable > .meta { margin: 2px 0 0; }
.dashboards .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: 12px; }
/* A locked card sinks to the page's colour behind a dashed border -- set
 * apart without dimming its words, which carry the lock and the badge. */
.card.panel.locked { background: var(--page); border-style: dashed; }
h2.section { margin-top: 28px; }

/* ---- phase 11: a dashboard's panels ---- */
/* minmax(0, 1fr), not 1fr: a track's automatic minimum is its content's
 * min-content, and a panel's title is a nowrap heading -- so on a phone one
 * long title ("Calendar-year return, against the benchmark") widened the whole
 * page by 81 px and cut the chart and the badge off at the right (the
 * real-browser check of phase 20, on the Portfolio at 375 px). */
.dashboard-view .panels { display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; }
.panel { border-top: 1px solid var(--rule); padding-top: 12px; min-width: 0; }
.panel.provisional > .card-head .badge { font-style: italic; }
.panel .coverage { margin: 2px 0 6px; }
.panel .note { font-size: 12px; color: var(--ink-2); margin: 2px 0; }
/* min(): a pane narrower than 360 px holds one column instead of overflowing. */
.panel .lines { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(360px, 100%), 1fr)); gap: 12px; }
/* THE LOCK (phase 18b; card.js lockSlot, lockCard and lockChip, worded by
 * locks.js). A sentence to READ, in the body's ink -- not the warning
 * colour, which on every locked card taught the eye that amber means
 * nothing (design triage, point 12) -- beside a small padlock the stylesheet
 * draws (no <svg>, so no locked card holds a <path> a chart count could
 * mistake for a chart). The chip and the padlock are the premium mark, in
 * --key; the words carry the lock either way, never the colour alone.
 * api/tests/test_terminal_css.py holds every colour here at 4.5:1 on every
 * surface in both themes. */
.lock-slot { display: flex; gap: 10px; align-items: flex-start; }
.lock-slot + .lock-slot { margin-top: 8px; }
.lock-words { min-width: 0; }
.lock-words > p { margin: 0; }
.lock-words > p + p { margin-top: 4px; }
.lock { color: var(--ink); font-size: 13px; line-height: 1.45; }
.lock-audience { color: var(--ink-2); font-size: 12.5px; line-height: 1.45; }
.lock-icon {
  position: relative; flex: none; width: 11px; height: 8px; margin: 9px 1px 0;
  border: 1.5px solid var(--key); border-radius: 2px;
}
.lock-icon::before {
  content: ""; position: absolute; left: 1px; bottom: 100%; width: 6px; height: 5px;
  border: 1.5px solid var(--key); border-bottom: 0; border-radius: 4px 4px 0 0;
}
.lock-chip {
  font-size: 10.5px; font-weight: 600; white-space: nowrap; flex: none;
  padding: 2px 7px; border-radius: 5px; border: 1px dashed currentColor; color: var(--key);
}
.card.locked > .pad { padding-top: 12px; padding-bottom: 12px; }
.lock-group + .lock-group { margin-top: 14px; }
.lock-group h3 { font-size: 13px; font-weight: 600; margin: 0 0 6px; }
.lock-group ul { margin: 8px 0 0; }
/* The reader who paid since signing in (terminal.js, locks.entitledBanner):
 * one line above the panes, in words. */
.notice.banner { margin: 6px 12px 0; }
/* A panel's header heads the cards beneath it: a heading, not another box. */
.panel > .card-head { background: none; border-bottom: 0; border-radius: 0; padding-left: 0; min-height: 32px; }
.call { font-size: 13px; color: var(--ink-2); margin: 0 0 6px; }
.quad-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; max-width: 220px; }
.quad-grid .cell { aspect-ratio: 1; display: grid; place-items: center; border: 1px solid var(--rule); color: var(--ink-2); }
.quad-grid .cell.current { background: var(--ink); color: var(--page); font-weight: 600; }
.quadrant .forming .meta { font-style: italic; }


/* ---- phase 12: the tape, the revision tracker, the releases ---- */
.search { display: flex; gap: 10px; align-items: center; margin: 0 0 10px; flex-wrap: wrap; }
.search input.ticker { width: 220px; font-family: var(--mono); }
/* A ticker is a code, and reads as one wherever it is printed. */
.ticker { font-family: var(--mono); }
.search .count { margin-left: auto; }
.spread { margin: 0 0 14px; }
.leg-slot .card { margin-top: 6px; }
/* THE WINDOW NOTE is routine -- "showing the latest 400 observations" is on
 * every board card -- so it is quiet. Amber that is always there teaches the
 * eye to ignore amber (the owner's screenshot, 2026-09-29): the warning
 * colour is kept for a card whose ENTITLEMENT is capped (card.js::capNote
 * sets data-exposure), which an upgrade would change. */
.card-note.capped { color: var(--ink-3); }
.card-note.capped[data-exposure="capped"] { color: var(--warn); }

/* TABLES (phase 18b). Every table sits in a .table-wrap (card.js::
 * tableWrap), which scrolls sideways inside its pane when the table is wider
 * than the pane. The table itself is never display:block: a block table puts
 * its rows in an anonymous shrink-to-fit table, and the status page filled
 * 40% of its pane (the owner's screenshot 6). Numbers, dates and times never
 * wrap ("2026-/08-31" was one date on two lines); headers are sentence case
 * and do not wrap; links in a table read as its ink and underline on hover
 * or focus. A column a narrow pane can do without is .col-opt, and is
 * dropped by the container query below rather than scrolled to. */
.table-wrap { max-width: 100%; overflow-x: auto; }
/* A long table the reader scrolls within: the wrap is then the scroller, so
 * the header row stays in view (tableWrap(table, { tall: true })). */
.table-wrap.tall { max-height: min(70vh, 640px); overflow: auto; }
.grid-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.grid-table th, .grid-table td { padding: 6px 10px; border-top: 1px solid var(--border); text-align: left; vertical-align: top; }
.grid-table th {
  font-size: 11.5px; font-weight: 600; color: var(--ink-3); white-space: nowrap;
  position: sticky; top: 0; z-index: 1; background: var(--surface);
}
.grid-table .num { text-align: right; white-space: nowrap; }
.grid-table .when, .grid-table .nowrap { white-space: nowrap; }
.grid-table a, .grid-table button.linkish { color: var(--ink); text-decoration: none; }
.grid-table a:hover, .grid-table a:focus-visible,
.grid-table button.linkish:hover, .grid-table button.linkish:focus-visible {
  text-decoration: underline; text-decoration-color: currentColor;
}
.grid-table tr.benchmark td { border-top: 1px dashed var(--overdue); }
.kind, .basis { color: var(--ink-2); font-size: 12.5px; }
.verdict.none { color: var(--muted); font-style: italic; }
.verdict.beat { color: var(--fresh); } .verdict.miss { color: var(--overdue); } .verdict.in_line { color: var(--ink-2); }
button.linkish { width: auto; background: none; border: 0; padding: 0; color: var(--accent); font: inherit; cursor: pointer; text-decoration: underline; text-decoration-color: var(--accent-line); }
button.linkish:hover { text-decoration-color: currentColor; filter: none; }
.window { margin: 12px 18px 0; }
.ladder .periods { display: grid; gap: 10px; }
.ladder .period-label { margin-bottom: 4px; }
.rungs { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.rung { display: grid; grid-template-columns: 110px 120px 90px 1fr auto; gap: 12px; padding: 4px 8px; border-left: 3px solid var(--grid); }
.rung.first-print { border-left-color: var(--ink); font-weight: 600; }
.rung.revision { border-left-color: var(--accent); }
.rung.benchmark { border-left-color: var(--overdue); border-left-style: dashed; }
.rung.unchanged { border-left-color: var(--grid); color: var(--muted); }
.withheld ul.plain { list-style: none; margin: 0; }
.withheld li { padding: 4px 0; font-size: 13px; }
/* A PANE, not the window, decides how wide a screen is: a 480 px pane on a
 * desktop gets the narrow ladder and a scrolling table, as a phone does. */
@container pane (max-width: 760px) {
  .rung { grid-template-columns: 1fr 1fr; }
  .grid-table .col-opt { display: none; }
}

/* ---- phase 14: the disclosure inside the card, and the breakdown table ---- */
.card-note.disclosure { color: var(--ink-2); font-style: italic; margin-top: 8px; }
.card.breakdown .grid-table td.ticker { width: 40%; }

/* ------------------------------------------------------------ phase 15 */
/* THE BOARD (phase 18b). A toolbar -- the view (tiles or a table), the
 * connection in words -- then the cards as TILES: value, change, a
 * sparkline and the badge, several to a row, and a tile opened to its full
 * card spans the row. A single column of 290 px charts showed one card per
 * screen (the owner's screenshots 6-9). */
.board-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 0 0 8px; }
.board-toolbar .connection { margin: 0 0 0 auto; font-size: 12px; color: var(--muted); }
.about { margin: 0 0 8px; font-size: 12px; color: var(--muted); }
.about > summary { cursor: pointer; width: max-content; }
.about p { margin: 4px 0 0; max-width: 72ch; line-height: 1.5; }
.seg { display: inline-flex; border: 1px solid var(--border-2); border-radius: 6px; }
.seg button { width: auto; padding: 3px 10px; font: 500 12px var(--sans); border: 0; border-radius: 0;
              background: transparent; color: var(--ink-2); }
.seg button + button { border-left: 1px solid var(--border-2); }
.seg button:hover { color: var(--ink); background: var(--hover); filter: none; }
.seg button[aria-pressed="true"] { color: var(--ink); background: var(--accent-wash); box-shadow: inset 0 -2px 0 var(--accent); }
/* A window the reader's plan does not offer (history.js): muted, with its chip; still focusable, answered in words when pressed. */
.seg button[data-plan-locked] { color: var(--ink-2); }
.seg button[data-plan-locked] .seg-chip { margin-left: 6px; }
.board-grid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); }
/* `hidden` must win over the display these set (an author's display:grid
 * beats the browser's own [hidden] rule). */
.board-grid[hidden], .board-table[hidden], .about[hidden] { display: none; }
.board-grid .card { margin: 0; }
.board-slot { min-width: 0; }
.board-slot[data-open="true"] { grid-column: 1 / -1; }
/* A tile: the card's header and key reading at a glance, a sparkline under
 * them. The title is the button that opens the full card. */
.tile .card-head { min-height: 32px; }
.tile .tile-open {
  width: auto; min-width: 0; max-width: 100%; padding: 0; border: 0; background: none;
  color: var(--ink); font: 600 13px/1.3 var(--sans); text-align: left; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile .tile-open:hover, .tile .tile-open:focus-visible { text-decoration: underline; filter: none; }
.tile .pad { padding: 8px 12px 10px; }
.tile .value { font-size: 22px; }
.tile .spark { display: block; width: 100%; height: 44px; margin-top: 6px; }
.spark .line { stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.board-table .grid-table td:first-child { max-width: 28ch; }
/* The exports as a row under a card whose header another module draws. */
.export-bar { display: flex; gap: 2px; justify-content: flex-end; margin: 2px 0 10px; }
.export-bar .menu-item { width: auto; display: inline-block; padding: 3px 8px; font-size: 12px; }
.connection { margin: 0; }
.notice.cap { border: 1px solid var(--border-2); padding: 10px 12px; border-radius: 8px; margin-bottom: 12px; }
.notice.cap p { margin: 6px 0; }
.status-section .status-line { font-weight: 600; margin: 0 0 6px; }
.status-section table { margin-top: 8px; }
/* Phase 18b (wave B): the counts as a row of badges, each its state's word. */
.status-summary { margin: 0 0 6px; }
.status-summary p { margin: 4px 0 0; }
.status-counts { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 4px; }
.status-table td .rel-title > span:first-child { font-weight: 600; }

/* Phase 16: the release calendar. Every state is also in words on the row
 * (colour-alone is a Phase 15 gate); style only reinforces what the text says. */
.legend-list { margin: 6px 0 0; padding-left: 0; list-style: none; font-size: 12.5px; color: var(--muted); }
.legend-list li { margin: 4px 0; }
/* Phase 18b: the legend is folded away -- "What the tags mean" -- and the
 * window line sits over the table, not in a card of prose. */
details.legend { margin: 0 0 10px; }
details.legend > summary { cursor: pointer; width: max-content; color: var(--muted); font-size: 12px; }
.cal-window { margin: 0 0 6px; }
.grid-table tr.now-row td {
  padding: 2px 10px; border-top: 2px solid var(--accent);
  color: var(--accent); font-size: 11.5px; font-weight: 600;
}
.grid-table td .agency + .external::before { content: "·"; margin: 0 6px; color: var(--muted); }
/* the notes cell carries "time passed" in words; the rest of a passed row
 * reads in --muted (a link keeps its own colour), each at 4.5:1 */
.grid-table tr.passed td:not(:last-child) { color: var(--muted); }

/* ===================================================================
 * PHASE 18b: THE RELEASE SCREENS -- releases, the calendar, the status page
 * (releases.js, calendar.js, status.js). Existing tokens only, and the
 * terminal's own controls: a filter is the segmented control (.seg), a
 * table sits in its .table-wrap. What is new is small:
 *   .filters      the row of filters over a table, a search box among them
 *   .chip         a short label that IS its words ("US", "Estimated", "Seen
 *                 late"); the class only repeats them
 *   tr.day-row    a day (or a group) as a row inside the ONE table, so the
 *                 columns line up across days -- one table per day did not
 *                 (the owner's screenshot 11) -- sticking under the header
 *                 row while its rows scroll
 *   tr.member     a release's other series, indented under it
 *   tr.now-row    "Now" in today's group on the calendar
 * =================================================================== */
.filters { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin: 4px 0 10px; }
.filters .filter-search {
  width: min(220px, 100%); padding: 4px 9px; font: 13px/1.3 var(--sans);
  background: var(--field); color: var(--ink); border: 1px solid var(--border-2); border-radius: 6px;
}
.filters .filter-search::placeholder { color: var(--placeholder); }
.filters .filter-search:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring); }
.filters[hidden] { display: none; }

.chip {
  display: inline-block; padding: 0 6px; border: 1px solid var(--border-2); border-radius: 4px;
  font-size: 11px; font-weight: 600; line-height: 18px; color: var(--ink-2); white-space: nowrap;
}
.chip.country { min-width: 28px; text-align: center; color: var(--ink); }
.chip.high { color: var(--ink); }
/* "Seen late" and a date nobody announced: set apart by their words, the
 * border only echoing them. */
.chip.seen-late { color: var(--warn); border-color: currentColor; }
.chip.firm.estimated { font-style: italic; border-style: dashed; }
.chip.firm.date_only { border-style: dashed; }
.chip.firm.announced { color: var(--ink); }
/* The lock's chip in a row of chips (the calendar's notes and its legend,
 * beside "Postponed" or "Estimated"): the same box as .chip, keeping its own
 * dashed border and --key (the lock rules above, phase 18b integration). */
.grid-table .lock-chip, .legend-list .lock-chip {
  display: inline-block; padding: 0 6px; font-size: 11px; line-height: 18px; border-radius: 4px;
}

/* The day (or group) row. It sticks below the header row -- whose height is
 * one line of 11.5 px at 1.45, its padding and its rule, about 29 px -- in a
 * .table-wrap.tall, which is the scroller. */
.grid-table tr.day-row th {
  top: 29px; z-index: 1; padding: 10px 10px 5px; background: var(--surface);
  font-size: 12.5px; font-weight: 600; color: var(--ink); text-align: left;
}
.grid-table tr.day-row th .day-name { margin-right: 8px; }
.grid-table tr.day-row th .day-name + .day-date { color: var(--muted); font-weight: 400; }
.grid-table tr.day-row th .day-extra { margin-left: 8px; color: var(--muted); font-weight: 400; }

.rel-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 16px; padding: 8px 14px; }
.rel-head p, .rel-head .window { margin: 0; }
.rel-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 6px; }
.rel-title > button.linkish, .rel-title > span:first-child { font-weight: 600; text-align: left; }
.grid-table td.release { min-width: 16ch; }
.grid-table td.release .meta { margin-top: 1px; }
.grid-table td.actual b { font-weight: 600; }
.grid-table td.num .meta { font-weight: 400; }
.grid-table tr.member td { border-top-style: dashed; }
.grid-table tr.member td.release { padding-left: 22px; }
button.more {
  width: auto; margin: 3px 0 0; padding: 0; border: 0; border-radius: 0; background: none;
  color: var(--accent); font: 500 12px/1.3 var(--sans); cursor: pointer;
}
button.more:hover, button.more:focus-visible { text-decoration: underline; filter: none; }
.history-slot:empty { display: none; }
.history-slot { margin: 0 0 12px; }
.history-slot h2.section { margin-top: 4px; }

/* Phase 17: the page is served under `style-src 'self'`, which refuses a style
 * ATTRIBUTE as well as a <style> element -- so what the shell once set inline
 * lives here (api/tests/test_serve_http.py fails a client file that sets one). */
.rail .foot .notice { margin-top: 8px; font-size: 12px; }
.rail .foot .signout { margin-top: 10px; }
.flush { margin: 0; }
.flush-bottom { margin-bottom: 0; }
.fine.tight { border: 0; padding-top: 0; margin-top: 2px; }
h2.sub.gallery-head { margin-top: 26px; }
h1.gallery-section { margin-top: 34px; }

/* ============================================================ phase 18
 * THE TERMINAL: a command line, the function-key bar and the panes. */

/* Read by screen readers, not drawn: the one live region for every pane's
 * connection (terminal.js). */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* THE TOP BAR: the command line, then the clocks, on ONE line. The bar is
 * a size container and the clocks give way as it narrows -- the day first,
 * then London and Tokyo, then all four -- so a clock is never cut in half
 * at the window's edge ("Tokyo 03:17 Wed 30 Se", the owner's screenshot 6). */
.term-bar {
  display: flex; flex-wrap: nowrap; align-items: flex-start; gap: 8px 16px; margin-bottom: 6px;
  container-type: inline-size; container-name: bar;
  /* A size container is a stacking context, painted in tree order: without
   * a z-index of its own the panes after it would paint over the command
   * line's suggestions. */
  position: relative; z-index: 10;
}

/* The command line. The global label and button rules are for the sign-in
 * forms (field labels, full-width buttons); the terminal's bar resets both. */
.cmdline { flex: 1 1 320px; min-width: 0; display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 2px 10px; }
.cmdline .cmd-label {
  display: inline; margin: 0; text-transform: none; letter-spacing: 0;
  font: 600 12px var(--sans); color: var(--muted);
}
.cmd-box { position: relative; min-width: 0; }
.cmdline input.cmd-input {
  width: 100%; padding: 7px 44px 7px 10px; font: 13px/1.2 var(--mono);
  background: var(--field); color: var(--ink); border: 1px solid var(--border-2); border-radius: 7px;
}
.cmdline input.cmd-input::placeholder { color: var(--placeholder); }
.cmdline input.cmd-input[aria-invalid="true"] { border-color: var(--bad-line); }
/* "⌘K" inside the field's right edge: how to get back here from anywhere. */
.cmd-box .cmd-kbd { position: absolute; right: 8px; top: 7px; pointer-events: none; }
.cmd-list {
  position: absolute; z-index: 7; left: 0; right: 0; top: calc(100% + 4px);
  list-style: none; margin: 0; padding: 4px; max-height: 280px; overflow-y: auto;
  background: var(--raised); border: 1px solid var(--border-2); border-radius: 8px; box-shadow: var(--shadow);
}
.cmd-list[hidden] { display: none; }
.cmd-opt { padding: 6px 9px; border-radius: 5px; font: 13px var(--mono); color: var(--ink-2); cursor: pointer; }
.cmd-opt:hover { background: var(--hover); color: var(--ink); }
/* The highlighted suggestion: a bar AND a background, not colour alone. */
.cmd-opt[aria-selected="true"] { background: var(--accent-wash); color: var(--ink); box-shadow: inset 3px 0 0 var(--accent); }
.cmd-msg { grid-column: 2; margin: 0; min-height: 1em; font-size: 12px; color: var(--muted); }
.cmd-msg[data-error="true"] { color: var(--bad-ink); }

.clocks { display: flex; flex-wrap: nowrap; gap: 12px; white-space: nowrap; flex: none;
          font-size: 12px; color: var(--ink-2); padding-top: 8px; }
.clock-city { color: var(--muted); }
.clock-time { color: var(--ink); font-family: var(--mono); }
.clock-day { color: var(--muted); }
@container bar (max-width: 900px) { .clock-day { display: none; } }
@container bar (max-width: 720px) { .clock[data-rank="3"], .clock[data-rank="4"] { display: none; } }
@container bar (max-width: 520px) { .clocks { display: none; } }

/* THE FUNCTION-KEY STRIP: flat, the key as a keycap in --key, the words in
 * sentence case beside it. */
kbd {
  font: 600 11px/1.5 var(--mono); color: var(--key);
  background: var(--raised); border: 1px solid var(--border-2); border-bottom-width: 2px;
  border-radius: 4px; padding: 0 5px;
}
.fkeys { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; margin: 0 0 8px; }
.fkeys button {
  width: auto; padding: 3px 8px 3px 4px; font: 500 12px var(--sans); border-radius: 6px;
  background: transparent; color: var(--ink-2); border: 1px solid transparent;
}
.fkeys button:hover { color: var(--ink); background: var(--hover); filter: none; }
.fkeys kbd { margin-right: 6px; }
/* The layouts: one segmented control, the pressed one filled AND underlined. */
.fkeys .bar-group { display: inline-flex; margin-left: auto; border: 1px solid var(--border-2); border-radius: 6px; }
.fkeys .bar-group button { padding: 3px 10px; border: 0; border-radius: 0; }
.fkeys .bar-group button + button { border-left: 1px solid var(--border-2); }
.fkeys button[aria-pressed="true"] { color: var(--ink); background: var(--accent-wash); box-shadow: inset 0 -2px 0 var(--accent); }
/* The workspaces: a quiet button beside the layouts, its menu (menu.js) opening
 * under it. */
.fkeys .bar-menu-host { position: relative; }
.fkeys button.workspaces { border: 1px solid var(--border-2); padding: 3px 10px; }
.pop-menu.bar-menu { top: calc(100% + 4px); right: 0; min-width: 160px; }
/* The theme toggle: a sun or a moon, named in words for a screen reader. */
.fkeys button.theme-toggle { display: grid; place-items: center; width: 30px; height: 28px; padding: 0; border: 1px solid var(--border-2); }
.theme-toggle .icon { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.theme-toggle .icon .fill { fill: currentColor; stroke: none; }

/* THE PANES. Each is a size container, so a screen responds to the pane it
 * is in (the @container rules above), not to the window. */
.panes { display: grid; gap: 8px; grid-template-columns: 1fr; align-items: start; }
.panes[data-layout="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: minmax(0, 1fr); }
.panes[data-layout="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); }
.pane { container-type: inline-size; container-name: pane; min-width: 0; position: relative; }
/* ONE WINDOW, SEVERAL PANES. Two or four panes fill the window; a 2x2 grid two
 * windows tall is not a grid anyone reads at once. One pane scrolls with the
 * page, as the single screen always did.
 *
 * EACH PANE IS A REAL SCROLL REGION (phase 20): its header -- the number, the
 * title, "active" and the screen picker -- is fixed, and its BODY scrolls on
 * its own, so nothing is cut off without a way to reach it. (The owner,
 * 2026-10-07, of four panes: "tiles cut off".) The body is a focusable
 * region -- the terminal gives it tabindex 0 in these layouts -- so a keyboard
 * scrolls it, and it shows a soft edge where there is more above or below,
 * because a scrollbar that only appears while scrolling does not say so. */
.main.terminal:has(> .panes:not([data-layout="1"])) {
  height: 100dvh; display: flex; flex-direction: column; padding-bottom: 12px;
}
.panes:not([data-layout="1"]) { flex: 1 1 auto; min-height: 0; align-items: stretch; }
.panes:not([data-layout="1"]) .pane {
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
  border: 1px solid var(--border); border-radius: 8px; padding: 0;
}
.panes[data-layout="1"] .pane-head { display: none; }
.pane-head { display: flex; align-items: center; gap: 8px; min-height: 32px; font-size: 12px; color: var(--muted); }
.panes:not([data-layout="1"]) .pane-head {
  flex: none; padding: 0 6px 0 12px; background: var(--page); border-bottom: 1px solid var(--border);
}
.panes:not([data-layout="1"]) .pane-body {
  flex: 1 1 auto; min-height: 0; overflow: auto; padding: 8px 12px 12px;
  scrollbar-width: thin; scrollbar-color: var(--border-2) transparent;
  background:
    linear-gradient(var(--page) 30%, transparent) top / 100% 24px no-repeat local,
    linear-gradient(transparent, var(--page) 70%) bottom / 100% 24px no-repeat local,
    radial-gradient(farthest-side at 50% 0, var(--border-2), transparent) top / 100% 8px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, var(--border-2), transparent) bottom / 100% 8px no-repeat scroll;
}
.panes:not([data-layout="1"]) .pane-body:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pane-no {
  display: grid; place-items: center; min-width: 18px; height: 18px; padding: 0 3px;
  border: 1px solid var(--border-2); border-radius: 4px; font-size: 11px; font-weight: 600; color: var(--ink-2);
}
.pane-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
              font-size: 13px; font-weight: 600; color: var(--ink); }
.pane-mark { font-size: 11px; font-weight: 600; color: var(--accent); }
/* THE SCREEN PICKER: a "▾" in the pane's header, a small quiet button -- the
 * global `button` rules are the sign-in card's -- opening the menu below. */
button.pane-pick {
  width: auto; flex: none; padding: 2px 8px; border-radius: 6px; font: 500 13px/1.2 var(--sans);
  background: transparent; color: var(--ink-2); border: 1px solid var(--border-2);
}
button.pane-pick::after { content: "▾"; }
button.pane-pick:hover { color: var(--ink); background: var(--hover); filter: none; }
button.pane-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
button.pane-pick[aria-expanded="true"] { color: var(--ink); background: var(--accent-wash); }
/* THE POPUP MENU (menu.js): a pane's screens, and the workspaces. It opens
 * over what is below it, inside its pane (or its bar), never clipped by the
 * body that scrolls, because it is the pane's own child and not the body's. */
.pop-menu {
  position: absolute; z-index: 12; top: 34px; right: 6px; display: grid; gap: 1px;
  min-width: 190px; max-height: min(70vh, 460px); overflow-y: auto; padding: 4px;
  background: var(--raised); border: 1px solid var(--border-2); border-radius: 8px; box-shadow: var(--shadow);
}
/* Inside a pane the menu may not be taller than the pane it opens in (the pane
 * clips what overflows it): it scrolls within itself instead. */
.pane > .pop-menu { max-height: calc(100% - 42px); }
.pop-menu button.menu-item {
  display: block; width: 100%; text-align: left; padding: 6px 10px; border: 0; border-radius: 5px;
  background: transparent; color: var(--ink-2); font: 13px/1.3 var(--sans); cursor: pointer;
}
.pop-menu button.menu-item.sub { padding-left: 24px; font-size: 12.5px; }
.pop-menu button.menu-item:hover, .pop-menu button.menu-item:focus-visible { background: var(--hover); color: var(--ink); filter: none; }
.pop-menu button.menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The screen on show: a bar AND a background, not colour alone, and it is
 * said to a screen reader (aria-current). */
.pop-menu button.menu-item[aria-current="true"] { background: var(--accent-wash); color: var(--ink); box-shadow: inset 3px 0 0 var(--accent); }
/* THE ACTIVE PANE: an outline drawn INSIDE its edge, so nothing moves when
 * it changes -- the 1 px border becoming 2 px shifted every line of the pane
 * by a pixel -- AND the word "active" in its head. */
.panes:not([data-layout="1"]) .pane[data-active="true"] { outline: 2px solid var(--accent); outline-offset: -2px; }
/* ONE TITLE PER PANE. Inside a titled pane the screen's own heading says
 * what the pane's head already says, so it is read, not drawn: it stays the
 * first heading, where the focus goes when the screen opens, and the pane's
 * title is ringed when it has it. One pane has no head, and draws the
 * heading as the screen's title. */
.panes:not([data-layout="1"]) .pane-body > * > h1:first-child {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.panes:not([data-layout="1"]) .pane:has(.pane-body > * > h1:first-child:focus-visible) .pane-title {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px;
}
/* PHASE 24 (W24-B): an Equities page keeps its heading one level down, in the body that swaps while the page loads (marketpages.js);
 * the same rule: hidden inside a titled pane, ringed on the pane's title when it has the focus. */
.panes:not([data-layout="1"]) .pane-body > .market-page > .market-page-body > h1:first-child,
.panes:not([data-layout="1"]) .pane-body > .market-page > .market-page-body > * > h1:first-child {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.panes:not([data-layout="1"]) .pane:has(.pane-body > .market-page > .market-page-body > h1:first-child:focus-visible) .pane-title,
.panes:not([data-layout="1"]) .pane:has(.pane-body > .market-page > .market-page-body > * > h1:first-child:focus-visible) .pane-title {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px;
}
/* The heading a screen hands the focus to when it opens: ringed for the
 * KEYBOARD (F6, P2, Esc), not after every mouse click on the rail. */
.pane h1[tabindex="-1"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* THE LAUNCHER: what an empty pane offers instead of a dead end. */
.launcher { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr)); gap: 8px; margin: 8px 0 0; }
.launcher button.launch {
  display: grid; gap: 2px; width: auto; text-align: left;
  padding: 10px 12px; border-radius: 8px;
  background: var(--surface); color: var(--ink); border: 1px solid var(--border);
  font: 400 13px/1.35 var(--sans);
}
.launcher button.launch:hover { background: var(--raised); border-color: var(--accent-line); filter: none; }
.launch-title { font-weight: 600; }
.launch-sum { color: var(--muted); font-size: 12px; }
.launcher-foot { margin-top: 12px; }

/* A PHONE shows the active pane alone; the rest are released and kept in
 * the address (terminal.js NARROW, the same width). */
@media (max-width: 760px) {
  .main.terminal:has(> .panes:not([data-layout="1"])) { height: auto; display: block; }
  .panes[data-layout] { grid-template-columns: 1fr; grid-template-rows: none; }
  .pane[data-shown="false"] { display: none; }
  .panes:not([data-layout="1"]) .pane { overflow: visible; }
  /* The page scrolls on a phone: no pane body is a scroll region of its own. */
  .panes:not([data-layout="1"]) .pane-body { overflow: visible; background: none; }
  .fkeys .bar-group { margin-left: 0; }
}

/* Phase 18: the price chart (pricechart.js). The library sizes its canvas to
 * this box (autoSize), so the box needs a DEFINITE height -- from the
 * viewport, so a short pane still shows its axis; the probe is the hidden SVG
 * the chart's colours are read from, with the same `.chart .line-*` and
 * `.stroke-*` rules an SVG chart is stroked with -- nothing here is a second
 * palette. The attribution is the licence's notice and link, in text. */
.pricechart { position: relative; height: clamp(200px, 40vh, 360px); margin: 10px 0 6px; }
.pricechart-probe { position: absolute; width: 0; height: 0; overflow: hidden; pointer-events: none; }
.pricechart-card .span { margin: 0; }
.attribution { font-size: 11px; color: var(--muted); margin: 8px 0 0; }
.attribution a { color: var(--muted); }
.gp-picks { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 6px; }
button.older { margin: 0 0 10px; }

/* ===================================================================
 * PHASE 18b: THE PRICE CHART'S TOOLBAR, READOUT AND MARKS
 * (pricechart.js, charttools.js; the tape and GP). Existing tokens and this
 * phase's chart tokens only; the card around the chart is the shared one
 * (card.js: header, key reading, footnote), and its window note is
 * capNote's, styled with every other card's. The colours the canvas draws
 * with are read off the hidden probe through the `.chart .pc-*` rules below
 * -- the library cannot follow a var(), so these rules ARE its palette, one
 * per mark.
 * =================================================================== */
.chart .pc-up    { stroke: var(--up); }
.chart .pc-down  { stroke: var(--down); }
.chart .pc-sma1  { stroke: var(--sma-1); }
.chart .pc-sma2  { stroke: var(--sma-2); }
.chart .pc-sma3  { stroke: var(--sma-3); }
.chart .pc-ema1  { stroke: var(--ema-1); }
.chart .pc-ema2  { stroke: var(--ema-2); }
.chart .pc-ema3  { stroke: var(--ema-3); }
.chart .pc-rsi   { stroke: var(--rsi); }
.chart .pc-vol   { stroke: var(--volume); }

/* Panes below the price take a share of a taller box, so the price keeps
 * its height when RSI or volume is on. */
.pricechart.panes-1 { height: clamp(260px, 52vh, 460px); }
.pricechart.panes-2 { height: clamp(320px, 62vh, 540px); }

.pc-tools { margin: 10px 0 4px; }
.pc-toolbar { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; }
/* EACH GROUP IS ONE SEGMENTED CONTROL, the terminal's own (.seg: the board's
 * view switch, the layout picker): the controls' font, one shared border,
 * rounded at the group's two ends, the pressed button filled AND underlined
 * -- said by aria-pressed, never by colour alone. The width is reset: the
 * global `button { width: 100% }` is the sign-in card's. */
.pc-group { display: inline-flex; flex-wrap: wrap; row-gap: 4px; align-items: center; }
.pc-label { font-size: 11.5px; font-weight: 600; color: var(--ink-3); margin-right: 6px; }
.pc-btn {
  width: auto; min-height: 26px; margin: 0; padding: 3px 9px;
  font: 500 12px var(--sans); line-height: 1; font-variant-numeric: tabular-nums;
  color: var(--ink-2); background: transparent; border: 1px solid var(--border-2); border-radius: 0; cursor: pointer;
}
.pc-btn + .pc-btn { border-left-width: 0; }
.pc-label + .pc-btn { border-radius: 6px 0 0 6px; }
.pc-btn:last-of-type { border-radius: 0 6px 6px 0; }
.pc-label + .pc-btn:last-of-type { border-radius: 6px; }
.pc-btn:hover:not([disabled]) { color: var(--ink); background: var(--hover); filter: none; }
.pc-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pc-btn[aria-pressed="true"] { color: var(--ink); background: var(--accent-wash); box-shadow: inset 0 -2px 0 var(--accent); }
.pc-btn[disabled] { cursor: not-allowed; }   /* dimmed by button[disabled], the one opacity allowed */
.pc-btn[data-weekly="true"] { color: var(--muted); }   /* W24b E5: not drawn at this interval, but a press builds the weekly bars: muted, still a button */
/* A study's toggle carries its line's colour as a swatch before its words
 * (the words name it; the swatch only matches it to its line). */
.pc-btn.pc-tone::before {
  content: ""; display: inline-block; width: 8px; height: 8px; margin-right: 5px;
  border-radius: 2px; vertical-align: 0;
}
.pc-btn.pc-sma1::before  { background: var(--sma-1); }
.pc-btn.pc-sma2::before  { background: var(--sma-2); }
.pc-btn.pc-sma3::before  { background: var(--sma-3); }
.pc-btn.pc-ema1::before  { background: var(--ema-1); }
.pc-btn.pc-ema2::before  { background: var(--ema-2); }
.pc-btn.pc-ema3::before  { background: var(--ema-3); }
.pc-btn.pc-rsi::before   { background: var(--rsi); }
.pc-btn.pc-vol::before   { background: var(--volume); }
.pc-off { font-size: 11px; color: var(--ink-3); margin-left: 6px; }
/* PHASE 22 (A2): the averages' periods -- a disclosure beside the Indicators, three small number boxes a kind. The global
 * `input` rule is the sign-in card's (full width), so the width is reset here. */
.pc-periods { flex-basis: 100%; }
.pc-periods summary { cursor: pointer; font-size: 12px; color: var(--ink-2); width: max-content; }
.pc-periods summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pc-periods-body { margin: 6px 0 2px; }
.pc-period-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 6px 0; }
.pc-period-row .pc-label { min-width: 120px; margin-right: 0; }
.pc-period { width: 68px; min-height: 26px; padding: 3px 6px; font: 500 12px var(--sans); font-variant-numeric: tabular-nums; }
.pc-period:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.pc-period-reset { width: auto; margin: 4px 0 0; min-height: 26px; padding: 3px 9px; font: 500 12px var(--sans); }
.pc-period-note:empty { display: none; }
.pc-note { font-size: 12px; color: var(--ink-3); margin: 4px 0 0; }
.pc-notes .pc-note { margin: 2px 0 0; }
/* THE READOUT WRAPS AT EVERY WIDTH (review of phase 18b, P18B-CHART-10): it
 * is the canvas's text mirror, about 150 characters with five studies on,
 * and nowrap with an ellipsis cut the RSI and the later studies off in any
 * pane wider than 520px. It sits UNDER the chart box (closure check of 18b,
 * charts NEW-1): above it, a wrapped second line moved the canvas under the
 * pointer; below, it moves only the lines under it. The minimum height holds
 * its line when it is empty. */
.pc-readout {
  margin: 6px 0 0; font-size: 12px; color: var(--ink-2); min-height: 1.4em;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.pricechart-empty { margin: 10px 0; }
@container pane (max-width: 520px) {
  .pc-label { display: none; }
}

/* ======================================================= THE WORKBOOK
 * Phase 20: a dashboard of tabs (dashboard.js renderDashboard, workbook.js),
 * the owner's US macro page in the terminal's two themes. Every colour is a
 * token; nothing is dimmed with opacity; a number a colour repeats is
 * printed (its sign, its glyph), so no rule here carries a meaning alone. */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
           clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.panel[hidden] { display: none; }
.panel.bare { border-top: 0; padding-top: 0; }

/* The tab strip: pills, one in the tab order, the selected one marked by its
 * underline and by aria-selected. A locked tab says so beside its name. */
.workbook { min-width: 0; }
.tabbar { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 14px; }
.tabbar .tab { width: auto; padding: 6px 14px; font: 600 13px var(--sans); color: var(--ink-2); background: transparent;
               border: 1px solid var(--border-2); border-radius: 999px; cursor: pointer; }
.tabbar .tab:hover { color: var(--ink); background: var(--hover); filter: none; }
.tabbar .tab[aria-selected="true"] { color: var(--ink); background: var(--accent-wash); border-color: var(--accent-line);
                                     box-shadow: inset 0 -2px 0 var(--accent); }
.tabbar .tab-note { margin-left: 6px; font: 500 11px var(--sans); color: var(--ink-3); }
.dashboard-view .panels.workbook-panels { grid-template-columns: minmax(0, 1fr); }
@container pane (min-width: 860px) {
  .dashboard-view .panels.workbook-panels { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dashboard-view .workbook-panels > .panel[data-wide="true"],
  .dashboard-view .workbook-panels > .card.locked,
  /* PHASE 25 (W25-C, review R3B M1): the Gold page's TradingView card (tvchart.js tvViewTiles) is a chart: the whole row, like every Gold panel */
  .dashboard-view .workbook-panels > .tv-view-tile { grid-column: 1 / -1; }
}

/* The regime header: the badge, the confirmed-through line, the banners. */
.regime-line { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 0 0 8px; }
.regime-badge { padding: 4px 14px; font: 700 13px var(--sans); color: var(--ink); border: 2px solid var(--border-2);
                border-radius: 999px; }
.regime-badge.q1 { border-color: var(--ma-20); }
.regime-badge.q2 { border-color: var(--up); }
.regime-badge.q3 { border-color: var(--key); }
.regime-badge.q4 { border-color: var(--down); }
.regime-header .notice.banner { margin: 8px 0 0; }
.regime-header .notice.banner p { margin: 0 0 6px; line-height: 1.55; }
.regime-header .notice.banner p:last-child { margin-bottom: 0; }
.notice.info { color: var(--ink); border-color: var(--accent-line); background: var(--accent-wash); }

/* The score row. */
.score-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; }
.score-card { padding: 10px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; min-width: 0; }
.score-label { font: 600 12px var(--sans); color: var(--muted); }
.score-value { font: 700 24px var(--sans); color: var(--ink); font-variant-numeric: tabular-nums; }
.score-value.neg { color: var(--down); }
.score-delta { font: 700 12px var(--sans); font-variant-numeric: tabular-nums; }
.score-delta.good { color: var(--up); }
.score-delta.bad { color: var(--down); }
.score-delta.flat { color: var(--muted); }
.score-delta .meta { font-weight: 400; }
.score-asof { margin-top: 2px; }
.scorecards-body .how-to-read { margin: 10px 0 0; }

/* Charts: SVG drawn at the width they are shown at (fit.js). */
.wb-chart { min-width: 0; }
.wb-caption p { margin: 6px 0 0; line-height: 1.5; }
.wb-caption .warn { color: var(--warn); }
.chart.wb .axis.zero { stroke: var(--ink-3); }
.chart.wb .wb-band { fill: var(--band); }
.chart.wb .wb-line { fill: none; stroke-width: 1.6; stroke-linejoin: round; }
.chart.wb .wb-line.s0 { stroke: var(--accent); }
.chart.wb .wb-line.s1 { stroke: var(--key); }
.chart.wb .wb-line.s2 { stroke: var(--up); }
.chart.wb .wb-line.s3 { stroke: var(--ma-50); }
.chart.wb .wb-line.s4 { stroke: var(--ma-20); }
.chart.wb .wb-line.s5 { stroke: var(--down); }
.chart.wb .wb-line.s6 { stroke: var(--ma-200); }
/* A reference line is a thin muted rule with its label in words; no dash, which
 * is a mark this stylesheet keeps for nothing (test_dom_one_axis.py). */
.chart.wb .wb-ref { stroke: var(--muted); stroke-width: 1; }
.chart.wb .ref-label { fill: var(--muted); }
.chart.wb .wb-area { fill: var(--down-wash); }
.chart.wb .quad-fill.q1 { fill: var(--q1-wash); }
.chart.wb .quad-fill.q2 { fill: var(--up-wash); }
.chart.wb .quad-fill.q3 { fill: var(--q3-wash); }
.chart.wb .quad-fill.q4 { fill: var(--down-wash); }
.chart.wb .quad-name { fill: var(--ink-3); font-weight: 700; }
.chart.wb .wb-trail { fill: none; stroke: var(--ink-3); stroke-width: 1.2; }
.chart.wb .dot.q1, .chart.wb .strip-run.q1 { fill: var(--ma-20); }
.chart.wb .dot.q2, .chart.wb .strip-run.q2 { fill: var(--up); }
.chart.wb .dot.q3, .chart.wb .strip-run.q3 { fill: var(--key); }
.chart.wb .dot.q4, .chart.wb .strip-run.q4 { fill: var(--down); }
/* A dot that reads 0 on an axis has no quadrant: neutral, never a guessed colour. */
.chart.wb .dot.q0 { fill: var(--muted); }
.chart.wb .ring { fill: none; stroke-width: 2.5; }
.chart.wb .ring.now { stroke: var(--ink); }
.chart.wb .ring.forming { stroke: var(--muted); stroke-width: 2; }
.chart.wb .ring-label { font-weight: 700; }
.chart.wb .ring-label.now { fill: var(--ink); }
.chart.wb .ring-label.forming { fill: var(--muted); }
.chart.wb .bar.pos { fill: var(--accent); }
.chart.wb .bar.neg { fill: var(--down); }
.chart.wb .row-label { fill: var(--ink-2); font-size: 11px; }
.chart.wb .row-value { fill: var(--ink); }
.wb-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 6px 0 0; padding: 0; list-style: none;
             font-size: 12px; color: var(--ink-2); }
.wb-key { display: inline-flex; align-items: center; gap: 6px; }
.wb-key-value { color: var(--muted); font-variant-numeric: tabular-nums; }
.swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
.swatch.s0 { background: var(--accent); }
.swatch.s1 { background: var(--key); }
.swatch.s2 { background: var(--up); }
.swatch.s3 { background: var(--ma-50); }
.swatch.s4 { background: var(--ma-20); }
.swatch.s5 { background: var(--down); }
.swatch.s6 { background: var(--ma-200); }
.swatch.q1 { background: var(--ma-20); }
.swatch.q2 { background: var(--up); }
.swatch.q3 { background: var(--key); }
.swatch.q4 { background: var(--down); }

/* PHASE 22 (A1): THE HOVER READOUT -- readings ON a dashboard chart (readout.js). The owner, 2026-10-09: "there should be
 * readings on the chart". The guide, the dots and the plate are UI, not data: tokens only, solid, nothing dashed and nothing
 * faded (api/tests/test_dom_one_axis.py). The chart box is the tab stop (a focus ring, drawn); the SVG lets a vertical swipe
 * scroll the page and takes a horizontal one as the reading; the group is click-through so the pointer never flickers
 * between the chart and its own plate. */
.chart-fit[tabindex] { border-radius: 6px; }
.chart-fit[tabindex]:focus { outline: none; }
.chart-fit[tabindex]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chart.wb { touch-action: pan-y; }
.chart.wb .wb-hover { pointer-events: none; }
.chart.wb .wb-guide { stroke: var(--ink-3); stroke-width: 1; }
.chart.wb .wb-guide-band { fill: var(--band); }
.chart.wb .wb-hover-dot { stroke: var(--surface); stroke-width: 1.5; fill: var(--ink); }
.chart.wb .wb-hover-dot.s0 { fill: var(--accent); }
.chart.wb .wb-hover-dot.s1 { fill: var(--key); }
.chart.wb .wb-hover-dot.s2 { fill: var(--up); }
.chart.wb .wb-hover-dot.s3 { fill: var(--ma-50); }
.chart.wb .wb-hover-dot.s4 { fill: var(--ma-20); }
.chart.wb .wb-hover-dot.s5 { fill: var(--down); }
.chart.wb .wb-hover-dot.s6 { fill: var(--ma-200); }
.chart.wb .wb-plate { fill: var(--surface); stroke: var(--border-2); stroke-width: 1; }
.chart.wb .wb-readout-head, .chart.wb .wb-readout-name, .chart.wb .wb-readout-val { font-size: 11px; }
.chart.wb .wb-readout-head { fill: var(--ink); font-weight: 700; }
.chart.wb .wb-readout-name { fill: var(--ink-2); }
.chart.wb .wb-readout-val { fill: var(--ink); font-variant-numeric: tabular-nums; }
.chart.wb .wb-readout-swatch { fill: var(--ink-3); }
.chart.wb .wb-readout-swatch.s0 { fill: var(--accent); }
.chart.wb .wb-readout-swatch.s1 { fill: var(--key); }
.chart.wb .wb-readout-swatch.s2 { fill: var(--up); }
.chart.wb .wb-readout-swatch.s3 { fill: var(--ma-50); }
.chart.wb .wb-readout-swatch.s4 { fill: var(--ma-20); }
.chart.wb .wb-readout-swatch.s5 { fill: var(--down); }
.chart.wb .wb-readout-swatch.s6 { fill: var(--ma-200); }
.readings { margin-top: 8px; }
.readings summary { cursor: pointer; font-size: 12px; color: var(--ink-2); }
.parts-table tr.total td { border-top: 2px solid var(--border-2); font-weight: 700; }
.wb-note .note { font-size: 12px; color: var(--ink-2); margin: 2px 0; }
/* ------------------------------------------------- phase 20: the Markets home */
/* Four widgets, a small "Compare asset classes" line above them, and -- only when the reader starts a comparison -- one chart. A PANE,
 * not the window, decides how many columns there are room for (the @container rules above): one column on a narrow pane, two, then
 * three. The widget whose class is being compared takes a whole row (two columns on a wide pane) and the rest follow IN ORDER: no
 * `dense` packing (review of phase 23, R23 F5), which would back-fill a later widget beside an earlier one and put the visual order
 * out of step with the tab order. The price is a gap beside the widget above the one that grew. */
.seg[hidden] { display: none; }
.markets-home { display: grid; gap: 8px; grid-template-columns: minmax(0, 1fr); align-items: start; grid-auto-flow: row; }
.markets-home .card { margin: 0; min-width: 0; }
.markets-home > .notice { grid-column: 1 / -1; order: -3; margin: 0; }
.markets-home > .notice[hidden] { display: none; }
.markets-home[hidden] { display: none; }
.markets-home > .compare-bar { grid-column: 1 / -1; order: -2; margin: 0; font-size: 13px; }
.markets-home > .compare-card { grid-column: 1 / -1; order: -1; }
.markets-home > .compare-card[hidden] { display: none; }
@container pane (min-width: 720px) {
  .markets-home { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .markets-home > .widget[data-comparing="true"] { grid-column: 1 / -1; }
}
@container pane (min-width: 1240px) {
  .markets-home { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .markets-home > .widget[data-comparing="true"] { grid-column: span 2; }
}
/* THE SMALL LINE (the owner, 2026-10-10: "a small line somewhere which mentions 'compare asset classes'"): plain words that are a
 * button -- a link's look, never the big filled button, and never a banner. */
.compare-line { width: auto; background: none; border: 0; padding: 2px 0; margin: 0; font: 600 13px var(--sans); color: var(--accent);
                cursor: pointer; text-decoration: underline; text-decoration-color: var(--accent-line); text-underline-offset: 3px; }
.compare-line:hover { text-decoration-color: currentColor; filter: none; }
.compare-line:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }
.compare-hint[hidden] { display: none; }
.widget .card-head h2 { font: 600 15px var(--sans); margin: 0; }
.widget .card-head .widget-board { padding: 3px 10px; font-size: 12px; flex: none; }
.widget-compare { padding: 8px 12px 6px; border-bottom: 1px solid var(--border); }
.widget-compare[hidden] { display: none; }
.widget-table { width: 100%; }
.widget-table th, .widget-table td { padding: 5px 6px; }
.widget-table th.tick, .widget-table td.tick { width: 1%; padding: 0; }
.widget-table th.tick[hidden], .widget-table td.tick[hidden] { display: none; }
/* The tick box is 15px, below a touch target: the label around it fills the cell and is the target (24px at
 * least), and it is not focusable, so the tab order is the box's own. */
.widget-table .tick-target { display: flex; align-items: center; justify-content: center; min-width: 28px; min-height: 28px; cursor: pointer; }
.widget-table input[type="checkbox"] { width: 15px; height: 15px; margin: 0; cursor: pointer; accent-color: var(--accent); }
.widget-table .group-row th { text-align: left; font: 600 11px var(--sans); color: var(--muted); background: var(--head-bg); padding-top: 8px; }
.widget-table td.name { font: 500 13px var(--sans); color: var(--ink); min-width: 9ch; }
.widget-table td.last { font-variant-numeric: tabular-nums; color: var(--ink); }
.widget-table .spark-cell { width: 64px; }
.widget-table .spark { display: block; width: 56px; height: 22px; margin: 0; }
.widget-table tr.row-note td { padding: 0 8px 6px 31px; }
.widget-table tr.row-note[hidden] { display: none; }
.widget-table tr.row-note .card-note { margin: 0; font-size: 12px; }
.widget-table .chip.capped { margin-left: 6px; }
.widget-foot { padding: 0 12px 10px; }
.widget-foot:empty { padding: 0; }
.widget-foot .card-note { margin: 6px 0 0; }

/* THE COMPARE PANEL (compare.js): in the cross-asset card at the top, or inside the class's own card. The controls are the terminal's
 * own: the window is the segmented control, the rest quiet buttons. */
.compare-card .pad { padding: 10px 12px 12px; }
.compare .cmp-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 8px; }
.compare .cmp-title { font: 600 15px var(--sans); margin: 0; }
.compare .cmp-head .cmp-close, .compare .cmp-actions button { padding: 3px 10px; font-size: 12px; }
.compare .cmp-controls { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; margin: 0 0 8px; }
.compare .cmp-actions { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.compare .cmp-actions button[hidden] { display: none; }
.np-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; min-height: 26px; }
.np-chip { display: inline-flex; align-items: center; border: 1px solid var(--border-2); border-radius: 999px; font: 500 12px var(--sans);
           color: var(--ink); }
.np-chip .np-key { width: auto; display: inline-flex; align-items: center; gap: 6px; padding: 2px 4px 2px 8px; border: 0; border-radius: 999px;
                   background: none; color: inherit; font: inherit; cursor: pointer; }
.np-chip .np-key:hover { filter: none; background: var(--hover); }
.np-chip .np-key:focus-visible, .np-chip .np-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.np-swatch { width: 10px; height: 10px; border-radius: 2px; background: var(--np-1); border: 2px solid var(--np-1); box-sizing: border-box; }
.np-val { font-variant-numeric: tabular-nums; }
.np-status { color: var(--muted); }
.np-tag { color: var(--muted); font-size: 11px; border: 1px solid var(--border-2); border-radius: 4px; padding: 0 4px; }
/* A hidden line: its swatch is hollow, its name struck through and said as "hidden" -- never dimmed (no rule here fades anything). */
.np-chip.is-hidden .np-swatch { background: transparent; }
.np-chip.is-hidden .np-name { text-decoration: line-through; color: var(--muted); }
.np-x { width: auto; min-width: 0; padding: 0 8px 0 2px; border: 0; background: none; color: var(--ink-2); font: 500 14px var(--sans); cursor: pointer; }
.np-x:hover, .np-x:focus-visible { color: var(--ink); filter: none; }
.np-box { min-height: 120px; }
.np-empty { margin: 0; padding: 24px 0; }
.np-panel + .np-panel { margin-top: 10px; }
.np-panel-title { margin: 0 0 2px; font: 600 12px var(--sans); color: var(--ink-2); }
.np-chart { display: block; width: 100%; }
.np-chart .np-line { fill: none; stroke-width: 1.75; vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke: var(--np-1); }
.np-chart .np-dot { fill: var(--np-1); }
.np-chart .np-leader { fill: none; stroke-width: 1; stroke: var(--np-1); }
.np-chart .np-pill-box { fill: var(--surface); stroke-width: 1.5; stroke: var(--np-1); }
.np-chart .tick.np-pill-text { fill: var(--ink); font-size: 10.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.np-chart .axis.zero { stroke: var(--border-2); }
.np-notes .card-note { margin: 6px 0 0; }
.np-notes .np-foot { margin: 6px 0 0; font-size: 12px; }
.np-swatch.np-1 { background: var(--np-1); border-color: var(--np-1); }
.np-swatch.np-2 { background: var(--np-2); border-color: var(--np-2); }
.np-swatch.np-3 { background: var(--np-3); border-color: var(--np-3); }
.np-swatch.np-4 { background: var(--np-4); border-color: var(--np-4); }
.np-swatch.np-5 { background: var(--np-5); border-color: var(--np-5); }
.np-swatch.np-6 { background: var(--np-6); border-color: var(--np-6); }
.np-swatch.np-7 { background: var(--np-7); border-color: var(--np-7); }
.np-swatch.np-8 { background: var(--np-8); border-color: var(--np-8); }
.np-chip.is-hidden .np-swatch.np-1 { background: transparent; }
.np-chip.is-hidden .np-swatch.np-2 { background: transparent; }
.np-chip.is-hidden .np-swatch.np-3 { background: transparent; }
.np-chip.is-hidden .np-swatch.np-4 { background: transparent; }
.np-chip.is-hidden .np-swatch.np-5 { background: transparent; }
.np-chip.is-hidden .np-swatch.np-6 { background: transparent; }
.np-chip.is-hidden .np-swatch.np-7 { background: transparent; }
.np-chip.is-hidden .np-swatch.np-8 { background: transparent; }
.np-chart .np-line.np-1 { stroke: var(--np-1); }
.np-chart .np-line.np-2 { stroke: var(--np-2); }
.np-chart .np-line.np-3 { stroke: var(--np-3); }
.np-chart .np-line.np-4 { stroke: var(--np-4); }
.np-chart .np-line.np-5 { stroke: var(--np-5); }
.np-chart .np-line.np-6 { stroke: var(--np-6); }
.np-chart .np-line.np-7 { stroke: var(--np-7); }
.np-chart .np-line.np-8 { stroke: var(--np-8); }
.np-chart .np-dot.np-1 { fill: var(--np-1); }
.np-chart .np-dot.np-2 { fill: var(--np-2); }
.np-chart .np-dot.np-3 { fill: var(--np-3); }
.np-chart .np-dot.np-4 { fill: var(--np-4); }
.np-chart .np-dot.np-5 { fill: var(--np-5); }
.np-chart .np-dot.np-6 { fill: var(--np-6); }
.np-chart .np-dot.np-7 { fill: var(--np-7); }
.np-chart .np-dot.np-8 { fill: var(--np-8); }
.np-chart .np-leader.np-1 { stroke: var(--np-1); }
.np-chart .np-leader.np-2 { stroke: var(--np-2); }
.np-chart .np-leader.np-3 { stroke: var(--np-3); }
.np-chart .np-leader.np-4 { stroke: var(--np-4); }
.np-chart .np-leader.np-5 { stroke: var(--np-5); }
.np-chart .np-leader.np-6 { stroke: var(--np-6); }
.np-chart .np-leader.np-7 { stroke: var(--np-7); }
.np-chart .np-leader.np-8 { stroke: var(--np-8); }
.np-chart .np-pill.np-1 .np-pill-box { stroke: var(--np-1); }
.np-chart .np-pill.np-2 .np-pill-box { stroke: var(--np-2); }
.np-chart .np-pill.np-3 .np-pill-box { stroke: var(--np-3); }
.np-chart .np-pill.np-4 .np-pill-box { stroke: var(--np-4); }
.np-chart .np-pill.np-5 .np-pill-box { stroke: var(--np-5); }
.np-chart .np-pill.np-6 .np-pill-box { stroke: var(--np-6); }
.np-chart .np-pill.np-7 .np-pill-box { stroke: var(--np-7); }
.np-chart .np-pill.np-8 .np-pill-box { stroke: var(--np-8); }
/* The readout (readout.js) names a line by its slot: the dot on it and the swatch on the plate. */
.chart.wb .wb-hover-dot.np-1 { fill: var(--np-1); }
.chart.wb .wb-hover-dot.np-2 { fill: var(--np-2); }
.chart.wb .wb-hover-dot.np-3 { fill: var(--np-3); }
.chart.wb .wb-hover-dot.np-4 { fill: var(--np-4); }
.chart.wb .wb-hover-dot.np-5 { fill: var(--np-5); }
.chart.wb .wb-hover-dot.np-6 { fill: var(--np-6); }
.chart.wb .wb-hover-dot.np-7 { fill: var(--np-7); }
.chart.wb .wb-hover-dot.np-8 { fill: var(--np-8); }
.chart.wb .wb-readout-swatch.np-1 { fill: var(--np-1); }
.chart.wb .wb-readout-swatch.np-2 { fill: var(--np-2); }
.chart.wb .wb-readout-swatch.np-3 { fill: var(--np-3); }
.chart.wb .wb-readout-swatch.np-4 { fill: var(--np-4); }
.chart.wb .wb-readout-swatch.np-5 { fill: var(--np-5); }
.chart.wb .wb-readout-swatch.np-6 { fill: var(--np-6); }
.chart.wb .wb-readout-swatch.np-7 { fill: var(--np-7); }
.chart.wb .wb-readout-swatch.np-8 { fill: var(--np-8); }
/* PHASE 20 (E-a): the rates screen's click-through chart and its Curves view.
 * One line per date: the identity is the weight as well as the hue (the owner's rule: no stylesheet
 * rule dashes a chart line, api/tests/test_dom_one_axis.py), so a curve is never told by colour
 * alone and the legend names each date in words. Today is the accent and the thickest. */
.board-chart-bar { display: flex; align-items: center; gap: 12px; margin: 0 0 10px; }
.board-chart-bar .board-back { flex: none; }
.board-chart-bar .board-chart-name { font: 600 15px var(--sans); margin: 0; color: var(--ink); }
.curves-picks { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 8px; }
.curves-status { margin: 4px 0 8px; }
.curves-picture svg.curve-chart { width: 100%; height: auto; max-height: 320px; display: block; }
.curve-chart .cv { fill: none; stroke-width: 1.6; stroke-linecap: round; }
.curve-chart .cv-0 { stroke: var(--accent); stroke-width: 2.8; }
.curve-chart .cv-1 { stroke: var(--up); stroke-width: 2; }
.curve-chart .cv-2 { stroke: var(--down); stroke-width: 1.6; }
.curve-chart .cv-3 { stroke: var(--muted); stroke-width: 1.3; }
.curve-chart .cv-4 { stroke: var(--ink); stroke-width: 1; }
.curve-chart .cv-gap { stroke-width: 0.8; }
.curve-chart .cv-dot { fill: var(--accent); stroke: none; }
.curve-chart .cv-missing { font-style: italic; }
.curves-legend { list-style: none; display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 8px 0; padding: 0; font-size: 13px; }
.curves-legend li { display: flex; align-items: center; gap: 6px; }
.curve-swatch { width: 28px; height: 10px; }
.curve-swatch .cv-0 { stroke: var(--accent); stroke-width: 2.8; }
.curve-swatch .cv-1 { stroke: var(--up); stroke-width: 2; }
.curve-swatch .cv-2 { stroke: var(--down); stroke-width: 1.6; }
.curve-swatch .cv-3 { stroke: var(--muted); stroke-width: 1.3; }
.curve-swatch .cv-4 { stroke: var(--ink); stroke-width: 1; }
.curve-swatch .cv { fill: none; stroke-width: 1.6; }

/* PHASE 20 (worker T): TradingView's own chart, in a frame (tvchart.js, docs/TRADINGVIEW.md). The frame
 * fills the pane and sits on the card surface; the chart-only tiles are the board's own tiles. */
.tv-frame { display: grid; gap: 8px; }
.tv-frame .tv-switch { justify-self: start; }
.tv-frame .tv-pane[hidden] { display: none; }
.tv-frame iframe.tv-chart { display: block; width: 100%; height: 560px; border: 0; background: var(--surface); }
.tv-frame .tv-credit { margin: 0; }
.tv-frame .tv-note { margin: 0 0 8px; }   /* W24b E5: the line under the tab row, above the widget */
@media (max-width: 640px) { .tv-frame iframe.tv-chart { height: 440px; } }
.board-tv-only { margin-top: 12px; }
.board-tv-only[hidden] { display: none; }
.tv-only-title { font: 600 14px var(--sans); color: var(--ink); margin: 0 0 8px; }
.tv-only-grid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); }
.tv-only-grid .card { margin: 0; }
.tv-chip { font: 600 11px var(--sans); color: var(--muted); border: 1px solid var(--border-2); border-radius: 999px;
           padding: 1px 8px; white-space: nowrap; }

/* PHASE 21: a heading above the first card of a group on the rates board's tiles ("Treasury ETFs"); the table prints
 * the same words as a group row, in its own style. A widget title that opens another board on the Markets home is a
 * link that looks like the title it is, with an arrow that says there is more. */
.board-group { grid-column: 1 / -1; margin: 10px 0 0; font: 600 13px var(--sans); color: var(--muted); }
.board-grid .board-group:first-child { margin-top: 0; }
.board-table .group-row th { text-align: left; font: 600 11px var(--sans); color: var(--muted); background: var(--head-bg); padding-top: 8px; }
.widget .card-head h2 .widget-link { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit;
                                      cursor: pointer; text-align: left; }
.widget .card-head h2 .widget-link:hover { color: var(--accent); }
.widget .card-head h2 .widget-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }
.widget-link-go { color: var(--muted); font-weight: 400; }
.widget .card-head h2 .widget-link:hover .widget-link-go { color: var(--accent); }
.board-card { margin-top: 8px; }

/* PHASE 21: the rail's "Dashboards" is a drop-down (terminal.js): a button that looks like a rail link, a chevron that
 * turns, and the dashboards one step in underneath while it is open. */
.rail .rail-group-head {
  display: flex; align-items: center; justify-content: space-between; width: 100%; box-sizing: border-box;
  padding: 9px 20px; background: none; border: 0; border-left: 2px solid transparent; border-radius: 0;
  color: var(--ink-2); font: inherit; font-size: 14px; text-align: left; cursor: pointer;
}
.rail .rail-group-head:hover { color: var(--ink); background: var(--hover); }
.rail .rail-group-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rail .rail-group-head[data-current="true"] { color: var(--ink); font-weight: 600; }
.rail .rail-chevron { display: inline-block; font-size: 11px; color: var(--ink-3); transition: transform 0.12s ease; }
.rail .rail-group-head[aria-expanded="true"] .rail-chevron { transform: rotate(90deg); }
.rail .rail-group-body { display: flex; flex-direction: column; gap: 1px; }
.rail .rail-group-body[hidden] { display: none; }
.rail-group { display: flex; flex-direction: column; gap: 1px; }

/* PHASE 24 (W24-B): the rail's "Market dashboards" drop-down (railmenu.js): under the same head and chevron as "Dashboards", two
 * small headings -- Rates, Equities -- each over its three pages, one step further in than a dashboard's link. */
.rail .rail-subhead { padding: 8px 20px 2px 34px; font-size: 12px; font-weight: 600; color: var(--ink-3); }
.rail .rail-subgroup { display: flex; flex-direction: column; gap: 1px; }
.rail a.rail-sub2 { padding: 6px 20px 6px 48px; font-size: 13px; }
/* a market page's heading is the page's own; the page keeps the board's rhythm. */
.market-page-placeholder .card { margin-top: 8px; }

/* PHASE 20 (worker E4): the Treasury ETF tiles on the rates board (tape.js legsBlock, registry/boards.json
 * `legs`). A titled group of tiles under the board's own view, in the board's grid rhythm; opening one puts
 * the tape's own price chart of that ticker in the pane (the .board-chart host above). */
.board-legs { margin-top: 12px; }
.board-legs[hidden] { display: none; }
.leg-group-title { font: 600 14px var(--sans); color: var(--ink); margin: 0 0 4px; }
.leg-group-note { margin: 0 0 8px; }
.leg-grid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); }
.leg-grid .card { margin: 0; }

/* PHASE 21 (worker W-ESI): the equity sentiment dashboard (kinds-esi.js, docs/ESI-WORKBOOK.md). The gauge sits
 * left, the history right and the warning under the gauge, as on the owner's page; every colour is a variable of
 * the theme, so the page is right in light and in dark. No dashed or translucent line anywhere (one-axis rule). */


@container pane (min-width: 860px) {
  .dashboard-view .workbook-panels:has(> .kind-sentiment_gauge) { grid-template-columns: minmax(0, 1fr) minmax(0, 1.45fr); }
  .dashboard-view .workbook-panels > .panel.kind-sentiment_gauge[data-panel] { grid-column: 1; grid-row: 1; }
  .dashboard-view .workbook-panels > .panel.kind-flag_box[data-panel] { grid-column: 1; grid-row: 2; }
  .dashboard-view .workbook-panels > .panel.kind-index_history[data-panel] { grid-column: 2; grid-row: 1 / span 2; }
}
.esi-gauge-card { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 18px 12px 14px;
                  border: 1px solid var(--border); border-radius: 12px; background: var(--surface); }
.esi-gauge-wrap { width: 100%; max-width: 330px; }
.esi-dial { display: block; width: 100%; height: auto; }
.esi-arc { fill: none; }
.esi-stop.g0 { stop-color: var(--esi-fear-x); } .esi-stop.g1 { stop-color: var(--esi-fear); } .esi-stop.g2 { stop-color: var(--esi-neutral); }
.esi-stop.g3 { stop-color: var(--esi-greed); } .esi-stop.g4 { stop-color: var(--esi-greed-x); }
.esi-tick-line { stroke: var(--surface); stroke-width: 2; }
/* The dial is not a `.chart`, so the chart's tick rule never reached its numbers: they were the browser's black, and
 * unreadable on the dark card (seen in a real browser, 2026-10-09). */
.esi-dial .esi-tick-text { fill: var(--muted); font: 400 11px var(--sans); }
.esi-needle { stroke: var(--ink); stroke-width: 4; stroke-linecap: round; }
.esi-hub { fill: var(--surface); stroke: var(--ink); stroke-width: 3; }
.esi-score { font: 700 56px/1 var(--sans); margin-top: -4px; color: var(--ink); }
.esi-cat { font: 650 17px var(--sans); margin-top: 6px; }
.esi-help { font: 400 13px/1.45 var(--sans); color: var(--ink-3); margin: 8px 0 0; max-width: 300px; }
.esi-asof { margin: 10px 0 0; display: flex; flex-wrap: wrap; gap: 4px 6px; justify-content: center; align-items: center; }
.band-extreme-fear { color: var(--esi-fear-x); } .band-fear { color: var(--esi-fear); } .band-neutral { color: var(--esi-neutral); }
.band-greed { color: var(--esi-greed); } .band-extreme-greed { color: var(--esi-greed-x); }
.esi-history-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; margin: 0 0 4px; }
.esi-history-sub { margin: 0; flex: 1 1 260px; }
.esi-chip { display: inline-flex; align-items: center; gap: 6px; font: 650 13px var(--sans); white-space: nowrap; }
.esi-chip .dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
.chart.esi-history-chart .esi-zone.greed { fill: var(--up-wash); }
.chart.esi-history-chart .esi-zone.fear { fill: var(--down-wash); }
.chart.esi-history-chart .esi-cut { stroke-width: 1; }
.chart.esi-history-chart .esi-cut.greed { stroke: var(--esi-greed-x); } .chart.esi-history-chart .esi-cut.fear { stroke: var(--esi-fear-x); }
.chart.esi-history-chart .esi-cut-label { font-weight: 600; }
.chart.esi-history-chart .esi-cut-label.greed { fill: var(--esi-greed-x); } .chart.esi-history-chart .esi-cut-label.fear { fill: var(--esi-fear-x); }
.chart.esi-history-chart .esi-median { stroke: var(--muted); stroke-width: 1; }
.chart.esi-history-chart .esi-median-label { fill: var(--ink-3); }
.chart.esi-history-chart .esi-plate { fill: var(--surface); }
.chart.esi-history-chart .esi-episode { stroke: var(--border-2); stroke-width: 1; }
.chart.esi-history-chart .esi-episode-label { fill: var(--ink-2); font-weight: 600; }
.chart.esi-history-chart .esi-line { stroke-width: 1.8; }
.chart.esi-history-chart .esi-tri.up { fill: var(--esi-greed-x); stroke: var(--surface); stroke-width: 1.5; }
.chart.esi-history-chart .esi-tri.down { fill: var(--esi-fear-x); stroke: var(--surface); stroke-width: 1.5; }
.chart.esi-history-chart .last-dot { fill: var(--accent); stroke: var(--surface); stroke-width: 1.5; }
.esi-warn { display: grid; gap: 8px; }
.esi-flag { margin: 0; padding: 10px 14px; border: 1px solid var(--key); border-radius: 10px; background: var(--accent-wash);
            font: 400 13px/1.5 var(--sans); color: var(--ink); }
.esi-intro { margin: 0 0 6px; }
.esi-card-grid { display: grid; gap: 14px; margin-top: 12px; grid-template-columns: minmax(0, 1fr); }
@container pane (min-width: 660px) { .esi-card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container pane (min-width: 940px) { .esi-card-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.esi-card { display: flex; flex-direction: column; padding: 14px 16px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); }
.esi-card-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.esi-card-name { font: 600 14px var(--sans); color: var(--ink); }
.esi-card-val { font: 700 16px var(--sans); font-variant-numeric: tabular-nums; }
.esi-meter { display: block; width: 100%; height: 9px; margin: 9px 0 8px; }
.esi-meter-track { fill: var(--band); }
.esi-card-text { flex: 1; margin: 0; font: 400 12px/1.5 var(--sans); color: var(--ink-3); }
.esi-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--border); }
.esi-card-asof { margin: 0; }
.esi-card-chip { font: 650 11px var(--sans); color: var(--muted); }
/* PHASE 23 (W23-B): the weights, impossible to miss. "Weight 22%" under each card's name, and a table of the seven weights as bars
 * beside the latest scores at the top of the panel. Theme tokens only; a bar's length is a percent of the whole index and its
 * number is always printed beside it. */
.esi-card-weight { margin: 3px 0 0; font: 650 12px var(--sans); color: var(--ink-2); }
.esi-mix { margin: 6px 0 14px; }
.esi-mix-title { margin: 0 0 4px; font: 600 14px var(--sans); color: var(--ink); }
.esi-mix-lead, .esi-mix-rule { margin: 0 0 8px; }
.esi-mix-table td, .esi-mix-table th { vertical-align: middle; }
.esi-wcell { white-space: nowrap; }
.esi-wbar { display: inline-block; vertical-align: middle; width: 90px; height: 9px; margin-right: 8px; }
.esi-wbar-fill { fill: var(--accent); }
.esi-wpct { display: inline-block; min-width: 3.4em; text-align: right; font: 700 13px var(--sans); font-variant-numeric: tabular-nums; color: var(--ink); }
.esi-mix-table tr.total td { border-top: 1px solid var(--border-2); font-weight: 700; }
.esi-meth { border-top: 1px solid var(--border); margin-top: 6px; }
.esi-meth > summary { cursor: pointer; font: 600 14px var(--sans); color: var(--ink-2); padding: 14px 0 6px; }
.esi-method-body { padding: 6px 0 4px; }
.esi-method-table td { vertical-align: top; }
/* A card's score takes the step of its tens (0-10 -> --esi-s0 ... --esi-s10); the number is printed beside it, always. */
.esi-card-val.esi-s0 { color: var(--esi-s0); } .esi-meter-fill.esi-s0 { fill: var(--esi-s0); }
.esi-card-val.esi-s1 { color: var(--esi-s1); } .esi-meter-fill.esi-s1 { fill: var(--esi-s1); }
.esi-card-val.esi-s2 { color: var(--esi-s2); } .esi-meter-fill.esi-s2 { fill: var(--esi-s2); }
.esi-card-val.esi-s3 { color: var(--esi-s3); } .esi-meter-fill.esi-s3 { fill: var(--esi-s3); }
.esi-card-val.esi-s4 { color: var(--esi-s4); } .esi-meter-fill.esi-s4 { fill: var(--esi-s4); }
.esi-card-val.esi-s5 { color: var(--esi-s5); } .esi-meter-fill.esi-s5 { fill: var(--esi-s5); }
.esi-card-val.esi-s6 { color: var(--esi-s6); } .esi-meter-fill.esi-s6 { fill: var(--esi-s6); }
.esi-card-val.esi-s7 { color: var(--esi-s7); } .esi-meter-fill.esi-s7 { fill: var(--esi-s7); }
.esi-card-val.esi-s8 { color: var(--esi-s8); } .esi-meter-fill.esi-s8 { fill: var(--esi-s8); }
.esi-card-val.esi-s9 { color: var(--esi-s9); } .esi-meter-fill.esi-s9 { fill: var(--esi-s9); }
.esi-card-val.esi-s10 { color: var(--esi-s10); } .esi-meter-fill.esi-s10 { fill: var(--esi-s10); }
.esi-method-body p.note { margin: 0 0 8px; }
.esi-method-body > :last-child { margin-top: 12px; }
/* PHASE 22, THE METHOD TABS (kinds-method.js): headings for the recipe cards and the China contribution tables. No colour of
 * their own: every colour here is a theme token. */
.recipe-title { margin: 0 0 6px; font: 600 15px var(--sans); color: var(--ink); }
.recipe-group { margin-top: 16px; }
.recipe-group-title { margin: 0; font: 600 13px var(--sans); color: var(--ink-2); }
.cn-contrib { margin-top: 16px; }
.cn-contrib-head { margin: 0; font: 600 14px var(--sans); color: var(--ink); }
.cn-contrib-sub { margin: 2px 0 8px; }
.cn-contrib-table tr.total td { border-top: 1px solid var(--border-2); font-weight: 700; }
.cn-contrib-table td.when { color: var(--ink-3); font-size: 12px; }

/* The two-factor QR code (Phase 21, auth-ui.js + qr.js). Black modules on a white
 * ground WITH its quiet zone drawn into the SVG, so it reads in the dark theme
 * too; the frame around it is white for the same reason. */
.gate .qr { display: flex; justify-content: center; padding: 8px; background: var(--qr-paper); border: 1px solid var(--qr-edge); border-radius: 8px; }
.gate .qr svg { display: block; max-width: 100%; height: auto; }   /* the width attribute (a whole number of pixels per module) rules; a narrow card shrinks it */
/* The code's own fills carry the same colours as attributes (api/tests/test_dom_auth_qr.py), so a copy of the SVG read
 * outside this page is still black on white; these two rules only put the tokens in charge inside the page. */
.gate .qr svg rect { fill: var(--qr-paper); }
.gate .qr svg path { fill: var(--qr-ink); }

/* ------------------------------------------------ phase 21: the China dashboard (kinds-china.js)
 * His page in the terminal's two themes: a header with two chips, a note, eight tiles whose number and bar take the
 * band of the score, and for each section a head, charts and a table. Every colour is a token; nothing is dimmed
 * with opacity; every colour repeats something printed (the number, the arrow glyph, the word). */
.cn-chips { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px 8px; }
.cn-chip { padding: 4px 12px; font: 500 12px var(--sans); color: var(--ink-2); background: var(--raised);
           border: 1px solid var(--border-2); border-radius: 999px; font-variant-numeric: tabular-nums; }
.cn-chip strong { color: var(--ink); }
.cn-note { padding: 12px 14px; font: 400 13px/1.55 var(--sans); color: var(--ink-2); background: var(--surface);
           border: 1px solid var(--border); border-radius: 10px; }
.cn-note.key { border-left: 3px solid var(--key); }
.cn-note p { margin: 0 0 8px; }
.cn-note p:last-child { margin-bottom: 0; }
.cn-note strong { color: var(--ink); }
.cn-tile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(225px, 1fr)); gap: 11px; }
/* A tile's band (b4 best ... b0 weakest) colours its top edge, its number and its bar. The bands are classes of the
 * tile, not a variable the children inherit: a var() with a fallback is a colour the themes cannot reach
 * (api/tests/test_terminal_css.py). */
.cn-tile { position: relative; padding: 13px 14px 12px; background: var(--surface); border: 1px solid var(--border);
           border-top: 2px solid var(--accent); border-radius: 12px; min-width: 0; }
.cn-tile.b4 { border-top-color: var(--up); }
.cn-tile.b3 { border-top-color: var(--accent); }
.cn-tile.b2 { border-top-color: var(--key); }
.cn-tile.b1 { border-top-color: var(--overdue); }
.cn-tile.b0 { border-top-color: var(--down); }
.cn-tile-name { font: 600 12px/1.25 var(--sans); color: var(--ink-2); min-height: 30px; }
.cn-tile-score { margin-top: 6px; font: 750 34px/1 var(--sans); color: var(--accent); font-variant-numeric: tabular-nums; }
.cn-tile.b4 .cn-tile-score { color: var(--up); }
.cn-tile.b3 .cn-tile-score { color: var(--accent); }
.cn-tile.b2 .cn-tile-score { color: var(--key); }
.cn-tile.b1 .cn-tile-score { color: var(--overdue); }
.cn-tile.b0 .cn-tile-score { color: var(--down); }
.cn-tile-meta { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-top: 6px; }
.cn-z { font: 500 11px var(--sans); color: var(--muted); font-variant-numeric: tabular-nums; }
.cn-asof { color: var(--muted); }
.cn-trend { font: 800 11px var(--sans); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cn-trend.up { color: var(--up); }
.cn-trend.down { color: var(--down); }
.cn-trend.flat { color: var(--muted); }
.cn-bar { display: block; width: 100%; height: 5px; margin-top: 11px; }
.cn-track { fill: var(--rule); }
.cn-fill { fill: var(--accent); }
.cn-tile.b4 .cn-fill { fill: var(--up); }
.cn-tile.b3 .cn-fill { fill: var(--accent); }
.cn-tile.b2 .cn-fill { fill: var(--key); }
.cn-tile.b1 .cn-fill { fill: var(--overdue); }
.cn-tile.b0 .cn-fill { fill: var(--down); }
.cn-head { padding-top: 6px; border-top: 1px solid var(--border); }
.cn-head-line { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.cn-h { font: 680 18px var(--sans); color: var(--ink); }
.cn-chip-score { padding: 3px 11px; font: 700 11px var(--sans); border: 1px solid var(--border-2);
                 border-radius: 999px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.cn-chip-score.b4 { border-color: var(--up); color: var(--up); }
.cn-chip-score.b3 { border-color: var(--accent); color: var(--accent); }
.cn-chip-score.b2 { border-color: var(--key); color: var(--key); }
.cn-chip-score.b1 { border-color: var(--overdue); color: var(--overdue); }
.cn-chip-score.b0 { border-color: var(--down); color: var(--down); }
.cn-trend3 { margin: 4px 0 8px; font: 650 12px var(--sans); }
.cn-trend3.up { color: var(--up); }
.cn-trend3.down { color: var(--down); }
.cn-trend3.flat { color: var(--muted); }
.cn-trend3 .meta { font-weight: 500; color: var(--muted); }
.cn-desc { margin: 0 0 8px; max-width: 82ch; font: 400 12.5px/1.55 var(--sans); color: var(--muted); }
.cn-table td.when { color: var(--ink-3); white-space: nowrap; }
.cn-bottom-lines .note { margin: 0 0 8px; font-size: 13px; line-height: 1.55; color: var(--ink-2); }

/* ------------------------------------------------ phase 21: the volatility dashboard (kinds-vol.js) */
/* The stress chart's fill, its median rule and its event markers. A rule is a thin solid line (no dash, no opacity on
 * a line: test_dom_one_axis.py); the label is set apart from the line it crosses by a halo in the surface colour. */
.chart.wb .vol-area { fill: var(--accent-wash); }
.chart.wb .wb-event { fill: var(--ink-3); }
.chart.wb .vol-median { stroke: var(--ink-2); stroke-width: 1; }
.chart.wb .vol-median-label { fill: var(--ink-2); font-weight: 600; paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round; }
.chart.wb .last-dot { fill: var(--accent); stroke: var(--surface); stroke-width: 1.5; }
.regime-card { min-width: 0; }
.vol-hero { font: 700 52px/1.05 var(--sans); font-variant-numeric: tabular-nums; margin: 2px 0 6px; }
.vol-hero-sub { margin: 0 0 6px; color: var(--ink-2); font-size: 14px; }
.vol-hero-sub.meta { font-size: 13px; color: var(--muted); }
.vol-flag { margin: 12px 0; }
.vol-chip { display: inline-block; padding: 3px 14px; border: 1.5px solid var(--border-2); border-radius: 999px; font: 700 12.5px var(--sans);
            color: var(--ink-2); }
.vol-chip.level1 { border-color: var(--key); color: var(--key); }
.vol-chip.level2 { border-color: var(--warn); color: var(--warn); }
.vol-chip.level3 { border-color: var(--down); color: var(--down); }
.vol-note { margin: 0 0 8px; color: var(--ink-2); font-size: 13px; line-height: 1.5; }
.vol-note .muted { color: var(--muted); }
.vol-asof { margin-top: 10px; }
.vol-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; }
.vol-tile { padding: 12px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; min-width: 0; }
.vol-tile-label { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font: 600 12.5px var(--sans); color: var(--ink-2); }
.vol-tile .vol-chip { padding: 1px 8px; font-size: 10.5px; }
.vol-tile-value { margin: 2px 0; font: 600 26px var(--sans); font-variant-numeric: tabular-nums; }
.vol-spark { display: block; width: 100%; max-width: 170px; height: 34px; margin-top: 8px; }
.vol-table th[scope="row"] { text-align: left; color: var(--ink-2); font-weight: 600; }
.vol-cell { text-align: right; font-variant-numeric: tabular-nums; }
.vol-cell.own, .vol-cell.none { color: var(--ink-3); }
.vol-cell.own { text-align: center; }
.vol-cell.sig { background: var(--accent-wash); color: var(--ink); font-weight: 600; }
.vol-cell.strong { background: var(--accent-line); color: var(--ink); font-weight: 700; }
/* Main is the chart (three parts) beside the regime card (two), as on his page. */
@container pane (min-width: 860px) {
  .dashboard-view .workbook-panels:has(> .panel[data-kind="regime_card"]) { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
}

/* PHASE 21 (worker W-JP): the Japan dashboard (kinds-japan.js, docs/JAPAN-WORKBOOK.md). The score block of the owner's
 * page: a title row with the status chip, a row of cards, the 1Y / 3Y / 5Y buttons, the history and the words under it.
 * Every colour is a variable of the theme, so the page is right in light and in dark. No dashed or translucent line
 * anywhere (one-axis rule); a month still forming is a hollow ring. */
.jp-summary { margin: 0 0 14px; }
.jp-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin: 0 0 12px; }
.jp-title { font: 700 22px var(--sans); margin: 0; color: var(--ink); }
.jp-chip { display: inline-flex; align-items: center; gap: 7px; padding: 5px 12px; border: 1px solid var(--border); border-radius: 999px; font: 600 13px var(--sans); color: var(--ink); background: var(--surface); }
.jp-chip .dot { width: 9px; height: 9px; border-radius: 50%; background: currentColor; }
.jp-chip.tone-up .dot { color: var(--up); } .jp-chip.tone-down .dot { color: var(--down); } .jp-chip.tone-flat .dot { color: var(--muted); }
.jp-forming-note { margin: 0 0 10px; }
.jp-cards { display: grid; gap: 12px; justify-content: start; grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 300px)); }
.jp-card { padding: 14px 16px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); min-width: 0; }
.jp-card-label { font: 500 13px var(--sans); color: var(--ink-3); }
.jp-card-value { font: 700 34px/1.15 var(--sans); margin: 6px 0 4px; color: var(--ink); font-variant-numeric: tabular-nums; }
.jp-card-caption { font: 500 13px/1.4 var(--sans); }
.jp-card-caption.tone-up { color: var(--up); } .jp-card-caption.tone-down { color: var(--down); } .jp-card-caption.tone-flat { color: var(--muted); }
.jp-card-asof { margin: 6px 0 0; }
.jp-ranges { display: flex; gap: 8px; margin: 0 0 10px; }
.jp-range { flex: 0 0 auto; width: auto; padding: 5px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--ink-2); font: 600 13px var(--sans); cursor: pointer; }
.jp-range[aria-pressed="true"] { border-color: var(--accent); color: var(--ink); }
.jp-range:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.jp-history-sub { margin: 0 0 8px; }
.chart.jp-chart .jp-plate { fill: var(--surface); }
.chart.jp-chart .jp-ring { fill: var(--surface); stroke-width: 1.6; }
.chart.jp-chart .jp-ring.s0 { stroke: var(--accent); } .chart.jp-chart .jp-ring.s1 { stroke: var(--key); } .chart.jp-chart .jp-ring.s2 { stroke: var(--up); }
.chart.jp-chart .jp-end { stroke: var(--surface); stroke-width: 1.5; }
.chart.jp-chart .jp-end.s0 { fill: var(--accent); } .chart.jp-chart .jp-end.s1 { fill: var(--key); } .chart.jp-chart .jp-end.s2 { fill: var(--up); }
.jp-ring-key { display: inline-block; width: 9px; height: 9px; border: 1.6px solid var(--muted); border-radius: 50%; background: var(--surface); }
.jp-narrative { margin: 12px 0 0; padding-top: 12px; border-top: 1px solid var(--border); }
.jp-data { margin-top: 8px; }
.jp-cards-row { margin: 0 0 14px; }
.jp-group { color: var(--ink-3); }
.jp-latest td.when { color: var(--ink-3); white-space: nowrap; }
.chart.jp-bars-chart .row-value { font-weight: 600; }

/* PHASE 22 (worker W22-C): the Gold dashboard (kinds-gold.js, docs/GOLD-WORKBOOK.md). The price with its trend lines, the range
 * buttons, the study's table and the corrections under it. Every colour is a variable of the theme, so the page is right in light
 * and in dark. No dashed or translucent line anywhere (one-axis rule): the price and its two trend lines are told apart by
 * colour and by the words in the legend, which says where each stands. */
.gold-ranges { display: flex; gap: 8px; margin: 0 0 10px; }
.gold-range { flex: 0 0 auto; width: auto; padding: 5px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--ink-2); font: 600 13px var(--sans); cursor: pointer; }
.gold-range[aria-pressed="true"] { border-color: var(--accent); color: var(--ink); }
.gold-range:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gold-caption { margin: 0 0 10px; }
.gold-standing { margin: 10px 0 0; }
.gold-readings { margin-top: 8px; }
.gold-table th[scope="row"] { text-align: left; color: var(--ink); font-weight: 600; white-space: nowrap; }
.gold-table td.gold-cell { min-width: 96px; font-variant-numeric: tabular-nums; }
.gold-cell .gold-main, .gold-table th .gold-main { display: block; color: var(--ink); }
.gold-cell .gold-sub, .gold-table th .gold-sub { display: block; margin-top: 2px; color: var(--muted); font-size: 12px; font-weight: 400; }
.gold-count { margin: 8px 0 0; }
.gold-findings { margin: 14px 0 0; padding-top: 12px; border-top: 1px solid var(--border); }
.gold-findings-head { margin: 0 0 8px; font: 700 14px var(--sans); color: var(--ink); }
.gold-finding { margin: 0 0 8px; max-width: 84ch; }
/* =====================================================================================================================
 * PHASE 22 (W22-D): THE RATES SCREEN AS A WORLD BOND TABLE (bonds.js).
 *
 * The owner's Bloomberg "World Bond Markets" screen: tabs, countries under region headings, a yield and its change in
 * basis points per maturity, amber on the page. Every colour is a token (--amber, --up, --down, --muted ...), so the
 * light theme redefines the same rules. The COUNTRY column stays put while the maturities scroll under it (position:
 * sticky inside the table's own scroller); nothing is dimmed with opacity; a change always PRINTS its sign (+ or the
 * minus sign, or "unch"), the colour only repeats it.
 * ===================================================================================================================== */
.bonds-view { min-width: 0; }
.bonds-tabs { margin: 0 0 10px; }
.bonds-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin: 0 0 10px; }
.bonds-tools[hidden] { display: none; }
.bonds-tools-label { font-weight: 600; }
.bonds-panel { min-width: 0; }
.bonds-table { width: auto; min-width: 100%; }
.bonds-table th, .bonds-table td { padding: 6px 8px; }
.bonds-table thead th:not(.bonds-country) { position: static; text-align: right; }
.bonds-table thead th.bonds-col { text-align: center; color: var(--ink-2); font-size: 12.5px; border-top: 0; }
.bonds-table th.bonds-country { position: sticky; left: 0; z-index: 2; min-width: 132px; background: var(--surface);
                                border-right: 1px solid var(--border); text-align: left; white-space: normal; }
.bonds-table thead th.bonds-country { vertical-align: bottom; font-size: 12.5px; color: var(--ink-2); }
.bonds-table .group-row th { position: static; text-align: left; font: 600 11.5px var(--sans); color: var(--amber);
                             background: var(--head-bg); padding-top: 9px; border-right: 0; }
.bonds-table .group-row th.bonds-country { position: static; }
.bonds-region { position: sticky; left: 8px; display: inline-block; }
.bonds-table .country-name { color: var(--ink); font-weight: 600; font-size: 13px; text-align: left; }
.bonds-table button.linkish.country-name { text-decoration: underline; text-decoration-color: var(--accent-line); }
.country-asof { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; margin-top: 1px; font-weight: 400; }
.country-asof .badge { font-size: 10.5px; }
.bonds-table td.yld { color: var(--amber); font-weight: 600; }
.bonds-table td.yld button.linkish { color: var(--amber); font: inherit; font-weight: 600; }
.bonds-table td.bonds-none, .bonds-table td.bonds-none button.linkish { color: var(--muted); font-weight: 400; }
.bonds-table td.chg { font-size: 12.5px; }
.bonds-table td.chg.sign-flat, .bonds-table td.chg.sign-none { color: var(--muted); }
/* THE COLOUR OF A CHANGE ON THIS TABLE. The owner, 2026-10-09: "Like Bloomberg: green = yield fell". On the Bonds, Spreads and
 * Curves tabs a change is GREEN when the number FELL and RED when it ROSE -- the other way round from the rest of the app
 * (the Markets home, the cards, the change pills: green = up), because for a bond a falling yield is a rising price, and the
 * Treasury ETF rows under this table (price colours) then agree with the yields above them. The classes name the DIRECTION of
 * the number (sign-up / sign-down, which the colour-alone gate holds to a printed + or minus); these two rules say what colour
 * a direction is HERE. The sign is always printed, so colour never carries the meaning alone. */
.bonds-table td.chg.sign-down { color: var(--up); }     /* the number FELL: the app's green */
.bonds-table td.chg.sign-up { color: var(--down); }     /* the number ROSE: the app's red */
.bonds-table .chg-tag { margin-left: 4px; font-size: 11px; }
.bonds-table .chg-tag[hidden] { display: none; }
.bonds-legend { margin: 10px 0 0; max-width: 92ch; }
.bonds-others { margin-top: 18px; }
.bonds-others[hidden] { display: none; }
.bonds-other + .bonds-other { margin-top: 14px; }
.bonds-other .board-group { margin: 0 0 6px; }
.bonds-sources { margin-top: 12px; font-size: 12px; color: var(--muted); }
.bonds-sources[hidden] { display: none; }
.bonds-sources summary { cursor: pointer; }
.bonds-sources-list { margin: 6px 0 0; padding-left: 18px; }
.chip.monthly, .chip.benchmark { color: var(--ink-2); }


/* PHASE 25 frozen oil charts (W25-B), as a GALLERY (W25-C, 2026-10-15; api/static/app/kinds-oil.js, docs/COMMODITIES-CHARTS.md).
 * The owner: "the charts are too big ... we should smaller the size a bit", and a library of small cards three to a row ("something like
 * this ... only in oil"). A page of these panels is a grid of CARDS: at most three across, two on a tablet, one on a phone (the columns are
 * as many 300 px tracks as fit, never narrower than a third of the page). A card is one native button over a tag, a title, the stamp and a
 * PREVIEW; pressing it opens the chart LARGE in place of the grid (the other cards are put away by :has(), so a poll that rebuilds them
 * cannot bring them back) and the way back restores the grid.
 * THE SIZE: the page inside is always drawn W0 = --oil-w (1150) px wide, where the owner's pages are flat, `h<px>` pixels tall (one class
 * per height tools/oil_charts.py HEIGHTS allows, measured: tools/oil_frame_heights.py), and shown at --oil-k of that through transform:
 * scale(), inside a box of the scaled size. --oil-k is a custom property the client writes on the slot (a thumbnail: the slot's width over
 * 1150; an opened chart: at most 0.8); the stylesheet's default stands where nothing has been measured. The policy forbids a style
 * attribute in the markup and there is none. The page inside scrolls itself if it is still taller than its frame. */
.embedded-chart { margin: 0; min-width: 0; height: 100%; }
.panel.kind-embedded_chart { display: flex; flex-direction: column; }
.panel.kind-embedded_chart > .embedded-chart { flex: 1 1 auto; }
.dashboard-view .panels:has(> .panel.kind-embedded_chart) {
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  grid-template-columns: repeat(auto-fill, minmax(max(300px, calc((100% - 36px) / 3)), 1fr));
}
/* A page of live panels AND gallery cards (the crude page): the gallery is the grid above, and every live panel above it takes the whole row, as it
 * does on the other three Oil pages. Without this the cards panel was one grid cell with six cards stacked in it, and each chart a third of the page
 * (review R4C m1; seen in a browser on 2026-10-16). */
.dashboard-view .panels:has(> .panel.kind-embedded_chart) > .panel:not(.kind-embedded_chart) { grid-column: 1 / -1; }
.dashboard-view .panels:has(.oil-gal[data-mode="large"]) { grid-template-columns: minmax(0, 1fr); }
.dashboard-view .panels:has(.oil-gal[data-mode="large"]) > .panel:not(:has(.oil-gal[data-mode="large"])) { display: none; }
.oil-gal {
  position: relative; box-sizing: border-box; display: flex; flex-direction: column; gap: 6px; height: 100%; min-width: 0;
  padding: 12px; background: var(--surface); border: 1px solid var(--border-2); border-radius: 9px;
}
.oil-gal[data-mode="card"]:hover { border-color: var(--accent-line); }
.oil-gal[data-mode="card"] { cursor: pointer; }
.oil-tag { margin: 0; font: 600 11px var(--sans); color: var(--ink-3); }
.oil-title { margin: 0; font: 600 14px/1.35 var(--sans); color: var(--ink); }
.oil-name { display: none; margin: 0; font: 600 16px/1.3 var(--sans); color: var(--ink); }
.oil-bar { display: none; }
.oil-gal .chart-stamp { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 0; font-size: 12px; color: var(--muted); }
/* THE CARD IS ONE NATIVE BUTTON: its title is the name, and its ::after is laid over the whole card so a press anywhere on it is that press. */
.oil-open {
  display: block; width: 100%; margin: 0; padding: 0; text-align: left; background: none; border: 0; border-radius: 0;
  color: var(--ink); font: 600 14px/1.35 var(--sans);
}
.oil-open:hover:not([disabled]) { filter: none; }
.oil-open::after { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: 9px; }
.oil-open:focus-visible { outline: none; }
.oil-open:focus-visible::after { outline: 2px solid var(--accent); outline-offset: 2px; }
.oil-slot { position: relative; width: 100%; overflow: hidden; }
.oil-sizer {
  position: relative; margin: 0 auto; outline: 1px solid var(--border); border-radius: 4px;
  width: calc(var(--oil-w) * var(--oil-k) * 1px); height: calc(var(--chart-h) * var(--oil-k) * 1px);
}
.oil-sizer iframe {
  position: absolute; left: 0; top: 0; display: block; border: 0; background: var(--chart-ground); color-scheme: light;
  width: calc(var(--oil-w) * 1px); height: calc(var(--chart-h) * 1px);
  transform: scale(var(--oil-k)); transform-origin: 0 0; filter: var(--oil-frame-filter);
}
.oil-gal[data-mode="card"] .oil-sizer iframe { pointer-events: none; }
/* OPEN: the card gives up its button, shows the way back and the name, and the chart is clickable. A chart wider than the screen at its
 * smallest scale scrolls sideways inside the slot. */
.oil-gal[data-mode="large"] { height: auto; }
.oil-gal[data-mode="large"] .oil-open { display: none; }
.oil-gal[data-mode="large"] .oil-bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.oil-gal[data-mode="large"] .oil-name { display: block; }
.oil-gal[data-mode="large"] .oil-slot { overflow-x: auto; overflow-y: hidden; }
.embedded-chart .chart-frame.h600 { --chart-h: 600; }
.embedded-chart .chart-frame.h620 { --chart-h: 620; }
.embedded-chart .chart-frame.h640 { --chart-h: 640; }
.embedded-chart .chart-frame.h660 { --chart-h: 660; }
.embedded-chart .chart-frame.h680 { --chart-h: 680; }
.embedded-chart .chart-frame.h700 { --chart-h: 700; }
.embedded-chart .chart-frame.h720 { --chart-h: 720; }
.embedded-chart .chart-frame.h740 { --chart-h: 740; }
.embedded-chart .chart-frame.h760 { --chart-h: 760; }
.embedded-chart .chart-frame.h780 { --chart-h: 780; }
.embedded-chart .chart-frame.h800 { --chart-h: 800; }
.embedded-chart .chart-frame.h820 { --chart-h: 820; }
.embedded-chart .chart-frame.h840 { --chart-h: 840; }
.embedded-chart .chart-frame.h860 { --chart-h: 860; }
.embedded-chart .chart-frame.h880 { --chart-h: 880; }
.embedded-chart .chart-frame.h900 { --chart-h: 900; }
.embedded-chart .chart-frame.h920 { --chart-h: 920; }
.embedded-chart .chart-frame.h940 { --chart-h: 940; }
.embedded-chart .chart-frame.h960 { --chart-h: 960; }
.embedded-chart .chart-frame.h980 { --chart-h: 980; }
.embedded-chart .chart-frame.h1000 { --chart-h: 1000; }
.embedded-chart .chart-frame.h1020 { --chart-h: 1020; }
.embedded-chart .chart-frame.h1040 { --chart-h: 1040; }
.embedded-chart .chart-frame.h1060 { --chart-h: 1060; }
.embedded-chart .chart-frame.h1080 { --chart-h: 1080; }
.embedded-chart .chart-frame.h1100 { --chart-h: 1100; }
.embedded-chart .chart-frame.h1120 { --chart-h: 1120; }
.embedded-chart .chart-frame.h1140 { --chart-h: 1140; }
.embedded-chart .chart-frame.h1160 { --chart-h: 1160; }
.embedded-chart .chart-frame.h1180 { --chart-h: 1180; }
.embedded-chart .chart-frame.h1200 { --chart-h: 1200; }
.embedded-chart .chart-frame.h1220 { --chart-h: 1220; }
.embedded-chart .chart-frame.h1240 { --chart-h: 1240; }
.embedded-chart .chart-frame.h1260 { --chart-h: 1260; }
.embedded-chart .chart-frame.h1280 { --chart-h: 1280; }
.embedded-chart .chart-frame.h1300 { --chart-h: 1300; }
.embedded-chart .chart-frame.h1320 { --chart-h: 1320; }
.embedded-chart .chart-frame.h1340 { --chart-h: 1340; }
.embedded-chart .chart-frame.h1360 { --chart-h: 1360; }
.embedded-chart .chart-frame.h1380 { --chart-h: 1380; }
.embedded-chart .chart-frame.h1400 { --chart-h: 1400; }
.embedded-chart .chart-frame.h1420 { --chart-h: 1420; }
.embedded-chart .chart-frame.h1440 { --chart-h: 1440; }
.embedded-chart .chart-frame.h1460 { --chart-h: 1460; }
.embedded-chart .chart-frame.h1480 { --chart-h: 1480; }
.embedded-chart .chart-frame.h1500 { --chart-h: 1500; }
.embedded-chart .chart-frame.h1520 { --chart-h: 1520; }
.embedded-chart .chart-frame.h1540 { --chart-h: 1540; }
.embedded-chart .chart-frame.h1560 { --chart-h: 1560; }
.embedded-chart .chart-frame.h1580 { --chart-h: 1580; }
.embedded-chart .chart-frame.h1600 { --chart-h: 1600; }
.embedded-chart .chart-frame.h1620 { --chart-h: 1620; }
.embedded-chart .chart-frame.h1640 { --chart-h: 1640; }
.embedded-chart .chart-frame.h1660 { --chart-h: 1660; }
.embedded-chart .chart-frame.h1680 { --chart-h: 1680; }
.embedded-chart .chart-frame.h1700 { --chart-h: 1700; }
/* PHASE 25 frozen oil charts: end */

/* PHASE 26 billing: begin */
/* PHASE 26 (W26-B): BUYING ON THE ADD-ONS PAGE (addons.js, docs/BILLING-UI.md). Every colour is a token that already exists, so the light
 * theme reaches it; nothing here dims, dashes, capitalises or spaces out. The block sits ABOVE the equities block, which stays last
 * (api/tests/test_equities_css.py), and a disabled Buy is dimmed by the global `button[disabled]` rule -- the one dimming the house allows. */
.billing-test { margin: 0 0 12px; font-weight: 600; }
.billing-slot { margin: 0 0 4px; }
.billing-status { margin: 0 0 10px; }
/* An empty live line takes no room but stays RENDERED: a live region that is display:none when it fills is not announced. */
.billing-status:empty { margin: 0; padding: 0; border: 0; }
.billing-manage-row { margin: 0 0 10px; }
.billing-notes { margin: 0 0 14px; padding-left: 18px; color: var(--muted); font-size: 12.5px; line-height: 1.5; }
.billing-notes li { margin: 2px 0; }
.billing-buy { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin-top: 12px; }
.billing-price { font-weight: 600; font-variant-numeric: tabular-nums; color: var(--ink); }
button.billing-btn { width: auto; padding: 8px 20px; font-size: 13.5px; }
button.billing-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.billing-why { margin: 0; flex-basis: 100%; }
@media (max-width: 760px) {
  .billing-buy { flex-direction: column; align-items: stretch; gap: 8px; }
  .billing-buy .billing-price { text-align: left; }
  button.billing-btn { width: 100%; min-height: 44px; }
}
/* PHASE 26 billing: end */
/* PHASE 24c yield candles (W24c-B): the third tab of a yield's chart (yieldcandles.js) and the first tab's pointer at it (charttools.js).
 * Candles and Bars on Closes (ours) are not drawn at the daily interval; where the layout names real daily candles a press opens them on the next
 * tab, so the two buttons are muted (colour only, as the weekly ones are) and stay buttons. */
.pc-btn[data-next-tab="true"] { color: var(--muted); }
.yc-view { display: grid; gap: 8px; }
.yc-view .tv-note { margin: 0; }
.yc-failed { display: grid; gap: 8px; justify-items: start; }
/* three tabs ('Candles (CBOE index)' is the longest label) must not push a narrow phone sideways: the row may wrap */
.tv-frame .tv-switch { max-width: 100%; flex-wrap: wrap; }
/* PHASE 24c yield candles: end */
/* ===== THE CORRELATION PAGE (Phase 24e, worker W24e-P; api/static/app/kinds-correlation.js, docs/CORRELATION-PAGE.md) =====
 * Tokens only, every colour a sign or a state repeats words the cell already prints. The shade of a matrix cell is a wash over the ground
 * (a line is never faded and never broken). */
.corr-jump { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin: 0 0 10px; }
.corr-jump .quiet { padding: 3px 9px; }
.corr-macro { margin: 0 0 10px; color: var(--ink-2); }
.corr-table th, .corr-table td { vertical-align: top; }
.corr-table th.num, .corr-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.corr-table caption { text-align: left; font-weight: 600; padding: 0 0 6px; color: var(--ink-2); }
.corr-regime { min-width: 15rem; }
.corr-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; border: 1px solid var(--ink-3); }
.corr-dot.on { background: var(--up); border-color: var(--up); }
.chip.corr-state.on { border-color: var(--up); }
.chip.corr-thin { border-color: var(--warn); }
.chip.corr-turning { margin-left: 6px; border-color: var(--warn); }
.chip.corr-range.open, .chip.corr-range.none { color: var(--ink-3); }
.corr-edge { font-weight: 700; font-variant-numeric: tabular-nums; }
.corr-flat { color: var(--ink-2); }
.corr-driver { display: inline-block; margin: 0 10px 2px 0; white-space: nowrap; font-variant-numeric: tabular-nums; }
.corr-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px; }
.corr-card { border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; background: var(--surface); }
.corr-card.flip { border-color: var(--warn); }
.corr-card header { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 8px; }
.corr-card h3 { margin: 0; font-size: 1rem; }
.corr-figures { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px 8px; margin: 8px 0; }
.corr-figures dt { color: var(--ink-3); font-size: 0.75rem; }
.corr-figures dd { margin: 0; font-variant-numeric: tabular-nums; font-weight: 600; }
.corr-sentence { margin: 6px 0; }
.corr-spark { display: block; width: 100%; max-width: 220px; height: 34px; }
.corr-two { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 14px; }
.corr-sort { padding: 0; font-weight: 600; }
.corr-grid { border-collapse: separate; border-spacing: 1px; }
.corr-grid th, .corr-grid td { padding: 3px 5px; font-size: 0.75rem; text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.corr-grid th[scope="row"] { text-align: left; position: sticky; left: 0; background: var(--surface); }
.corr-cell.own, .corr-cell.none { color: var(--ink-3); text-align: center; }
.corr-cell.up.lvl1 { background: color-mix(in srgb, var(--up) 12%, transparent); }
.corr-cell.up.lvl2 { background: color-mix(in srgb, var(--up) 26%, transparent); }
.corr-cell.up.lvl3 { background: color-mix(in srgb, var(--up) 42%, transparent); }
.corr-cell.down.lvl1 { background: color-mix(in srgb, var(--down) 12%, transparent); }
.corr-cell.down.lvl2 { background: color-mix(in srgb, var(--down) 26%, transparent); }
.corr-cell.down.lvl3 { background: color-mix(in srgb, var(--down) 42%, transparent); }
.corr-matrix .seg { margin: 0 0 8px; }
@media (max-width: 640px) { .corr-figures { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* ===== the Correlation page: end ===== */
/* =================================================================== PHASE 27 (W27-B): legal
 * What legal.js draws (docs/LEGAL-UI.md): the cookie note, the acceptance screen, the footer rows and the pricing statements.
 * Tokens only, so both themes follow. No state rests on colour: a tag on the acceptance screen is a word. The server-rendered
 * legal pages (/legal/...) load this file too, and legal.css owns every class that begins `legal`; nothing below does. */

/* The sign-in wall's footer row sits under the card; the two stay together in the middle of the page. */
.gate { align-content: center; }

.legalbar { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; font-size: 12px; line-height: 1.5; color: var(--muted); }
.legalbar a { color: var(--ink-2); text-decoration: underline; text-decoration-color: var(--accent-line); }
.legalbar a:hover { color: var(--ink); text-decoration-color: currentColor; }
.legalbar a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.legalbar-co { color: var(--muted); }
.legalbar-gate { position: relative; width: 100%; max-width: 400px; margin-top: 16px; justify-content: center; text-align: center; min-height: 20px; }
/* A statement's place waits empty until the read ends (auth-ui.js); an empty place draws no ruled strip. */
.gate .fine:empty { display: none; }

/* In the rail: under the account lines, above Sign out. A rail link is a block with padding and a bar at its left; these are small
 * inline links, so the rail's own link rules are put back. On a phone the foot has no room, so the links go behind the menu button
 * instead (terminal.js draws them twice; one copy shows at a time). */
.rail .legalbar { margin-top: 10px; gap: 2px 10px; }
.rail .legalbar a { display: inline; padding: 0; border-left: 0; background: none; font-size: 12px; color: var(--ink-3); text-decoration: underline; }
.rail .legalbar a:hover { color: var(--ink); background: none; }
.rail .legalbar-menu { display: none; }

/* The cookie note: a small panel at the bottom RIGHT on a wide screen (the left menu's foot, Sign out included, stays reachable
 * under it), the width of the page at the bottom on a phone. It is drawn on the page itself, outside the app's root. It never moves
 * focus and holds nothing back: OK removes it. */
.cookie-note {
  position: fixed; right: 16px; bottom: 16px; z-index: 30;
  width: 288px; max-width: calc(100vw - 32px); max-height: 60dvh; overflow-y: auto;
  padding: 12px 14px; border: 1px solid var(--border-2); border-radius: 10px;
  background: var(--raised); color: var(--ink-2); box-shadow: var(--shadow);
  font-size: 12px; line-height: 1.5;
}
.cookie-note p { margin: 0 0 8px; }
.cookie-items { margin: 0 0 8px; padding-left: 16px; }
.cookie-items li { margin: 3px 0; }
.cookie-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
.cookie-actions a { color: var(--accent); }

/* The acceptance screen: one row for each text, with a link, the date it was updated, and a word that says whether it is new or changed. */
.accept-list { list-style: none; margin: 0 0 6px; padding: 0; border-top: 1px solid var(--border); }
.accept-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; padding: 10px 0; border-bottom: 1px solid var(--border); font-size: 14px; }
.accept-row a { font-weight: 600; }
.accept-date { color: var(--muted); font-size: 12px; }
.accept-tag { margin-left: auto; padding: 1px 7px; border: 1px solid var(--border-2); border-radius: 6px; color: var(--ink); font-size: 11px; font-weight: 600; }
.accept-tag[data-accept-tag="changed"] { border-color: var(--warn); }
.gate[data-accept] .check { align-items: flex-start; }
.gate[data-accept] .check input { flex: none; margin-top: 1px; }
.accept-signout { margin-top: 12px; }

/* The pricing statements on the Add-ons page. */
.addons-view > .addon-legal { margin: 0 0 12px; }
.buy-agree { margin: 0; flex-basis: 100%; }

@media (max-width: 760px) {
  .rail .foot .legalbar-rail { display: none; }
  .rail .rail-links .legalbar-menu { display: flex; flex-basis: 100%; margin: 4px 0 0; padding: 0 6px; }
  .cookie-note { left: 0; right: 0; bottom: 0; width: auto; max-width: none; max-height: 45dvh; border-width: 1px 0 0; border-radius: 10px 10px 0 0; }
  /* Room under the sign-in card for the note, so it never sits on a field or a button; the page scrolls instead. */
  body:has(> .cookie-note) .gate { padding-bottom: 240px; }
}

/* ===== PHASE 25 (W25-K): the seasonal band (kinds-commodity.js) ===== */
/* One weekly series, a line for every year. A line takes its colour from `--sbc`, which its class sets: this year is the accent, the
 * average the page's own ink, and each earlier year (sb-y1 is the year before this one) a step through the line colours the Equities
 * pages hold -- neighbours far apart, every one at 3:1 as a stroke on every surface (test_equities_css.py), read again for these slots
 * by test_dom_seasonal_band.py. Weight and colour tell the lines apart: this year heavy, the average a step lighter, the years before it
 * thin. Nothing here is dashed and nothing is faded (api/tests/test_dom_one_axis.py); the range is a fill. */
.sb-now { --sbc: var(--accent); }
.sb-avg { --sbc: var(--ink); }
.sb-y1 { --sbc: var(--eq-1); }
.sb-y2 { --sbc: var(--eq-2); }
.sb-y3 { --sbc: var(--eq-3); }
.sb-y4 { --sbc: var(--eq-4); }
.sb-y5 { --sbc: var(--eq-5); }
.sb-y6 { --sbc: var(--eq-6); }
.sb-y7 { --sbc: var(--eq-7); }
.sb-y8 { --sbc: var(--eq-8); }
.sb-y9 { --sbc: var(--eq-9); }
.sb-y10 { --sbc: var(--eq-10); }
.sb-y11 { --sbc: var(--eq-11); }
.sb-y12 { --sbc: var(--eq-12); }
.chart.wb .sb-line { stroke: var(--sbc); stroke-width: 1.25; }
.chart.wb .sb-line.sb-avg { stroke-width: 2; }
.chart.wb .sb-line.sb-now { stroke-width: 2.8; }
.chart.wb .sb-range-fill { fill: var(--band); stroke: none; }
/* The end labels carry a halo in the card's own colour, so a number stays readable where a line runs under it. */
.chart.wb .sb-end { font-weight: 700; font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round; }
.chart.wb .sb-end.sb-now { fill: var(--accent); }
.chart.wb .sb-end.sb-avg { fill: var(--ink); }
/* Four classes, so these beat `.chart.wb .wb-hover-dot` and `.chart.wb .wb-readout-swatch` (three) whatever the order (the Equities lesson). */
.chart.wb.sb-chart .wb-hover-dot { fill: var(--sbc); }
.chart.wb.sb-chart .wb-readout-swatch { fill: var(--sbc); }
.chart.wb.sb-chart .wb-readout-swatch.sb-range { fill: var(--band); stroke: var(--ink-3); stroke-width: 1; }
.swatch.sb-sw { background: var(--sbc); }
.swatch.sb-sw.sb-range { background: var(--band); border: 1px solid var(--ink-3); box-sizing: border-box; }
.sb-note { margin: 6px 0 0; }
/* ===== PHASE 25 (W25-K): the seasonal band: end ===== */
/* PHASE 24 equities pages: begin */
/* THE FREE EQUITIES PAGES (equities.js, docs/EQUITIES-PAGES.md): Countries, US sectors, US factor analysis. One chart per page, a pill at the
 * end of every line, a chip per fund. Every colour is a variable of the theme, so the page is right in light and in dark. Nothing here
 * dashes or fades a line (one-axis rule, api/tests/test_dom_one_axis.py): the zero line is drawn as dots by the script, not by a dash
 * style, and a hidden fund is a hollow swatch and a struck-through name, never a faded one. A fund's colour is named by its slot: the
 * class `eq-N` sets `--eqc` on whatever carries it (the line, its dot, its leader, its pill, the chip's swatch, the reading's dot and
 * swatch), and every rule below reads `--eqc`. */
.eq-1 { --eqc: var(--eq-1); }
.eq-2 { --eqc: var(--eq-2); }
.eq-3 { --eqc: var(--eq-3); }
.eq-4 { --eqc: var(--eq-4); }
.eq-5 { --eqc: var(--eq-5); }
.eq-6 { --eqc: var(--eq-6); }
.eq-7 { --eqc: var(--eq-7); }
.eq-8 { --eqc: var(--eq-8); }
.eq-9 { --eqc: var(--eq-9); }
.eq-10 { --eqc: var(--eq-10); }
.eq-11 { --eqc: var(--eq-11); }
.eq-12 { --eqc: var(--eq-12); }
.eq-13 { --eqc: var(--eq-13); }
.eq-14 { --eqc: var(--eq-14); }
.eq-15 { --eqc: var(--eq-15); }
.eq-16 { --eqc: var(--eq-16); }
.eq-17 { --eqc: var(--eq-17); }
.eq-18 { --eqc: var(--eq-18); }
.eq-19 { --eqc: var(--eq-19); }
.eq-20 { --eqc: var(--eq-20); }
.eq-21 { --eqc: var(--eq-21); }
.eq-22 { --eqc: var(--eq-22); }
.eq-page .eq-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 4px; }
.eq-page .eq-title { font: 600 15px var(--sans); margin: 0; }
.eq-page .eq-lede { margin: 0 0 10px; max-width: 92ch; }
.eq-page .eq-controls { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; margin: 0 0 8px; }
.eq-page .eq-actions { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.eq-page .eq-actions button { padding: 3px 10px; font-size: 12px; }
.eq-page .eq-actions button[hidden] { display: none; }
.eq-page .eq-status { margin: 0 0 6px; }
.eq-page .eq-status[hidden] { display: none; }
.eq-box { min-height: 120px; }
.eq-empty { margin: 0; padding: 24px 0; }
.eq-chart { display: block; width: 100%; }
.eq-chart .eq-line { fill: none; stroke: var(--eqc); stroke-width: 1.75; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.eq-chart .eq-dot { fill: var(--eqc); }
.eq-chart .eq-leader { fill: none; stroke: var(--eqc); stroke-width: 1; }
.eq-chart .eq-pill-box { fill: var(--surface); stroke: var(--eqc); stroke-width: 1.5; }
.eq-chart .tick.eq-pill-text { fill: var(--ink); font-size: 10.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.eq-chart .eq-zero { fill: none; stroke: var(--ink-2); stroke-width: 2; stroke-linecap: round; }
/* R24-2 M1: four classes, so these beat `.chart.wb .wb-hover-dot` and `.chart.wb .wb-readout-swatch` (three) whatever the order: with two classes the dots took the ink colour */
.chart.wb.eq-chart .wb-hover-dot { fill: var(--eqc); }
.chart.wb.eq-chart .wb-readout-swatch { fill: var(--eqc); }
/* R24-2 M2: inside the market-page router the router's own heading is the page's one title; the card's own would print it twice */
.market-page .eq-page .eq-title { display: none; }
/* The enlarged card covers the page's own heading, so it says its name itself (phase 28). */
.market-page .eq-page[data-enlarged="true"] .eq-title { display: block; }
.eq-chips { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 330px), 1fr)); gap: 6px; margin: 10px 0 8px; }
/* A CHIP IS TWO CONTROLS (W24b, the owner 2026-10-15): the BODY opens the fund's own chart, the SQUARE at its left hides or shows the line. Each is a
 * button with its own hover and focus ring (the ring is drawn inside, so a neighbour never clips it). Four parts, so the hover beats the global
 * `button:hover:not([disabled])` brightening. */
.eq-chip { display: flex; align-items: stretch; border: 1px solid var(--border-2); border-radius: 8px; background: none; color: var(--ink); font: 500 12.5px var(--sans); }
.eq-toggle, .eq-body { margin: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; width: auto; cursor: pointer; }
button.eq-toggle:hover:not([disabled]), button.eq-body:hover:not([disabled]) { filter: none; background: var(--hover); }
.eq-toggle:focus-visible, .eq-body:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.eq-toggle { flex: none; display: flex; align-items: center; justify-content: center; min-width: 34px; padding: 5px 8px; border-radius: 8px 0 0 8px; }
.eq-body { flex: 1 1 0; min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; padding: 5px 9px 5px 2px; border-radius: 0 8px 8px 0; }
div.eq-body { cursor: default; }
/* W24b E7: a small heading per group, a full-width row of the chips' grid; the chips flow in the same grid under it, at any width */
.eq-group-head { grid-column: 1 / -1; display: flex; align-items: center; margin: 8px 0 0; font: 600 13px var(--sans); color: var(--ink); }
.eq-group-head:first-child { margin-top: 0; }
/* PHASE 30: the tick in front of a group's name (the owner, 2026-10-17). The whole name is its label, so a press on it ticks; the box takes the page's
 * accent and the mixed dash is the browser's own. A phone gets a taller row and a larger box. */
.eq-group-label { display: inline-flex; align-items: center; gap: 8px; min-height: 28px; padding-right: 4px; cursor: pointer; }
.eq-group-tick { flex: none; width: 16px; height: 16px; margin: 0; cursor: pointer; accent-color: var(--accent); }
.eq-group-tick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* PHASE 32: the custom window (windowpick.js; the owner, 2026-10-17: a calendar for the day a window starts on). A button after the window buttons that opens a
 * small panel with the browser's own date field and a line of words; it takes the free space so the page's other buttons stay at the right. */
.win-custom-wrap { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; margin-right: auto; }
.win-custom-wrap .win-custom[aria-pressed="true"] { border-color: var(--accent); color: var(--ink); }
.win-panel { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; }
.win-panel[hidden] { display: none; }
.win-from { display: inline-flex; align-items: center; gap: 6px; font: 500 12.5px var(--sans); color: var(--ink-2); }
.win-date { font: 500 12.5px var(--sans); color: var(--ink); background: none; border: 1px solid var(--border-2); border-radius: 8px; padding: 4px 8px; min-height: 28px; }
.win-date:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.win-note { font: 500 12px var(--sans); color: var(--muted); max-width: 46ch; }
.win-note[data-bad] { color: var(--bad-ink); }
@media (max-width: 520px) { .win-date { min-height: 40px; } .win-custom-wrap .win-custom { min-height: 36px; } }
@media (max-width: 520px) { .eq-group-label { min-height: 40px; } .eq-group-tick { width: 20px; height: 20px; } }
.eq-group-avg { margin-left: 6px; font-size: 12px; font-weight: 500; color: var(--muted); font-variant-numeric: tabular-nums; }
/* The page body and the fund's chart take turns (a `hidden` attribute loses to any rule that sets `display`, so it is said here) */
.eq-page .eq-main[hidden], .eq-page .eq-chart-host[hidden] { display: none; }
.eq-swatch { flex: none; align-self: center; width: 10px; height: 10px; border-radius: 2px; box-sizing: border-box; background: var(--eqc); border: 2px solid var(--eqc); }
.eq-tick { font-weight: 700; }
.eq-name { flex: 1 1 140px; color: var(--ink-2); }
.eq-val { font-variant-numeric: tabular-nums; font-weight: 600; }
.eq-cagr { font-variant-numeric: tabular-nums; color: var(--ink-2); }
.eq-status { color: var(--muted); }
.eq-chip.is-hidden .eq-swatch { background: transparent; }
.eq-chip.is-hidden .eq-tick, .eq-chip.is-hidden .eq-name { text-decoration: line-through; color: var(--muted); }
.eq-notes .card-note { margin: 6px 0 0; }
.eq-notes .eq-foot { margin: 6px 0 0; font-size: 12px; }
/* PHASE 24 equities pages: end */

