/* == tokens/tokens.css == */
/* tokens.css — the SINGLE materialisation of design/TOKENS.json.
 *
 * Strategy: an ALIAS LAYER, not a rename (see design/PLAN.md §2). This file
 * does two jobs and nothing else:
 *
 *   1. Declares the Design 2.0 palette under its own `--ra-*` names, one
 *      custom property per design/TOKENS.json `colors` entry.
 *   2. Re-points every LEGACY variable (--bg-0, --pos, --neg, --ch-ax, …) at
 *      a 2.0 value, IN PLACE, under the exact name it has today. No consumer
 *      file is renamed or edited to adopt the palette.
 *
 * Rule (design/PLAN.md §9.1): after this file exists, it is the ONLY file in
 * platform/frontend/ allowed to contain a colour literal. Every other file
 * reads a var(--…). A dev who needs a colour that does not exist asks the
 * architect — they do not invent a hex.
 *
 * Rule (§9.3): never rename a --ch-* custom property. features/chart/render.js and
 * features/chart/trade-markers.js read these by literal string name via chTC()
 * (render.js:94-109); a renamed --ch-* returns "" from getComputedStyle,
 * `strokeStyle=""` is silently ignored by canvas, and the chart keeps
 * painting the OLD colour with no build error and no lint error. Only the
 * VALUE changes here, never the NAME.
 *
 * Exempt from token_lint by the tokens.css glob in devstudio.json
 * (careful: literal "star-star-slash" text here would close THIS comment
 * early and silently truncate the whole :root block below — it did, once).
 */

:root {

  /* ============================================================
     1. Design 2.0 palette — one custom property per design/TOKENS.json
        `colors` entry (excluding the `legacy.*` group, materialised in
        section 3 below, right next to the --ch-* names that read them).
     ============================================================ */
  --ra-surface-base: #060A10;
  --ra-surface-panel: #0D1319;
  --ra-surface-header: #0D1114;
  --ra-surface-tape: #15191C;
  --ra-surface-card: #23282C;
  --ra-surface-card-elevated: #2A2F33;
  --ra-surface-subtle: #50565A;
  --ra-surface-inset: #1C2024;
  --ra-surface-inset-deep: #080C10;
  --ra-surface-chip: #1A2332;
  --ra-surface-scrollbar-track: #262B33;
  --ra-surface-scrollbar-thumb: #737A85;
  --ra-surface-overlay: #1E252D;
  --ra-surface-stat-tile: #1C2635;
  --ra-surface-table-header: #151F28;
  --ra-surface-bubble-user: #24282E;
  --ra-surface-plot: #171D20;
  --ra-surface-price-chip: #1E2326;
  --ra-surface-rule-card: #0D1216;
  --ra-surface-btn-danger: #0B0E14;
  --ra-surface-black: #000000;
  --ra-surface-deepest: #010203;
  --ra-surface-panel-alt: #0D1117;
  /* S46 task #35 (design/TOKENS.json colors["surface.chip-count"]) — the
     news duplicate-count chip fill (`×3`/`×2`, NEWS_SPEC_S46.md §5.3),
     723:2751 x3. Light value in the [data-theme="light"] block below: this
     one DOES need a real derivation (not a carried hex) — task #38 measured
     that carrying #182029 into light passes contrast (5.72:1) but paints a
     near-black chip on a near-white page, which a ratio can't catch because
     it isn't a contrast defect. Derived the same way --ra-surface-inset
     already derives its light value: one tier darker than the page
     (`var(--bg-2)`), not a from-scratch invention. */
  --ra-surface-chip-count: #182029;
  /* S19 §2.1 (PLAN_S19.md): the frame's `Session Dropdown` component fill,
     read at EIGHT nodes that all agree -- 450:1087, 451:1165, 451:1168,
     450:1080, 451:1209 (Sound Rules 448:1631) and 443:716, 443:724, 443:728
     (Replay 443:707). A NEW name, not a reuse of --ra-text-neutral-900
     (same hex, #1A1A1A): that token is bound as a TEXT role and consumed as
     one (widgets.css:1277). Binding a SURFACE role to a TEXT name is how
     --ra-text-axis was once misread as a chart token -- two tokens sharing
     one hex under two roles is the existing convention here (--ra-chart-axis
     / --ra-text-axis, same #606060). design/TOKENS.json colors["surface.field"]. */
  --ra-surface-field: #1A1A1A;
  --ra-border-default: #1A222B;
  --ra-border-card: #23282C;
  --ra-border-strong: #1A2332;
  --ra-border-track: #333840;
  --ra-border-switch: #202A31;
  /* S46 task #35 (design/TOKENS.json colors["border.hairline"] /
     ["border.control"]) — the news row separator (NEWS_SPEC_S46.md §5.1,
     723:2751 x19). It measured 1.1-1.2:1 in DARK against a 3:1 non-text
     floor (task #38, NEWS_CONTRAST_S46.md §6) — task #38 argues the hairline
     isn't the kind of boundary 1.4.11 covers (a row divider that could be
     deleted without losing information). NO light override below: the SAME hex
     measures 15.6:1 / 14.4:1 in light (task #38, carried unchanged) — a
     dark line reads fine on a light page without any derivation, unlike a
     dark FILL (see --ra-surface-chip-count above). */
  --ra-border-hairline: #23282c;
  --ra-border-hairline-w: 1px;   /* S51 WP6-A: the hairline WIDTH, so a ds/ file can draw a border with no literal */
  --ra-border-control: #1B242E;
  --ra-text-primary: #EEF1F1;
  --ra-text-secondary: #9AA1A4;
  /* S32 #42 (owner ruling, S31 task #11 dialog, durable copy S31-R7.2):
     "the one kind of pixel movement he has approved" — the Figma value
     failed the readability standard in DARK theme (design-qa's canvas-
     normalized baseline, design/s32/qa/acceptance/run-20260827T-grey-dark/
     grey_baseline_readings.json: worst measured ratio 3.04 against the
     panel's own lightest real ground, rgb(35,40,44) — 66 elements below
     4.5:1 across the six sampled panels). LIGHT theme is untouched (it
     already passes; its own value lives at widgets.css:1085, a SEPARATE
     declaration under [data-theme=light]).
     Derivation, same method as S19-R7's rule-colour set: canvas-normalized
     (a <canvas> fillStyle/getImageData round-trip, never a hand oklch->
     sRGB formula or a regex — the S16-B1 class of bug design-qa's own
     first baseline attempt hit and self-caught) binary search on OKLCH
     lightness, hue 70 / chroma .012 held fixed (the same neutral hue this
     token's LIGHT-theme sibling already uses, oklch(0.52 0.012 70) —
     not a new hue invented for dark), walked UP from the old L until the
     WORST of this token's measured grounds cleared 4.5:1 with a real
     margin (4.59, not 4.50 exactly — a value sitting on the line
     re-fails the moment a future ground gets 1 shade darker).
     oklch(0.65 0.012 70) -> #948E87. Hierarchy re-verified after the
     raise, canvas-normalized relative luminance: text1 .874 > text2 .350
     > text3(new) .274 > text4(new, below) .226 — still strictly
     decreasing, primary/secondary/tertiary/faint keep their order. */
  --ra-text-tertiary: #948E87;
  --ra-text-muted: #6B7276;
  --ra-text-bright: #E6EDF3;
  --ra-text-dim: #6B7A88;
  /* S32 #42, same ruling and method as --ra-text-tertiary above — measured
     WORSE (dark floor 2.32 vs tertiary's 3.04, against its own worst
     ground rgb(20,25,35)). oklch(0.61 0.01 70) -> #87827D (chroma .01,
     matching this token's own light-theme sibling, widgets.css:1086 —
     oklch(0.64 0.01 70)). This token ALSO measures below 4.5:1 in LIGHT
     theme (design-qa's grey-light baseline: worst 2.82-3.37 depending on
     ground, screener's .scr-chart-tf). NOT fixed here — "light theme
     untouched" is this task's own explicit boundary, and the owner's
     ruling was about the dark grey specifically; light theme's failure on
     this token is a SEPARATE, not-yet-ruled-on defect. Filed, not
     absorbed: design/s32/dev_a/text4_light_theme_still_fails.md. */
  --ra-text-faint: #87827D;
  --ra-text-subtle: #8B959E;
  --ra-text-on-accent: #010203;
  --ra-text-inverse: #FFFFFF;
  /* Text on a hovered control: pure white on the dark theme, --text-1 on the light one (light
     block below). --ra-text-inverse itself has no light value: it is also the white that must
     stay white on solid fills (ticks, tags, buy / sell), so hovers use this instead. */
  --ra-text-hover: var(--ra-text-inverse);
  --ra-text-thumb: #E5EBED;
  --ra-text-slate: #C7D1DE;
  --ra-text-slate-dim: #93A6C5;
  --ra-text-switch-thumb: #62737E;
  --ra-text-axis: #606060;
  --ra-text-neutral-300: #A0A0A0;
  --ra-text-neutral-700: #404040;
  --ra-text-neutral-900: #1A1A1A;
  --ra-text-ellipse: #E2E8F0;
  --ra-text-scrollbar-alt: #667385;
  --ra-text-checkbox-grey: #596166;
  --ra-text-vol: #5A626A;
  /* S46 task #35 (design/TOKENS.json colors["text.inactive"]) — the news
     absolute-time line (`08:02:41`, 10.5px, NEWS_SPEC_S46.md §5.3) and the
     search placeholder. NOT a light-derivation case: task #38 measured this
     hex at 3.28:1 against --ra-surface-panel in DARK, against a 4.5:1 floor
     for text under 18px — a dark-theme breach that exists before any light
     value is considered, escalated to the owner as a frame defect (the
     frame specifies #5C6874 at 10.5px and the frame is dark-only). No
     override in [data-theme="light"]: the SAME hex measures 5.23:1 in
     light — carrying it unchanged already passes there, so light is not
     where the problem is and not where a fix would belong even if one were
     minted here. Do not correct the dark value on this task's authority. */
  --ra-text-inactive: #5C6874;
  --ra-accent-primary: #0FD4BB;
  --ra-accent-primary-soft: #7EE0CD;
  --ra-accent-secondary: #5AA597;
  --ra-accent-dark: #35A08B;
  --ra-teal-950: #022F2E; /* design-sync 2026-10-03: Figma teal/950, the empty-state dot grid */
  --ra-accent-deep: #1E826E;
  --ra-accent-tint: #E6FBF7;
  --ra-accent-blue: #6D9EE0;
  --ra-accent-blue-deep: #1E6B8A;
  --ra-accent-blue-ink: #0C4A6E;
  --ra-semantic-success: #00E676;
  --ra-semantic-success-alt: #4CAF50;
  --ra-semantic-warning: #F59E0B;
  --ra-semantic-error: #F87171;
  --ra-semantic-error-strong: #EF4444;
  --ra-semantic-error-deep: #9E3838;
  --ra-semantic-danger: #FF5252;
  --ra-semantic-info: #3B99FC;
  /* S17-R31 -- the ONE token S17 mints beyond --ra-link-ink. Frame 405:193 fills the
     five OVERLAY row icons #29ABE2 (the five MODE rows are #FFFFFF). It is a raw vector
     fill in the frame, NOT a design variable: absent from design/TOKENS.json's 135
     colours and from this file under any name -- dev-b established that and refused to
     mint it itself, correctly, this file being frozen to the lead.
     TWO VALUES, NOT ONE, AND THE REASON IS MEASURED: #29ABE2 is 7.13:1 on the dark
     popover and 2.62:1 ON WHITE -- under the 3:1 non-text minimum. A single-value mint
     would have shipped a light-theme defect of precisely the class S17-R26 ruled on.
     The light value below is OURS, a DECLARED DIVERGENCE (S17-R28 O-1): no light-theme
     frame exists for any node, so it cannot be frame-derived and is not claimed to be. */
  --ra-chart-overlay-icon: #29ABE2;
  --ra-market-up: #0FD4BB;
  --ra-market-down: #EF4444;
  --ra-market-up-bright: #38F2CE;
  --ra-market-up-bright-alt: #38F2CF;
  --ra-market-down-bright: #FF5555;
  --ra-market-up-flow: #00E5C9;
  --ra-market-down-flow: #FF5376;
  --ra-market-down-muted: #C96672;
  --ra-market-down-vivid: #FF5F57;
  --ra-market-sell: #FF4D6D;
  --ra-market-buy: #10B981;
  --ra-market-session-live: #76B900;
  /* S46 task #35 (design/TOKENS.json colors["signal.bull"/"bear"/"neutral"])
     — a NEW category, distinct from --ra-market-* and --ra-semantic-* (existing
     tokens with different hexes: --ra-market-up #0FD4BB vs signal.bull
     #35D07F — not the same colour, not a rename target). Two known
     consumers today, neither built yet: the news impact-bar segments
     (NEWS_SPEC_S46.md §5.4, graphical, 3:1 floor) and the duplicate-count
     chip TEXT (§5.3, `×3`/`×2` on --ra-surface-chip-count, 4.5:1 floor —
     signal.neutral only). Light values below are DERIVED and MEASURED
     (task #38's numbers + design/s46/qa/rg_colors_s46.js), not carried: all
     three fail their floor in light if the dark hex is reused unchanged
     (task #38 §7.3 measured 1.71 / 2.75 / 2.42 against a plain carry). The
     bull/bear derivation models the impact bar's own tint the same way this
     file's shipped sentiment pill already tints a signal colour (news.css
     `.pill.pos`/`.pill.neg`: color-mix(in oklch, COLOR 14%, transparent))
     rather than inventing a different assumption for an unbuilt component —
     when #24 builds the real impact bar, re-verify against its actual CSS,
     the same way every other section-1e-style modelled value in this file
     gets re-checked once a real consumer exists. */
  --ra-signal-bull: #35D07F;
  --ra-signal-bear: #F2565F;
  --ra-signal-neutral: #8C9AA8;
  --ra-chart-bg: #0D1319;
  --ra-chart-grid: #1A2332;
  --ra-chart-grid-sub: #131C28;
  --ra-chart-axis: #606060;
  --ra-chart-candle-up: #0FD4BB;
  --ra-chart-candle-down: #EF4444;
  --ra-chart-volume-up: #0FD4BB;
  --ra-chart-volume-down: #EF4444;
  --ra-chart-ema-fast: #B39DDB;
  --ra-chart-ema-slow: #5AA597;
  /* S27-R9 (owner: give EMA-100 its own colour, no longer shared with
     EMA-21). MINTED, not a Figma verbatim value like its neighbours above
     — there is no frame for this, it's an ad-hoc owner ruling. A vivid
     magenta/pink: measured min sRGB distance to every other overlay/
     sub-pane line is ~79 (closest is ema50/rsi's shared lavender-purple;
     every other line is ≥92) — design/s27b/dev-a/
     indicator_legend_readings.json task_22 entry has the full table. */
  --ra-chart-ema100: oklch(0.72 0.19 340);
  --ra-chart-vwap: #E5A663;
  --ra-chart-vwap-line: #F59E0B;
  --ra-chart-price-line: #7EE0CD;
  --ra-chart-crosshair: #FFFFFF;
  --ra-state-hot-bg: #380F0F;
  --ra-state-hot-border: #6B1F1F;
  --ra-state-hot-fg: #FF5959;
  --ra-state-warm-bg: #2E260D;
  --ra-state-warm-border: #61521A;
  --ra-state-warm-fg: #FFD940;
  --ra-state-chip-bg: #00323C;
  --ra-state-chip-border: #00505A;
  --ra-state-tag-bg: #112E24;
  --ra-state-danger-bg: #2D191E;
  --ra-state-checkbox-blue: #2659A6;
  /* seal/checkbox (Figma acDx3hoowFphwB8tGtqIPi 3057:26338, ds/atoms/checkbox.css):
     the Control/* styles plus the fills its Hover / Pressed / Focus / Disabled
     variants carry. The unchecked fill is --bg-2 (#15191C, Control/Selected). */
  --ra-control-checked: #3B82F6;
  --ra-control-checked-hover: #4F8DF7;
  --ra-control-checked-pressed: #2F6FD6;
  --ra-control-checked-focus: #8CB5FA;
  --ra-control-checkmark: #FFFFFF;
  --ra-control-checkbox-border: #5F676E;
  --ra-control-checkbox-hover-bg: #1B2227;
  --ra-control-checkbox-hover-border: #828A91;
  --ra-control-checkbox-pressed-bg: #101519;
  --ra-control-checkbox-disabled-border: #595F6680;
  /* seal/button Neutral MD (Figma 3057:26180, the Replay Control panel's Play, 3057:26616): its stroke and label. */
  --ra-control-button-border: #4B5258;
  --ra-control-button-ink: #D2D7DA;
  --ra-state-star: #598CD9;
  --ra-state-neutral: #D9D9D9;
  /* S36 OT-3 / OT-4 (design/PLAN_S36.md §11 D14, D15): the session recorder's
     RECORDING-LIVE chip. Sourced from `seal/chip-status` 233:94's HOT-Hover
     state in the design file, which the journal frames reuse deliberately
     rather than minting a new red in Figma. They are NOT the hot pair three
     lines above -- checked, not assumed: hot is #380F0F / #6B1F1F, these are
     #240D0D / #471414, and neither value appears anywhere else under tokens/.
     Minted because a colour cannot be approximated the way a radius can: one
     pixel of radius is invisible, a wrong red is not. */
  --ra-state-rec-bg: #240D0D;
  --ra-state-rec-border: #471414;
  /* The record glyph's ink (ring + core) in the header's Record-session chip:
     `record` inside `seal/chip-tiny` 3020:32651, file acDx3hoowFphwB8tGtqIPi.
     Minted for the same reason as the pair above — the nearest reds here
     (market.down-flow #FF5376, semantic.danger #FF5252) are other colours. */
  --ra-state-rec-ink: #FF5C7A;
  /* S36 OT-5 (design/PLAN_S36.md §11 D16): the journal entry-row hover fill --
     the accent at 20%. Written as a color-mix over the accent token rather
     than as a flattened literal so it FOLLOWS the accent instead of drifting
     from it, and so it needs no second definition in the light-theme block.
     Literal-free by construction: token_lint's colour regexes match
     `oklch(<number>`, and `in oklch,` is a colour SPACE argument, never a
     value. A dev reaching for rgba(53,160,139,.2) is blocked, correctly. */
  --ra-state-row-hover: color-mix(in oklch, var(--ra-accent-dark) 20%, transparent);
  --ra-link-cyan: #0AC6E6;
  --ra-link-magenta: #F63BF3;
  /* S13-A6e / owner ruling S13-R44a: blue and purple are a DECLARED
     DIVERGENCE from 362:1608 (frame-exact there, like the other four).
     Measured against the panel surface rgb(35,40,44), the frame values
     scored 1.44 (blue) and 1.36 (purple) -- WORSE than an ungrouped tile's
     own 2.37, i.e. joining either group made the route marker HARDER to
     see than not being routed at all. Re-picked at the SAME hue (blue
     224.4deg -> 224.0deg, purple 263.5deg -> 263.6deg -- both within
     0.5deg, so "reads as blue"/"reads as purple" holds) and raised
     lightness only, until contrast >= 4.5 against the surface (target per
     R44a; landed at 4.80/4.79, the same margin the readable four already
     carry). Mutual distinguishability re-checked pairwise across all six
     after the move: full readings + the CIELAB deltaE + hue-angle tables
     in design/s13/dev_a/S13-A6e_evidence.md. Do NOT restore the frame
     values as a parity fix -- this is the owner's call, recorded here and
     in design/TOKENS.json (colorSources.link.blue/.purple), same filing as
     the removed `ribbon` row (design/s13/dev_b/CD755_ribbon_removed.md). */
  --ra-link-blue: #6E8FEA;
  --ra-link-orange: #F97316;
  --ra-link-yellow: #FACC15;
  --ra-link-purple: #A87CEC;
  /* S13-A5 (design/PLAN.md §S13.3.2): the ONE token this session mints.
     402:209's selection-ring stroke (strokeAlign OUTSIDE, 1.5px) -- numerically
     equal to --ra-text-inverse / --ra-chart-crosshair but semantically neither
     (a selection affordance, not text or a chart element). design/TOKENS.json
     colors.link.ring carries the same value + provenance. */
  --ra-link-ring: #FFFFFF;
  /* S17-R1 (lead, Addendum S17-A item 1): the ink for a glyph sitting ON a
     group identity fill, once the group signal moves off the 3px top stripe
     and onto the link button itself. The ONE token S17 mints.

     WHY A VALUE AND NOT --ra-text-inverse: the operand underneath is a group
     IDENTITY colour, not a surface, so the ink must be chosen against six
     fixed fills rather than against a theme. --ra-text-inverse is #FFFFFF and
     is ALSO an open owner decision (HANDOFF_S17 §D, the F-13 class remainder)
     -- binding this to it would make a settled contrast result hostage to an
     unsettled one.

     WHY DARK, when the owner's own reference crop (design/owner-reports/
     s17-fb-644.png) paints a LIGHT glyph #E6FBF7: measured, his glyph fails
     BOTH the 4.5:1 text floor and the 3:1 non-text floor on every one of the
     six group colours (worst yellow 1.42, cyan 1.91). The addendum that
     carries his order also requires "glyph contrast measured per theme, not
     assumed", so ratifying the crop's pixels would fail its own term. Dark
     clears 4.5:1 on all six:
       cyan 7.69 . magenta 5.14 . blue 5.10 . orange 5.63 . yellow 10.31 .
       purple 5.08 (worst)
     Recomputed by the lead off the six values above, not inherited.

     WHY #1A2332 and not #1A1A1A (which scores marginally better, worst 5.60):
     #1A2332 is the ink the frame itself uses on the y-axis price badges
     (6.59-10.10:1), and a small coloured chip carrying ink is the closest
     authored analogue to this button. Consistency with the nearest precedent
     over 0.5 of a contrast point.

     NO LIGHT-THEME OVERRIDE, DELIBERATELY -- and it is measured, not assumed:
     none of the six --ra-link-* colours carries one (widgets.css:1030
     overrides exactly one family member, --ra-link-ring, and leaves the six
     alone), so the fill is theme-invariant and both operands of every ratio
     above are fixed. cf06961c's own acceptance term measured the consequence
     directly: cyan resolves to rgb(10,198,230) "in BOTH dark and light,
     identically". THE BOUND, so it can expire honestly: this collapses if any
     of the six ever gains a light override -- at which point this token needs
     one too, re-measured, not guessed.

     The FILLED state it serves is a DECLARED DIVERGENCE (S17-R2): the design
     system authors the colour PICKER (362:1608) and has never authored a
     RESULT for a grouped panel. Established twice, independently -- the S13
     search recorded in components.css, and S17's pixel sample of 341:3788
     returning #23282C x376px with ZERO cyan. */
  --ra-link-ink: #1A2332;
  --ra-rule-orange: #FF9F00;
  --ra-rule-violet: #CB89FA;
  --ra-rule-sky: #59AAF8;
  --ra-rule-mint: #6FD087;
  --ra-rule-green: #4CD676;
  --ra-rule-tangerine: #FF823F;
  --ra-rule-peach: #FE9B60;
  --ra-rule-steel: #7392B3;

  /* Radii + display font (design/TOKENS.json `radii.conventions` /
     `fontFamilies.display`) — not colours, materialised here anyway so the
     Rail/header this session builds and the card system dev-b builds read
     the same names. Legacy --r-sm/--r/--r-lg (base.css) are untouched: they
     still serve un-migrated files and are a separate, smaller radius scale
     by design (§2 of PLAN.md scopes the alias layer to colour). */
  --ra-radius-xs: 4px;
  --ra-radius-sm: 6px;
  --ra-radius-md: 8px;
  --ra-radius-card: 14px;
  --ra-radius-xl: 16px;
  --ra-radius-pill: 27px;
  --ra-radius-full: 999px;
  /* S51 WP4: the two small radii the panels draw on tags, chips and handles (3px at 40+ sites,
     2px at 15) -- minted so the kit variants that reproduce those panels carry no literal. */
  --ra-radius-2xs: 3px;
  --ra-radius-3xs: 2px;
  --font-display: "Space Grotesk", var(--font-sans);

  /* ============================================================
     1b. Type scale (design/PLAN_S19.md §2.3) -- the frame's SEVEN named
     text styles (design/TOKENS.json `typography.*`, all seven already
     registered there), materialised as CSS custom properties for the
     first time. `font:` SHORTHAND deliberately -- a consumer that reads
     the size cannot skip the weight, which is exactly the drift the S19
     census measured (three pop-up roots at 10/11/12, six controls at
     9.5/10/13). `-ls` is a separate property (letter-spacing isn't part
     of the `font` shorthand). `text-transform` is NOT in any of these --
     only Heading/H3 carries textCase:UPPER, on one node; the shared class
     that consumes it (.pop-head-title) declares the case itself, not the
     token, or every other consumer of the scale would inherit an unwanted
     uppercase. Nodes, per style (design/PLAN_S19.md §2.3 table): h3-card
     447:1059 · h4-subtitle 448:1649 · h6-table-header 13 nodes incl.
     451:1094/448:1654/451:1160/1162/1233/1235 · body-small 9 nodes incl.
     451:1182/1229/1211/1212 · body-medium 450:1089/451:1167/450:1082 (the
     three carets) · button-medium 448:1737/451:1250 · caption-helper
     449:1063/451:1118/448:1743/448:1744. */
  /* design-sync 2026-10-03: card-title, table-header, body-medium, button-medium, widget-title,
     nav-tab, label-form and label-stat realigned to Platform's local text styles (owner: Figma's
     text styles are the truth). */
  --ts-h3-card-title:      500 15px/24px var(--font-display); --ts-h3-card-title-ls: 0;
  --ts-h4-card-subtitle:   600 15px/20px var(--font-sans);    --ts-h4-card-subtitle-ls: 0;
  --ts-h6-table-header:    500 11px/16px var(--font-sans);    --ts-h6-table-header-ls: 0.2px;
  --ts-body-small:         400 12px/16px var(--font-sans);    --ts-body-small-ls: 0;
  --ts-body-medium:        400 14px/20px var(--font-sans);    --ts-body-medium-ls: 0;
  --ts-button-medium:      500 14px/20px var(--font-sans);    --ts-button-medium-ls: 0.3px;
  --ts-caption-helper:     400 12px/18px var(--font-sans);   --ts-caption-helper-ls: 0.1px;
  /* FOUND WHILE MEASURING THE TOKEN BELOW, NOT THIS SESSION'S TO FIX: this
     rule reads 10px, but `design/TOKENS.json`'s own `typography.caption
     .helper` (its declared Figma source, `figmaVariable: "Caption/Helper"`)
     records size **9**, same 16px line-height. The materialised value here
     disagrees with its own source of truth by 1px. Flagged for whoever owns
     the token audit — not changed here, since every existing consumer of
     `--ts-caption-helper` would move under a same-name value fix, which is
     exactly what this file's single-writer rule (plan §1.3) exists to gate.

  S44 task #41 (CD-S44-090), MINTED WITH EXPLICIT AUTHORITY (team-lead,
     2026-09-05): the gamma ladder's row pitch — `gex.css` `.gex-row` — has
     an owner-set BUILD target of 15px pitch / 14px tall (the frame's own
     measurement, live again after OR-S44-14 cancelled the chart-binding
     reading that had marked it "superseded"). No existing `--ts-*` token
     below `--ts-h6-table-header`'s 12px shares anything but a 16px line-
     height (measured: `--ts-body-small` 12/16, `--ts-caption-helper`
     10/16 as shipped above) — even the frame's OWN 9px row-label spec pairs
     with a 16px line-height (`caption.helper` above), which cannot fit a
     14px row at any padding >=0. Reaching the frame's row-GEOMETRY number
     needs a tighter line-height the frame's own type styles never pair
     with a caption-sized font — this is a derived value for a layout
     constraint, not a mirrored Figma text style, and does not carry a
     `figmaVariable` name in `design/TOKENS.json` for that reason.
     Derivation, shown so the next reader can check it rather than trust it:
     row content-box height 14px = 2px padding-top (`--ra-sp-3xs`, already
     the tightest existing spacing token) + line-height + 2px padding-
     bottom => line-height = 10px. Font-size 9px matches the frame's own
     CD-S44-094/096 measurement (and, once the drift noted above is fixed,
     `--ts-caption-helper`'s true source value) exactly. This is an
     ADDITION, not a same-name edit — no existing consumer moves. */
  --ts-caption-tight:      400 9px/10px var(--font-sans);       --ts-caption-tight-ls: 0;
  /* GEX panel frame 780:2557 (2026-09-09, design/s50/GEX_PARITY_AUDIT.md
     GP-04/05/07/10): the ladder's strike + value labels are Inter 400 11px
     (780:2657), the NET|SPLIT segment and the Live label Inter 500 11px
     ls 0.2 (799:17082, 780:2726). ADDITIONS — no existing consumer moves. */
  --ts-caption-11:         400 11px/16px var(--font-sans);      --ts-caption-11-ls: 0;
  --ts-caption-11m:        500 11px/16px var(--font-sans);      --ts-caption-11m-ls: 0.2px;
  /* GP-20 (780:2564; the GEX cut, 2026-10-05): the GEX card's symbol subtitle is Inter 400
     9px/16 in the frame -- Caption/Helper's source value in design/TOKENS.json
     (`typography.caption.helper`, 9/16), while `--ts-caption-helper` above now materialises
     at 12/18 (the design-sync 2026-10-03 realign). This step carries the frame value
     (TOKENS.json `typography.caption.9`); it replaces the panel's `--ts-caption-tight` +
     `line-height: 16px` pair. ADDITION — no existing consumer moves. */
  --ts-caption-9:          400 9px/16px var(--font-sans);       --ts-caption-9-ls: 0;
  /* S51 WP6-A (2026-09-14, design/PLAN_S51.md section 7): two APP-MINTED steps, not in
     design/TOKENS.json's typography (the frame never named them) -- the census of the
     panels' own sheets (design/s51/wp6/COMPONENT_MAP.md) found `font-size: 10px` written
     as a literal in ~150 rules and a 9px tracked uppercase label in ~40; a step the app
     uses that often is a step of the scale. Consumed through ds/atoms/text.css
     (`.t-caption-10`, `.t-eyebrow`); retiring either later is a one-token edit that
     moves every site at once. ADDITIONS -- no existing consumer moves. */
  --ts-caption-10:         400 10px/14px var(--font-sans);      --ts-caption-10-ls: 0;
  --ts-eyebrow:            500 9px/12px var(--font-sans);       --ts-eyebrow-ls: 0.12em;

  /* The remaining NINE of the frame's sixteen named text styles
     (design/TOKENS.json `typography.*`, same source as the seven above).
     S19 materialised seven -- the ones pop-up 448:1631 consumes -- and
     stopped there, correctly, because that was its mandate. S20's mandate is
     the whole app, so the other nine land now, generated from the Figma REST
     dump by design/build_tokens.py; no number here was typed by hand, and the
     generator re-checks the original seven against the same source on every
     run (0 discrepancies -- S19 typed all seven correctly).

     ADOPTION HAZARD, read this before consuming one (owner ruling S20-R2):
     the `font:` shorthand sets family + weight + size + line-height in ONE
     declaration. A rule that today writes only `font-size: 12px` and INHERITS
     its weight will silently change weight the moment it adopts the shorthand.
     The ruling is that no pixel moves, so a site adopts a --ts-* only when the
     computed style is identical before and after -- measure it, do not reason
     about it (design/measure_dom.js). Where it is not identical the site is
     off-scale and takes a declared exception in section 1e instead. That is
     not a workaround, it IS the ruling. */
  --ts-h2-section:          500 22px/28px var(--font-display);    --ts-h2-section-ls: -0.2px;
  --ts-h5-widget-title:     500 13px/18px var(--font-sans);       --ts-h5-widget-title-ls: 0.1px;
  --ts-body-large:          400 15px/22px var(--font-sans);       --ts-body-large-ls: 0;
  --ts-label-form:          400 12px/20px var(--font-sans);       --ts-label-form-ls: 0.3px;
  /* S51 WP4: the small TONE TAG the panels draw ~15 times (trade side, heat tier, urgency, flags):
     mono 600 9px, uppercase, ls .06em. App-derived, like the --ra-xfs-* entries below. */
  --ts-tag:                 600 9px var(--font-mono);         --ts-tag-ls: 0.06em;
  --ts-label-nav-tab:       500 10px/12px var(--font-display);    --ts-label-nav-tab-ls: 0.3px;
  --ts-label-badge:         500 12px/16px var(--font-sans);       --ts-label-badge-ls: 0.5px;
  --ts-label-status-upper:  500 10px/14px var(--font-sans);       --ts-label-status-upper-ls: 0.4px; /* Figma label/status: the toast title */
  --ts-button-small:        500 12px/16px var(--font-sans);       --ts-button-small-ls: 0.5px;
  --ts-dropdown-menu-item:  400 13px/13px var(--font-sans);       --ts-dropdown-menu-item-ls: 0;
  --ts-dropdown-icon-label: 700 12px/12px var(--font-sans);       --ts-dropdown-icon-label-ls: 0;

  /* A SEVENTEENTH text token, and deliberately not grouped with the sixteen
     above -- S20-R53, owner ruling 2026-08-21, escalated from the
     --ts-label-nav-tab misadoption at 80 sites (S20-R8's audit, task #25).
     THIS IS THE B1 CLASS, THE SAME SHAPE AS SECTION 1e'S B1 ROWS: drawn on
     67:2, never named. Do not read this entry as a seventeenth named
     Figma style -- re-measured live, all five source nodes carry an
     EMPTY styles map (no style binding at all). The frame HAS an answer for
     10px text that is not a nav tab; it just never gave that answer a
     name, the same gap the --ts-label-nav-tab misadoption exposed.
     SOURCE: five odd-numbered sibling nodes at 432:5635/5637/5639/5641/5643
     (BP, DAY, NIGHT, UPNL, RPNL) -- the even ids in that range are null,
     the component is the five odd ones. Read at file version
     2389946676565475445. Weight 600, size 10px, family Inter (var(--font-
     sans), never var(--font-display) -- that is the whole reason the
     nav-tab adoption was wrong at these sites).
     12.1px line-height, not the fuller Figma-auto decimal: lineHeightUnit
     is INTRINSIC_%, the same auto-leading mechanism already measured for
     the 1.21x family (section 2c below) -- 10 * 1.21 = 12.1, rounded to
     the same precision the .t-u-* utility classes already use for exactly
     this class of value.
     textCase:UPPER is real on all five source nodes and is DELIBERATELY
     NOT baked into this token, on --ts-h3-card-title's own precedent: the
     shared class a site uses declares the case, not the token -- binding
     it here would force uppercase on every future consumer whether or
     not their markup wants it. */
  --ts-label-stat: 500 12px/16px var(--font-sans); --ts-label-stat-ls: 0.3px;

  /* ============================================================
     1d. Spacing scale -- the first 2.0 spacing tokens this product has had
     (design/TOKENS.json `spacing`). Until now every one of the ~1354
     padding/margin/gap declarations under platform/frontend/ was a bare
     literal, which is exactly why 38 distinct values are in use: nothing
     named them, so nothing stopped the 39th from being added.

     *** THE PREFIX IS --ra-sp-, NOT --sp-, AND THAT IS NOT A STYLE CHOICE. ***
     css/base.css:20-25 ALREADY declares --sp-1 .. --sp-6, a DIFFERENT scale
     (4/6/8/12/16/24, each multiplied by var(--density)), and base.css loads
     AFTER this file (index.html:24-25), so it WINS every collision. Written
     under the obvious prefix, two of these tokens would have been silently
     overridden and resolved to a value from the other scale:
         --sp-3  frame says 3px  ->  base.css resolves it to  8px
         --sp-5  frame says 5px  ->  base.css resolves it to 16px
     No error, no warning, no build failure -- the same silent-fallthrough
     class as the --ch-* rename hazard documented at the top of this file.
     Caught by measurement before shipping, not by review. Do not "tidy" this
     prefix back to --sp-. The two scales coexisting under distinguishable
     names is the point: var(--sp-3) is the legacy density scale, and
     var(--ra-sp-lg) is the 2.0 frame scale.

     TWO TIERS below, and the distinction is load-bearing -- do not collapse:

     T-SHIRT NAMES are design/TOKENS.json `spacing.named`, copied exactly.
     They are the design file's own words. Do not rename, do not extend.

     NUMERIC NAMES are values the frame USES HEAVILY that the named scale
     never named. This is not drift being legitimised after the fact; it is
     measured, and the measurement sits in the design file itself.
     `spacing.observedFrequency.padding` records 5px x434, 3px x320, 7px x204
     and 14px x48 across the Figma nodes -- 5px is the SECOND most frequent
     padding in the entire frame, and 3px and 7px each beat 10px, 16px and
     24px, all three of which ARE on the named scale. `spacing.scale` is
     therefore an editorial selection, not a census, and a file painting 5px
     agrees with the frame rather than defying it. A numeric name reads as
     "the frame measures this, the scale forgot to name it"; keeping the tiers
     visually distinct is how that stays readable a session from now.
     ============================================================ */
  --ra-sp-3xs:   2px;
  --ra-sp-2xs:   4px;
  --ra-sp-xs:    6px;
  --ra-sp-sm:    8px;
  --ra-sp-md:    10px;
  --ra-sp-lg:    12px;
  --ra-sp-xl:    16px;
  --ra-sp-2xl:   24px;
  --ra-sp-3xl:   32px;
  --ra-sp-4xl:   48px;

  --ra-sp-5:     5px;   /* Figma padding x434 */
  --ra-sp-3:     3px;   /* Figma padding x320 */
  --ra-sp-7:     7px;   /* Figma padding x204 */
  --ra-sp-14:    14px;   /* Figma padding x48 */
  --ra-sp-40:    40px;   /* Figma inset, Review sessions sub-row 723:6517 (S47, 2026-09-07):
                            the frame insets it 40 and the scale jumps 32 -> 48, so rounding
                            would move a pixel the designer placed (owner ruling S20-R2).
                            REVIEW_SPEC_S47.md:534. Added by the lead: tokens.css is routed
                            to no seat, and widening your own guard is not authorising it. */

  /* Semantic spacing -- design/TOKENS.json `spacing.conventions`, measured
     off frame 321:90. ALIASES, never new values: a card's inset changes here,
     in one place, instead of in every panel that ever drew one. */
  --ra-sp-card-x:         var(--ra-sp-xl);   /* 16 */
  --ra-sp-card-y:         var(--ra-sp-14);   /* 14 */
  --ra-sp-card-gap:       var(--ra-sp-md);   /* 10 */
  --ra-sp-panel-head-gap: var(--ra-sp-sm);   /* 8  */
  --ra-sp-gutter:         var(--ra-sp-sm);   /* 8  -- columnGutter */
  --ra-sp-workspace:      var(--ra-sp-sm);   /* 8  -- workspacePadding */

  /* ============================================================
     1g. Size scale -- design-sync 2026-10-03, Figma Platform variables
     `size/...` (collection Spacing): controls 4 heights by role, menu rows 2,
     icons 6 plus one stroke weight, labels 5, the three app bars.
     ============================================================ */
  --ra-size-control-xs:   18px;
  --ra-size-control-sm:   24px;
  --ra-size-control-md:   32px;
  --ra-size-control-lg:   44px;
  --ra-size-menu-item-sm: 30px;
  --ra-size-menu-item-md: 36px;
  --ra-size-icon-xs:      10px;
  --ra-size-icon-sm:      12px;
  --ra-size-icon-md:      14px;
  --ra-size-icon-lg:      16px;
  --ra-size-icon-xl:      20px;
  --ra-size-icon-2xl:     24px;
  --ra-size-icon-stroke:  1.33px;
  --ra-size-badge-xs:     16px;
  --ra-size-badge-sm:     22px;
  --ra-size-badge-md:     24px;
  --ra-size-badge-lg:     28px;
  --ra-size-badge-xl:     32px;
  --ra-size-bar-top:      60px;
  --ra-size-bar-ticker:   38px;
  --ra-size-bar-status:   48px;
  /* A chart's one-line head in a Picture-in-Picture window (design/TOKENS.json layout.panelHead, D6 shell-c;
     ds/organisms/pip.css): its 34px minimum and 40px cap. The shared panel head is 48 since the Fitts & Miller parity
     pass (8 + the 28 action row + 12, ds/organisms/panel-variants.css). */
  --ra-size-panel-head:     34px;
  --ra-size-panel-head-max: 40px;
  /* Figma effect style shadow/popover (toast): two accent-primary drop shadows at 12% and 16%; the first one sits
     5 px down since the Figma round of 2026-10-15 (was 8). */
  --ra-shadow-popover: 0 5px 24px #0FD4BB1F, 0 6px 12px #0FD4BB29;

  /* ============================================================
     1h. Text metrics one property at a time -- E1, 2026-10-05
     (design/behavior/PLAN_FULL_ATOMIC.md). A rule that sets ONLY a size, a
     line-height or a tracking cannot adopt a --ts-* shorthand without also
     taking its weight and family (the adoption hazard above), so these carry
     the one value and nothing else: computed style is identical by
     construction, which is the ruling (S20-R2, no pixel moves).
     --ra-fs-N   design/TOKENS.json `fontSizes.scale` (9..15, 18, 22), the
                 frame's own sizes, materialised for the first time. Not an
                 exception, so not the --ra-xfs- prefix of section 1e.
     --ra-lh-N   the line-heights of the frame's text styles (16 20 24, the
                 1.21x auto-leading of the 20px ticker, 24.2) plus the tight
                 8 and 14 two kit atoms set; value-named.
     --ra-ls-*   tracking the kit sets on its own (uppercase labels, mono
                 numerals): -em-NN = 0.NN em, -em-nNN its negative, -px-0-N =
                 0.N px. Value-named, section 1e's rule: the frame never named
                 them, so the value IS the reason. */
  --ra-fs-9:  9px;  --ra-fs-10: 10px; --ra-fs-11: 11px; --ra-fs-12: 12px;
  --ra-fs-13: 13px; --ra-fs-14: 14px; --ra-fs-15: 15px; --ra-fs-18: 18px;
  --ra-fs-22: 22px;
  --ra-lh-8: 8px;   --ra-lh-14: 14px; --ra-lh-16: 16px; --ra-lh-20: 20px;
  --ra-lh-24: 24px; --ra-lh-24-2: 24.2px;
  --ra-ls-em-01: 0.01em; --ra-ls-em-02: 0.02em; --ra-ls-em-03: 0.03em; --ra-ls-em-04: 0.04em;
  --ra-ls-em-05: 0.05em; --ra-ls-em-06: 0.06em; --ra-ls-em-07: 0.07em; --ra-ls-em-08: 0.08em; --ra-ls-em-10: 0.1em;
  --ra-ls-em-12: 0.12em; --ra-ls-em-14: 0.14em; --ra-ls-em-16: 0.16em; --ra-ls-em-20: 0.2em;
  --ra-ls-em-28: 0.28em; --ra-ls-em-30: 0.3em; --ra-ls-em-n01: -0.01em; --ra-ls-em-n02: -0.02em;
  --ra-ls-px-0-2: 0.2px; --ra-ls-px-0-3: 0.3px;
  /* D3, 2026-10-06 (the trading organisms): the 1.21x auto-leading of 10, 11, 12 and 14px text (Inter's intrinsic line
     height, the values the .t-u-* classes already carry), and the stat labels' 0.5px tracking. Value-named, as above. */
  /* --ra-lh-12: the Customization swatches' clear chip (ds/organisms/account-settings.css .cu-sw-clear), f1b-account-settings 2026-10-06. */
  --ra-lh-12: 12px;
  --ra-lh-12-1: 12.1px; --ra-lh-13-31: 13.31px; --ra-lh-14-52: 14.52px; --ra-lh-16-94: 16.94px;
  --ra-ls-px-0-5: 0.5px;
  /* --ra-ls-em-07: the velocity ignite badge's tracking (ds/organisms/screener-depth.css), added by D8 screener-b. */

  /* ============================================================
     1k. Component variables -- Figma round 2026-10-09 (fig-tokens), Platform library variables added 6/10 and 8/10
     with no code syntax, named by the "Como usar" rule: --ra- + the variable path, / as -, lower case (the
     collection is not part of the name, as size/control/md -> --ra-size-control-md). design/TOKENS.json `platform`.
     One value for both themes (Figma gives Dark = Light); an alias follows its target's light value. Figma
     opacities are percentages (100, 35), CSS takes 0..1. Drawn at /catalog, section Tokens (tokens/tokens.demo.js).
     ============================================================ */
  /* radio (Platform 2820:1206): dot, inner gap of the small one, ring widths, face opacity */
  --ra-radio-dot-big:               7px;
  --ra-radio-dot-small:             3.5px;
  --ra-radio-gap-small:             6px;
  --ra-radio-border-big-default:    2px;
  --ra-radio-border-big-selected:   2.5px;
  --ra-radio-border-small-selected: 1.25px;
  --ra-radio-opacity-default:       1;
  --ra-radio-opacity-disabled:      0.35;
  /* send-button (Platform 2921:1249), the chat send / pause button: accents, disabled face, focus ring width */
  --ra-button-message-pause-accent:       var(--ra-link-yellow);
  --ra-button-message-send-accent:        var(--ra-accent-primary);
  --ra-button-message-disabled-opacity:   0.38;
  --ra-button-message-focus-border-width: 2px;
  /* Intelligence brief, Today's pulse cards (Organisms 13:35263): the same colours the dark theme paints today
     (--ra-intel-h165-80-15, --ra-intel-h70-82-13, --ra-intel-info-80-12), here fixed for both themes */
  --ra-intell-drivers:     #41DCA5;
  --ra-intell-divergences: #FAB45F;
  --ra-intell-catalysts:   #80C3FF;
  /* trading (Figma round 2026-10-09, fig-trading; design/TOKENS.json `trading`): values the round's trading organisms
     need that Figma leaves unbound or gives one value in both themes. The Closed P&L card's height cap (Organisms
     trading-activity 13:12251, 261, no variable) and the activity cards' floor (head, table head and empty state; the
     app's own, Figma draws no short column). The ticket's side tones (ds/atoms/tx-side.css): dark = Figma's
     (accent/primary, the bright down red, the down red for the SELL on fill); light repoints at --pos / --neg in the
     light block at the end of this file, as Figma's single values read about 1.7:1 and 2.3:1 on the light card. The
     danger skin's ring and label (the ticket's Cancel), --ra-semantic-error in dark; it and --ra-state-danger-bg take
     the app's --neg / --neg-bg in light (the pair the sound rules' delete button switches to there). */
  --ra-size-trading-closed-card: 261px;
  --ra-size-trading-act-card-min: 177px;
  --ra-tx-side-buy:      var(--ra-accent-primary);
  --ra-tx-side-sell:     var(--ra-market-down-bright);
  --ra-tx-side-sell-on:  var(--ra-market-down);
  --ra-state-danger-ink: var(--ra-semantic-error);
  /* the Desk AI column (fig-followups; design/TOKENS.json `deskai`): Figma's greeting group and message/agent roots
     are 855 wide since 2026-10-15 (760 before), unbound (Organisms 428:21944, 13:37274); the welcome, the ask box and the
     thread's items. Beside a conversation, the evidence column (ai-desk/chat-evidence 626:21098, 432, unbound). */
  --ra-size-chat-column: 855px;
  --ra-size-chat-evidence: 432px;

  /* ============================================================
     1e. Declared exceptions -- owner ruling S20-R2, 2026-08-21.

     A size or a spacing the frame does not carry SURVIVES HERE, with a token,
     a reason and a date. It is not snapped to the nearest scale step, because
     snapping moves pixels and the ruling is that no pixel moves. The owner's
     words: "conservar como excepcion declarada".

     ONE TOKEN PER VALUE, ONE REASON PER CLASS. A custom property holds one
     value, so 35 sites at 10.5px share one token; and the reason for 10.5px is
     the same sentence as the reason for 9.5px, so it is written once. An entry
     with no stated reason is a defect, not an exception -- an undeclared
     exception is just a literal that learned how to hide from the linter.

     HOW AN ABSENCE HERE WAS ESTABLISHED, once for the whole block: a single
     live GET /v1/files/kW1IFt1tCe7o5AnwJjJpV4 (22.2 MB) walked the ENTIRE
     Figma document -- all five pages, 21,624 nodes -- reading style.fontSize
     off every TEXT node together with its styles.text binding. Four pages
     carry sized TEXT (5,736 TEXT nodes with a fontSize); the fifth (0:1
     Cover, 2 nodes) carries none. Script design/s20/_fullfile_census.py,
     output design/s20/fullfile_census.json, table design/s20/TYPE_SPACING_MAP.md.
     A search scoped to the five app frames could not have found a value living
     in a component elsewhere and would have proved nothing.

     THE PAGE IS PART OF THE EVIDENCE. The document has five pages and only
     ONE is the 2.0 design:
         67:2  Rally Alpha Design - 2.0   <- THE design surface
         41:2  KIT QA                      <- the old 1.0 kit, historical
         49:2  Seal kit remix              <- a variant exploration, no authority
         7:2   Scan reproductions          <- TRACED RASTER OF SCREENSHOTS, see A3
         0:1   Cover                       <- 2 nodes
     A citation that does not name its page is not evidence.

     PREFIX: --ra-xfs-<value> for a font size, --ra-xsp-<value> for a spacing.
     x for exception, and the trailing dash is a decimal point (--ra-xfs-10-5
     is 10.5px). Deliberately NOT the --ra-fs- prefix: section 1d's numeric
     tier (--ra-sp-5) asserts "the frame measures this heavily and the scale
     forgot to name it", and a section 1e token asserts the OPPOSITE. Two
     tiers that mean opposite things must not look alike.

     COMMENT HAZARD, BINDING (S20-R13). Never let an asterisk sit immediately
     before a slash inside a comment. CSS closes the comment there and every
     entry below it dies silently: no error, no warning, and nothing in this
     project parses CSS. A glob at the end of a clause is the trap -- write
     "panels/<name>/", never the star-slash form; write the spacing-scale
     prefix followed only by a space or a word, never directly by a slash.
     This is not hypothetical: a line in an earlier draft of this very block
     named two prefixes with a bare slash between them and survived only
     because a space happened to be there. Describe such a construction in
     words if you must refer to it -- never reproduce the two characters
     adjacent, even to explain the hazard, which is itself a trap this
     project has now fallen into more than once. Live fatal instance in the
     corpus at css/widgets.css:1198-1200 (killed --ra-text-axis outright,
     repaired in S20-R13/TH-1); a fragile near-miss at css/widgets.css:661
     and here at :1200 post-repair, both a whitespace deletion from fatal.

     ONE FURTHER GUARD, LEARNED WHILE WRITING THIS VERY BLOCK (S20-R13,
     second instance): a comment EXPLAINING a colour value in prose is
     itself a colour literal to token_lint's colour scanner, which does NOT
     strip comments the way the type/spacing scanner does. Describe a
     colour's direction of change in words ("lighter", "unchanged") and cite
     the file with the real measurement; never quote a hex or oklch() value
     in a comment inside a file that is on the colour migrationDebt list.
     ============================================================ */

  /* --- A1: ABSENT FROM THE ENTIRE DOCUMENT. -------------------------------
     REASON (2026-08-21, S20-R2): these five sizes exist NOWHERE in the Figma
     file -- not on 67:2, not on the old kit, not in the traced scans, not on
     any page. Pure app-side drift, and the strongest absence this corpus can
     produce: five pages, 21,624 nodes walked, zero occurrences. There is no
     design intent to honour and nothing to adopt, so they are preserved
     exactly and named here. 88 of the 128 off-scale literals are in this
     class, and 86 of the app's 91 half-pixel font sizes are the four clean
     halves below -- which is the shape of drift, not of a design decision.
     REVISIT: never, unless the frame starts drawing one. Shrink by MIGRATING
     sites to a --ts-* style under S20-R8, not by re-pointing this token. */
  /* design-sync 2026-10-03 (owner: half-pixel font sizes are rounded): every --ra-xfs-N-5 below
     is rounded UP to N+1 -- plain half-up, each result is a Platform text-style size (9..15) and
     no label drops under the 9px floor. Names kept; the literal sweep retires them. */
  --ra-xfs-8-5:    9px;      /* 5 sites  */
  --ra-xfs-9-5:    10px;     /* 27 sites */
  --ra-xfs-10-5:   11px;     /* 35 sites
     ADDENDUM (2026-09-06, S46 task #33): this value is no longer absent from
     the whole document. The news frame's TIME cell absolute line (`723:2943`,
     unstyled, Inter 400 10.5px/13 ls0 -- design/s46/NEWS_SPEC_S46.md
     Sections 5.3/7.3) draws it. That falsifies this class's own premise for
     THIS size specifically (8.5/9.5/11.5/26 above are unaffected -- not
     re-checked here, do not assume they're clean by association) and
     devstudio.json's `_nota_typeSpacingDebt`, which asserts the same thing
     app-wide ("no existen en ningun estilo del frame"). RECORDED, NOT
     RECLASSIFIED: moving this size to a different section-1e class, or
     promoting it to a named --ts-* style the way caption.tight was, is a
     call for whoever owns that boundary (escalated to the team-lead), not
     something to do unilaterally while minting an unrelated exception. Until
     that's decided, this token is a valid font-size source for the news
     time-cell absolute line (its line-height/weight/letter-spacing are
     declared at the call site, same as every other consumer here) -- no
     news.css site draws from it yet; that's #23's job. */
  --ra-xfs-26:     26px;     /* 2 sites  -- 67:2's nearest is 25.993px, ONE
                                scaled glyph (296:11735), which is not 26 */

  /* --- A2: NOT ON 67:2. Only on a page with no authority. -----------------
     REASON (2026-08-21, S20-R2): each of these appears in the file, but never
     on the 2.0 design surface -- only on 41:2 (the old 1.0 kit) or 49:2 (a
     variant exploration). The 2.0 surface does not draw them.
     24px AND 48px ARE NAMED THERE, and the name is why they still cannot be
     adopted: 24px is bound to `Headings/H4` on 41:2 (x25, 16 of them on the
     style) and 48px to `Headings/H1` (x8) -- both are REMOTE styles from the
     1.0 library. A 1.0 name is not a 2.0 name. Adopting one would import the
     old kit's type ramp into the new design under cover of a token migration,
     which is precisely what a declared exception exists to prevent.
     REVISIT: if a 2.0 style is ever registered at one of these sizes. */
  --ra-xfs-8:      8px;      /* 8 sites  -- 41:2 x4, 49:2 x3, unstyled both   */
  --ra-xfs-17:     17px;     /* 1 site   -- 41:2 x21, 49:2 x28, 7:2 x32, all
                                unstyled even there                           */
  --ra-xfs-24:     24px;     /* 4 sites  -- 41:2 x25, REMOTE `Headings/H4`    */
  --ra-xfs-30:     30px;     /* 1 site   -- 49:2 x3 only, remix exploration   */
  --ra-xfs-48:     48px;     /* 1 site   -- 41:2 x8, REMOTE `Headings/H1`     */

  /* --- A3: PRESENT ONLY ON 7:2, AND CITING 7:2 WOULD BE CIRCULAR. ---------
     ***  READ THIS BEFORE "DISCOVERING" FRAME SUPPORT FOR ONE OF THESE.  ***
     REASON (2026-08-21, S20-R2): each of these four sizes is absent from 67:2
     and appears ONLY on page `7:2 Scan reproductions`, which is TRACED RASTER
     OF SCREENSHOTS OF THIS APP. So the frame did not specify these sizes --
     SOMEONE TRACED OURS. Citing 7:2 as frame support for an app value is
     CIRCULAR: it is our own value coming back to us with a node id attached,
     and a node id is exactly what makes it look like evidence.
     A future session searching the document WILL find 13.5px on 7:2 and may
     conclude the design supports it. It does not. That session is reading a
     photograph of this codebase. 7:2 is `authority: none` in every table in
     design/s20/TYPE_SPACING_MAP.md section 1.2, and this comment is the
     mechanism that stops the rediscovery.
     REVISIT: only if one of these appears on 67:2. Never on 7:2 evidence. */
  --ra-xfs-12-5:   13px;     /* rounded up, design-sync 2026-10-03 (see A1). 3 sites  -- 7:2 x4 traced, 67:2 none */
  --ra-xfs-13-5:   14px;     /* rounded up, design-sync 2026-10-03. 1 site   -- 7:2 x5 traced, 67:2 none */
  --ra-xfs-14-5:   15px;     /* rounded up, design-sync 2026-10-03. 1 site   -- 7:2 x17 traced, 67:2 none */
  --ra-xfs-19:     19px;     /* 1 site   -- 7:2 x3 traced, 67:2 none */

  /* --- B1: DRAWN ON 67:2 AND NEVER NAMED. ---------------------------------
     REASON (2026-08-21, S20-R2): the 2.0 design DOES draw these sizes, on the
     design surface itself -- and left every one of them unstyled. So the app
     agrees with the frame on the value and the frame declined to give it a
     name; there is no --ts-* to adopt because the design never minted one.
     This is the weakest-standing class in section 1e and the likeliest to be
     retired: a single new named style on 67:2 converts any of these from an
     exception into an adoption.
     REVISIT: whenever a 2.0 text style is registered. */
  --ra-xfs-6:      6px;      /* 1 site  -- 67:2 x7 unstyled. Below EVERY
                                registered style; caption-helper is the
                                smallest at 9px. The design draws it and the
                                scale stops above it                          */
  --ra-xfs-18:     18px;     /* 9 sites -- 67:2 x16 unstyled (296:11739 up-
                                arrow, 461:3831 refresh, 296:11655 copy).
                                Icon-glyph sizing, not text                   */
  --ra-xfs-20:     20px;     /* 4 sites -- 67:2 x1: 422:5261 "BLKB",
                                Inter 700 20px/24.2. One ticker-symbol
                                treatment, never promoted to a style          */
  --ra-xfs-28:     28px;     /* 3 sites -- 67:2 x2: 296:9253 "1.0x" and
                                321:1799 "0.0x", INTER 800. Its weight is not
                                in TOKENS.json fontWeights at all, so no token
                                could express this even if one existed        */

  /* --- B2: DRAWN AND NAMED ON 67:2, BUT THE NAME HAS NO TOKEN. ------------
     REASON (2026-08-21, S20-R2): 16px is the one size where the 2.0 design
     surface both draws it AND names it -- 6 nodes on 67:2, one bound to
     `Button/Large` (296:11728), five unstyled glyphs. It is still an exception
     because THE NAME RESOLVES TO NOTHING WE CAN CONSUME: `Button/Large` is the
     REMOTE style 82:768, and its LOCAL twin 93:1453 is one of the five unused
     styles recorded in TYPE_SPACING_MAP section 0.2. No --ts-* exists, so
     there is nothing to adopt.
     *** REVISIT THIS ONE FIRST. *** It is the single entry in section 1e that
     a design-side action retires outright: register `Button/Large` as a 2.0
     style, regenerate TOKENS.json, add --ts-button-large, and this token is
     deleted rather than argued about. Task #11 was told to keep it revisitable
     and this comment is that hook.
     ALSO: 3 of the 4 rules at this size are SPLIT in design/s20/TS_ADOPTION.json
     -- their own matched elements already compute differently -- so no single
     token can serve them even once one exists (S20-R8 item 5). */
  --ra-xfs-16:     16px;     /* 2 sites -- 67:2 x6, 1 on `Button/Large` */

  /* --- FLUID RAMPS: the token holds the whole clamp, not a bound. --------
     REASON (2026-08-21, S20-R2 + lead ruling on this draft): the four
     font-size declarations in features/squeeze-overlay/squeeze_overlay.js are container-query
     FLUID RANGES, not fixed sizes -- clamp(min, Ncqi, max) inside a
     container-sized overlay, with the cqi figures derived in that file's own
     comment at :40-42. Neither bound is a text size on its own: the min is
     what the type collapses to in a narrow overlay and the max is its
     ceiling, so a token named for either would be a name for half a range.
     THE WHOLE EXPRESSION IS THE VALUE, so the whole expression is the token.
     A literal with a comment still counts against the ceiling, and --strict
     -- an empty dictionary -- is the declared end state, so leaving these as
     literals would make that end state permanently unreachable for this file.
     MEASURED, so nobody re-derives it: token_lint captures a clamp as ONE
     fragment, so these are 4 literals and not 8, and a var() reference here
     scores zero because tokens.css is excluded from the scan. Moving them
     here removes 4 from the ceiling. ROLE-NAMED, not value-named, per this
     block's own rule: the number is not a plain size. */
  --ra-xfs-sqz-head:  clamp(10px, 4.29cqi, 18px);  /* squeeze_overlay.js:43
                                    `.sqz-ov .sqz-head`                      */
  --ra-xfs-sqz-pct:   clamp(11px, 4.64cqi, 20px);  /* squeeze_overlay.js:46
                                    `.sqz-ov .sqz-pct`                       */
  --ra-xfs-sqz-row:   clamp(9px, 3.57cqi, 15px);   /* 2 sites -- :51
                                    `.sqz-ov .sqz-stat` and :54 `.sqz-flags` */
  /* --- EM-RELATIVE: a size that is a ratio of the text around it. --------
     REASON (2026-10-05, D14): inline code inside an answer is drawn a step
     smaller than the prose it sits in, whatever that prose's size is (the
     Desk tab, the Review chat and the screener card each set their own).
     A px token would freeze it at one surface's size, so the ratio itself is
     the value. ROLE-NAMED for the same reason as the fluid ramps above. */
  --ra-xfs-code-inline: 0.92em;   /* 1 site -- harness-chat-thread.css `.hc-code-inline` */

  /* ============================================================
     1e (spacing half) -- owner ruling S20-R2, 2026-08-21.

     S20-R2 HOLDS IN FULL FOR SPACING. NOTHING MOVES. S20-R8 overturned it for
     TEXT ONLY -- "use the named styles anyway and accept things moving" was an
     answer about type, and stretching it to padding/margin/gap is the single
     most likely way this sweep breaks the layout. If a spacing value is not on
     the 14-token scale it is PRESERVED and declared here, never snapped.

     HOW AN ABSENCE HERE WAS ESTABLISHED, once for the whole block: the same
     whole-document walk as the type half -- one live GET of the Figma file,
     all five pages, 21,624 nodes, reading itemSpacing / counterAxisSpacing /
     padding on every node. Script design/s20/_fullfile_census.py, output
     design/s20/fullfile_census.json (spaceByPage), classification in
     design/s20/TOKENS_1E_SPACING_DRAFT.md.

     THREE pages carry spacing, not four: 67:2 (THE design surface), 41:2
     (the old 1.0 kit) and 49:2 (a variant exploration). Page 7:2 "Scan
     reproductions" carries NO spacing data at all, because it is traced
     raster -- so the circular-evidence trap documented on the type side does
     NOT arise here. A citation still has to name its page.

     TWO NAMING FORMS AND THE RULE THAT PICKS ONE, because it matters:
       VALUE-NAMED (--ra-xsp-22) when the exception is about the VALUE -- a
         genuine inset the frame does not carry at that size.
       ROLE-NAMED (--ra-xsp-payer-dot-center) when the number is NOT A SPACING
         VALUE AT ALL -- half a height, a border width, an overlap. This is
         S20-R11 one level up: that ruling refused to calc-negate a centring
         offset because it would couple the offset to the spacing scale, and a
         name like `neg-6` invites the same coupling by a slower route -- the
         next dev with an unrelated -6px reuses it because the name says only
         "negative six". A role name makes reuse-by-coincidence impossible,
         which is the only protection available for a value nothing can check.

     COMMENT HAZARD, BINDING (S20-R13): see the guard already stated above the
     type half of this block -- it governs this half identically. There is no
     asterisk anywhere adjacent to a slash in what follows, deliberately.
     ============================================================ */

  /* --- 1px: 52 LITERALS AND SIX DIFFERENT MEANINGS. ----------------------
     REASON (2026-08-21, S20-R2): 1px IS drawn on the 2.0 design surface --
     padding x6 at node 421:521 -- and the design never named it. So the value
     itself is honest; what is dishonest is one shared reason across 52 sites.
     They are not one thing, so they are not one token. Each name below says
     what its sites actually DO, and the split is the deliverable:
       chip-y       a small inline label's vertical inset (padding: 1px Npx)
       stack-gap    a tight column gap between stacked rows
       nudge        a 1px optical correction, always margin-top
       hairline     a gap that REVEALS THE PARENT BACKGROUND as a divider
       border-inset the mask-composite gradient-border ring width
       (bare 1)     miscellaneous 1px insets with no shared role
     REVISIT: hairline and border-inset FIRST -- see their own note. */
  --ra-xsp-chip-y:        1px;   /* 29 sites -- vertical inset of a chip,
                                    badge or pill; the horizontal half is
                                    already on-scale (4/5/6px)               */
  --ra-xsp-stack-gap:     1px;   /* 9 sites  -- flex-direction: column gap   */
  --ra-xsp-nudge:         1px;   /* 4 sites  -- optical margin-top only      */
  --ra-xsp-1:             1px;   /* 4 sites  -- bare 1px insets, no shared
                                    role: shell.css:444, widgets.css:375,
                                    drawing-popover.js:102, screener.css:1044 */

  /* --- 1px that is NOT SPACING. Flagged, declared, and due to move out. ---
     REASON (2026-08-21, S20-R2): these six sites spell a BORDER WIDTH using a
     spacing property, and the number means "one device pixel of rule", not
     "one pixel of space". They are declared here because they are literals
     today and the block must be pasteable today -- NOT because this is where
     they belong.
     *** RECOMMENDATION, needs an owner: migrate both to a border-width token
     and delete these two entries. *** Leaving them in the spacing tier
     couples a hairline to the spacing scale, which is exactly what S20-R11
     refused to do for a centring offset. Same argument, same answer, and it
     is on the record here rather than discovered later. */
  --ra-xsp-hairline:      1px;   /* 3 sites -- `gap: 1px` over a `background:
                                    var(--line)` parent: the GAP draws the
                                    rule. intelligence.css 237, 1680, 4023   */
  --ra-xsp-border-inset:  1px;   /* 3 sites -- `position:absolute; inset:0;
                                    padding:1px` gradient-border ring.
                                    components.css:1404, composer.css:140,
                                    screener.css:2598                        */

  /* --- B1: DRAWN ON 67:2 AND NEVER NAMED. --------------------------------
     REASON (2026-08-21, S20-R2): the 2.0 design surface draws each of these
     itself, at the node cited on each line, and the spacing scale never named
     it. The app agrees with the frame on the value; only the name is missing.
     Note WHAT the scale is: the Figma file binds exactly ONE spacing variable
     anywhere in the section (spacing/2xl = 48), so section 1d's 14-step scale
     is an EDITORIAL SELECTION from a measured frequency table, not a census.
     A file painting a value the frame paints is agreeing with the frame, not
     defying it -- which is why these are preserved rather than snapped.
     REVISIT: if a real spacing collection is ever published in the file. */
  --ra-xsp-9:      9px;      /* 20 sites -- 67:2 gap x11, 421:520(itemSpacing) */
  --ra-xsp-22:    22px;      /* 10 sites -- 67:2 padding x16, 85:5540(L/R)     */
  --ra-xsp-11:    11px;      /* 5 sites  -- 67:2 padding x32 + gap x1,
                                296:11703(itemSpacing), 391:4764(paddingTop)   */
  --ra-xsp-13:    13px;      /* 5 sites  -- 67:2 gap x1, 451:1208(itemSpacing) */
  --ra-xsp-19:    19px;      /* 3 sites  -- 67:2 padding x6, 296:11548(T/B)    */
  --ra-xsp-17:    17px;      /* 2 sites  -- 67:2 gap x8, 85:5563(itemSpacing)  */
  --ra-xsp-26:    26px;      /* 1 site   -- 67:2 gap x1, 443:721(itemSpacing)  */
  --ra-xsp-27:    27px;      /* 1 site   -- 67:2 padding x16, 85:5563(L/R)     */

  /* --- A2: NOT ON 67:2. Only on a page with no authority. -----------------
     REASON (2026-08-21, S20-R2): each appears in the file but NEVER on the
     2.0 design surface -- only on 41:2 (the old 1.0 kit) or 49:2 (a variant
     exploration). The 2.0 surface does not draw them. Their frequency on the
     old kit is not an argument FOR them: 40px is 41:2's most-used gap (x26)
     and 67:2 does not use it once, which is the shape of a ramp that was
     deliberately left behind.
     REVISIT: if the value appears on 67:2. Never on old-kit frequency. */
  --ra-xsp-18:    18px;      /* 29 sites -- 41:2 gap x3, 49:2 gap x2, 67:2 none */
  --ra-xsp-20:    20px;      /* 14 sites -- 41:2 gap x12 + padding x33,
                                49:2 padding x36, 67:2 none                     */
  --ra-xsp-40:    40px;      /* 5 sites  -- 41:2 gap x26 + padding x16, 67:2 none */
  --ra-xsp-28:    28px;      /* 3 sites  -- 41:2 gap x4 + padding x12, 67:2 none  */

  /* --- A1: ABSENT FROM THE ENTIRE DOCUMENT. ------------------------------
     REASON (2026-08-21, S20-R2): these eight exist NOWHERE in the Figma file
     -- not on 67:2, not on the old kit, not in the remix. Five pages, 21,624
     nodes walked, zero occurrences. Pure app-side drift, preserved because
     S20-R2 says no pixel moves and there is nothing to adopt.
     THE FOUR FRACTIONALS ARE THE BEST-DOCUMENTED EXCEPTIONS IN THIS CODEBASE
     and three of them carry their own derivation in the comment directly
     above the declaration -- cited per line below. They are NOT computed at
     runtime; they are static literals someone worked out and wrote down.
     REVISIT: never, unless the frame starts drawing one. */
  --ra-xsp-15:        15px;      /* 6 sites */
  --ra-xsp-30:        30px;      /* 6 sites */
  --ra-xsp-25:        25px;      /* 1 site  */
  --ra-xsp-56:        56px;      /* 1 site  */
  --ra-xsp-10-375:    10.375px;  /* 1 site -- screener.css:1705, derivation in
                                    its own comment at :1697 (CD-752/CD-753,
                                    row pitch 8 -> 10.375 to reach a 40px row) */
  --ra-xsp-21-258:    21.258px;  /* 1 site -- screener.css:264                 */
  --ra-xsp-7-5:        7.5px;    /* 1 site -- screener.css:1795, the compact
                                    variant of the 10.375 row pitch at :1765   */
  --ra-xsp-5-5:        5.5px;    /* 1 site -- screener.css:700. ALONE AMONG THE
                                    FRACTIONALS IT HAS NO STATED ORIGIN: no
                                    comment, no derivation, and no node in the
                                    file carries it. Recorded as unexplained
                                    rather than given a plausible reason --
                                    inventing one would make it unfalsifiable  */

  /* --- .cind's padding, ruled individually (S20-R4). ---------------------
     REASON (2026-08-21, S20-R4): SUSPECTED PARITY DEFECT, PRESERVED ON
     PURPOSE. screener.css:808 paints padding 3.57px. The frame carries
     3.5699381 at node 142:1564 -- but that node is a x1.19 SCALED INSTANCE,
     and the standing scaled-instance ruling (quoted in that rule's own
     comment) stores target / scale: 3.5699381 / 1.19 = 3.0000, i.e.
     --ra-sp-3. So the scaled-instance ruling says 3px and S20-R2 says no
     pixel moves. The lead ruled PRESERVE, DECLARE, FILE -- three acts --
     because changing it here would move a pixel under cover of a token
     migration, which is the exact shape S20-R2 exists to stop. A migration
     session may not silently repair a defect it notices.
     REVISIT: via the register row, as a deliberate parity fix, never here. */
  --ra-xsp-3-57:   3.57px;   /* 1 site -- screener.css:808 `.btn.cind`.
                                67:2 padding x44 at 149:61, all scaled         */

  /* --- NEGATIVES: ruled by S20-R11, and FOUR OF SIX ARE INVISIBLE. -------
     *** DO NOT DELETE THESE AS UNUSED. *** token_lint's UNIT_RE refuses a
     digit preceded by a minus sign, so `margin-top: -6px` is INVISIBLE to
     the gate -- the linter will never report a literal for it and never
     confirm this token is doing anything. Four of the six below are in that
     state, INCLUDING BOTH THAT S20-R11 EXPLICITLY RULED. They are declared
     anyway. A future session finding "a token with no matching literal" has
     found this comment, not dead code. (S20-R3 names this defect; its patch
     had not landed as of 2026-08-21.)

     S20-R11's TEST, which is why these are exceptions and not calc(): a
     negative MAY become calc(var(--ra-sp-X) * -1) when its magnitude is
     exactly a scale value AND READING THE SITE shows it is genuinely a
     spacing pull. An exact numeric match is PERMISSION TO LOOK, not
     permission to substitute. intelligence.css:477's -4px passed that test
     and is NOT here -- it leaves this population by being migrated. Every
     one below FAILED it, each for a stated reason. */
  --ra-xsp-payer-dot-center:   -6px;   /* intelligence.css:1492. Half the 12px
        dot height for the absolute-centring idiom; not a spacing value, and
        coupling it to the spacing scale would decentre the pin if the scale
        moved. INVISIBLE to the gate.                                        */
  --ra-xsp-tab-border-overlap: -1px;   /* automations.css:654. Pulls the tab up
        by one pixel so its own `border-bottom: 2px solid transparent` overlaps
        the container rule; a border-overlap offset, and there is no 1px
        spacing token to lean on. INVISIBLE to the gate.                     */
  --ra-xsp-rail-hr-bleed:      -1px;   /* intelligence.css:3997, in
        `margin: 8px -1px 0`. Bleeds a 1px rule to the card edge, mirroring
        .panel-head::after's inset (components.css). Seen by the gate ONLY
        because the 8px in the same shorthand matched -- migrate the 8px and
        this literal vanishes from the count.                                */
  --ra-xsp-cind-caret-nudge:  -0.8px;  /* screener.css:810. NO STATED ORIGIN.
        Sits beside .cind's 3.57px and is probably the same x1.19 scaled-
        instance family, but nothing in the file or the code says so, and
        0.8 x 1.19 = 0.952 is not a round number either. Recorded as
        UNEXPLAINED, in the same suspected-parity register row as S20-R4.
        Seen by the gate only because a period precedes the digit 8.         */

  /* ============================================================
     1c. Select caret (design/PLAN_S19.md §2.2) -- moved OUT of
     components.css, which is how it loses its only `%23` colour-literal
     (a debt point banked). Four things happen in one move: the ink becomes
     #9AA1A4, the frame's OWN value for the "▾" glyph (450:1082, 450:1089,
     451:1167 -- Inter 400 14/20 #9AA1A4; the app drew #88929C, a value
     from no node); the caret gets a LIGHT value for the first time (see
     the [data-theme="light"] block below); and there is exactly one place
     to change a caret from now on. The light ink is a REPOINT, not an
     invention (per the plan's explicit instruction, do not choose by eye):
     it is the resolved light value of --ra-text-secondary (var(--text-2)
     under [data-theme="light"], widgets.css:1064 -> oklch(0.38 0.012 70)
     -> #47413C), measured at 10.05:1 against oklch(1 0 0) -- clears the
     3:1 non-text floor with room to spare, so the plan's fallback step to
     --ra-text-tertiary's light value is not needed. */
  --ra-caret-select: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M2 4 L5 7 L8 4' stroke='%239AA1A4' stroke-width='1.2' fill='none' stroke-linecap='round'/></svg>");

  /* ============================================================
     1f. Motion vocabulary (PLAN_S31.md §3) -- the first motion tokens
     this product has had. `grep -- '--mo-' platform/frontend/` returned
     ZERO hits before this block [probed, PLAN_S31 §3.1] -- the prefix is
     free, and it is deliberately NOT --anim- (near-miss to the existing
     --ai-anim-ms, renamed --mo-dur-ai-morph at F2) and NOT --m- or --motion- (§3.1 is explicit).

     PROVENANCE -- NOT design/TOKENS.json. TOKENS.json has no `motion`
     group; the Figma file carries no timing data. Every value below is
     measured off the owner's reference video, design/s31/MOTION_REF_SPEC.md
     (frame ranges cited per value there), reconciled against
     design/UX-REVIEW-motion.md's four-tier vocabulary where the two
     disagreed -- each row below names its source. A dev who needs a
     duration that does not exist here asks the architect -- they do not
     invent a millisecond.

     THE ONE RULE WITH TEETH (PLAN_S31 §3.6): only `opacity`, `transform`
     and `background-color` are animatable. Never `width`, `height`, `top`,
     `left`, `filter`, `box-shadow`, `grid-template-columns` or `all` on
     anything that shares a frame with a chart. The single authorised
     exception in the whole session is `backdrop-filter` on the modal
     scrim (S31-R4).

     Reduced motion is NOT a token here -- html.reduce-motion (css/app.css)
     already forces every animation/transition duration to 0.01ms
     !important across the whole app; a second mechanism would drift from
     the first (§3.5). ============================================== */
  --mo-dur-hover-in:  90ms;   /* UX-R1 --mo-fast; ref measures 300-380ms but
                                 UX's 90ms wins -- it measured OUR dense
                                 tables, the ref measured someone else's */
  --mo-dur-hover-out: 0ms;    /* UX-R8: a hover that lingers reads as lag */
  --mo-dur-menu:      160ms;  /* UX-R1 --mo-base */
  --mo-dur-view:       220ms; /* MOTION_REF §7.1 measured 217ms, view out-fade;
                                 UX-R1 --mo-slow agrees */
  --mo-dur-exit:      160ms;  /* MOTION_REF §3: exit measured 183ms, snapped
                                 to --mo-dur-menu so exits and menus share
                                 one number */
  --mo-dur-scrim:     200ms;  /* MOTION_REF §2.1 -- measured 200ms, linear,
                                 both directions */
  --mo-dur-switch:    150ms;  /* S51 WP4: the thumb slide of the trading ticket / nav-bar and the
                                 Stats EMA switches, measured .15s at four sites; the kit switch
                                 variants (.switch.flat/.navbar/.lg) reproduce it through this token */
  --mo-dur-blur:      120ms;  /* MOTION_REF §7.1 -- measured 110-130ms */
  --mo-dur-flash:     500ms;  /* UX-R11a, down from today's literal 650ms */
  --mo-dur-pulse:    1200ms;  /* D14, 2026-10-05: one beat of a "working" dot (hc-pulse,
                                 an opacity loop) -- PROMOTED, not invented: the
                                 Desk AI status dot, its evidence rows and the
                                 card's activity line all shipped 1.2s */
  --mo-dur-caret:   1000ms;   /* D14: the streaming caret's blink (hc-pulse on
                                 .hc-streaming), shipped as 1s; kept apart from
                                 --mo-dur-pulse so neither changes speed */
  --mo-delay-busy:    300ms;  /* Feedback standard: a Loading face waits this
                                 long before it shows, so a fast answer never
                                 flashes it -- the chart's loading grace,
                                 features/chart/state.js:58-67. Not zeroed by
                                 reduce-motion (ds/molecules/empty-state.css) */
  --ra-stale-opacity: 0.7;    /* Level 1 stale: values stay readable, dimmed --
                                 the News list's dim,
                                 ds/organisms/news-states.css:34 */
  --ra-opacity-disabled: 0.4; /* btn-states, 2026-10-07: a disabled .btn
                                 (ds/atoms/button.variants.css), busy or not --
                                 PROMOTED, not invented: the Settings Save, the
                                 ticket submit and the review icon buttons all
                                 shipped .4. Same value in both themes
                                 (design/TOKENS.json opacity.disabled) */
  --mo-stagger:      60ms;   /* MOTION_REF §7.1 measures ~100ms; shortened
                                 so a 12-block modal lands in ~600ms, not 1.5s */
  /* E1, 2026-10-05: the kit's own durations, named where they already ship
     (each value unchanged, so no transition gets faster or slower). */
  --mo-dur-control:   120ms;  /* a kit control's hover/focus state change
                                 (border, fill, ink): .12s at ~20 kit sites --
                                 button, input, segmented, switch, rail, the
                                 search pill, the news badges */
  --mo-dur-nudge:     150ms;  /* the login submit arrow's hover slide */
  --mo-dur-toast:     200ms;  /* the toast's rise and fade (ui-kit.css) */
  --mo-dur-ring:      300ms;  /* the link-target ring on a chart (drag.css) */
  --mo-dur-mark:      1s;     /* the outline on a moved, added or copied screener panel (frame 27, screener-panel-drag.css) */
  --mo-dur-pulse:     1.2s;   /* one beat of the AI badge's pending pulse */
  --mo-dur-instant:   0s;     /* a step with no duration: the trading drawers' visibility flip (D3, 2026-10-06) */
  /* D8 screener-b, 2026-10-06: the screener organisms' own durations, named where they ship (each value unchanged). */
  --mo-dur-needle:        180ms;  /* the velocity gauge's needle swing (screener-depth.css #vel-needle-line) */
  --mo-dur-ignite:        2.6s;   /* the velocity panel's ignition flash; FLASH_MS 2600 in panels/screener/velocity.js */
  --mo-dur-ignite-badge:  450ms;  /* one beat of the ignite badge, four beats */
  --mo-dur-fbd-flash:     300ms;  /* a false-breakout fail flash on a panel */
  --mo-dur-sl-flash:      600ms;  /* the same flash when a stop-loss trips */
  --mo-dur-loading-pulse: 1.4s;   /* the chart loading veil's text pulse */
  --mo-dur-tint:          200ms;  /* the NBBO wide-spread background tint */
  --mo-dur-progress:      300ms;  /* the replay progress bar's growth */
  --mo-dur-knob:          160ms;  /* a sound rule's on/off knob slide */
  --mo-dur-row-in:        300ms;  /* a recent-fire row's entrance in the sound rules */
  --mo-dur-row-glow:      900ms;  /* a gainers row's sound-rule glow; soundGlowClear (950) outlasts it */
  --mo-dur-marquee:   60s;    /* one loop of the footer's trending tape (ticker-bar.css), the movers scrolling by */
  --mo-dur-lift:      80ms;   /* automations-b, 2026-10-06: a card's 1-2 px hover lift (the Automations canvas node and the
                                 trigger picker's cards: .08s, unchanged) */
  --mo-dur-ai-morph:  280ms;  /* F2, 2026-10-06: the Desk AI launcher morph and card grow-in, moved unchanged from
                                 ds/organisms/screener-ai-card.css (panels/screener/ai-agent.js reads it back for the
                                 timeout that sets [hidden]); 1ms under prefers-reduced-motion, after this block */
  /* The brand splash, design/rally-alpha-ident.html: its own surface (absent from
     the 2.0 section, design/CHANGE_MAP.md), its own choreography: the mark
     sweeps in, the apex pings, the two words' letters rise one step apart,
     the hint fades in last. */
  --mo-ident-sweep:        1.3s;
  --mo-ident-sweep-delay:  0.2s;
  --mo-ident-ping:         0.7s;
  --mo-ident-ping-delay:   1.05s;
  --mo-ident-letter:       0.7s;
  --mo-ident-word-1:       1.55s;  /* the first word's first letter */
  --mo-ident-word-2:       1.85s;  /* the second word's first letter */
  --mo-ident-letter-step:  0.05s;
  --mo-ident-hint:         0.6s;
  --mo-ident-hint-delay:   3.4s;
  --mo-ease-ident-reveal:  cubic-bezier(0.22, 1, 0.36, 1);
  --mo-ease-ident-soft:    cubic-bezier(0.16, 1, 0.3, 1);
  --mo-ease-settle: cubic-bezier(.2, .7, .2, 1);  /* the house curve,
                                 PROMOTED, not invented -- already shipping
                                 at css/widgets.css:816 and :973 */
  --mo-ease-exit:   cubic-bezier(.4, 0, 1, 1);    /* UX-R1 --mo-in */
  --mo-ease-blur:   ease;                          /* MOTION_REF §7.2 --
                                 measured to match CSS `ease` */
  --mo-ease-scrim:  linear;                        /* MOTION_REF §7.2 --
                                 measured flat, not a default */

  /* Blur-to-focus pair -- S33-R3 (owner picked option B, task #60):
     design/s33/CHART_REVEAL_REF.md §3.4 measured the reference's incoming
     content (chart route mounts AND the boot-time "Most active" block)
     entering blurred and resolving to sharp, camera-static so the numbers
     are real: duration ~420-250ms per block depending on ROI, easing not
     independently fit (camera contaminates the candle event itself) but
     the boot-window fit and CHART_REVEAL_OPTIONS.md §2 Option B both state
     it as ease-out -- taken as given per the ruling, not re-measured here.
     --mo-dur-focus snaps to the ROUND figure both the ref file and the
     options doc quote (~500ms), distinct from --mo-dur-blur (120ms) above,
     which is the S31-R4 SCRIM's backdrop-filter resolve -- same ingredient
     (filter), different surface, different number, kept as separate named
     tokens on purpose (same precedent as --mo-dur-exit deliberately NOT
     collapsing into --mo-dur-menu despite sharing 160ms).
     --mo-blur-focus is an explicit ESTIMATE, not a measurement: CHART_
     REVEAL_REF §0/§3.4 states every sharpness estimator saturates above
     sigma~=3-4px, so the reference's true peak (sigma 6-10px band) cannot
     be read off a frame directly. 8px sits mid-band. R3 p.3: "a peak
     radius that is an estimate must be TUNED AGAINST HIS EYE, not
     asserted" -- this value ships for the owner's look-and-tell, not as a
     settled figure; whoever changes it only edits this one line. */
  --mo-dur-focus:   500ms;  /* CHART_REVEAL_REF §3.3/§3.4, CHART_REVEAL_
                                OPTIONS.md §2 Option B -- ease-out, taken
                                as measured per S33-R3 */
  --mo-blur-focus:  8px;    /* ESTIMATE inside the measured 6-10px band --
                                see comment above, tune against the owner's
                                eye, do not re-derive as a measurement */

  /* Scrim trio -- S31-R4 (LEAD_RULINGS.md): ships AS THE REFERENCE DRAWS
     IT, blur included, radius its own token so the fallback flip is one
     variable away. --ra-scrim-modal is achromatic and absent from
     TOKENS.json, same declared-decision precedent as the pre-existing
     literal it replaces at css/components.css:1852 (Amendment 2,
     df9f04b8) -- quoted here, not re-invented. Alpha measured 0.345 open /
     0.354 close (MOTION_REF §2.1/§3); today's literal was 0.55, so the
     scrim gets deliberately LIGHTER and gains a blur -- QA should expect
     this as a designed change, not a regression.
     Blur: MOTION_REF measures sigma 3.8-4.0px. CSS `blur(<length>)` sets
     the Gaussian STANDARD DEVIATION, so 4px is correct -- 8px would be the
     diameter misreading the spec itself warns against. Verify against
     design/s31/ref_frames/ref-01-modal-backdrop.png before ever changing
     this number.
     PRE-AUTHORISED FALLBACK (S31-R4): if task #13's headful reading shows
     visible frame drops with charts streaming behind an open modal,
     --mo-scrim-blur -> 0px and --ra-scrim-modal's alpha -> 0.45 to
     compensate. A one-line, two-token edit with both readings attached --
     not a redesign, does not reopen PLAN_S31. */
  --ra-scrim-modal: oklch(0 0 0 / 0.35);
  --mo-scrim-bg:    var(--ra-scrim-modal);
  --mo-scrim-blur:  4px;

  /* ============================================================
     2. Legacy DOM tokens, re-pointed at 2.0 values. Same names consuming
        CSS has always used (css/components.css, css/widgets.css, every
        panel/*.css) — zero edits needed in those files to adopt 2.0.

     P5 (design/PLAN.md §8.1 #15): "delete the alias layer OR DECLARE
     PER-ALIAS WHY IT SURVIVES" — not an unconditional delete. Measured,
     not assumed, before touching anything: grepped every var(--<name>)
     call site across platform/frontend for each of the ~22 names below,
     PER NAME, not as a range — a range summary can conceal exactly the
     value being looked for (caught live: an early "--bg-0/1/2/3, --surf,
     --surf-hi by 2-14 files" summary hid that --surf itself was zero).
     Four names (--surf, --pos-dim, --neg-dim, --info-dim) had ZERO
     remaining consumers anywhere in the tree, in EITHER theme (each also
     had an orphaned [data-theme="light"] re-declaration in
     css/widgets.css — deleted there too, same commit) — deleted below,
     the literal act the phase is named for. Every other name is still
     called directly by 4-28 files each (exact counts on every group
     below) — full deletion would break rendering across most of the app,
     the OPPOSITE of the phase's acceptance test ("if a colour moves, a
     file was still riding the layer"). It would move on purpose, not by
     accident.

     THE FINDING UNDERNEATH THIS PHASE: migrationDebt (devstudio.json)
     only ever counted literal hex/oklch VALUES — it never counted
     var(--legacy-name) USAGE. A file can carry zero raw colour literals
     (and so sit off migrationDebt, reading as "migrated") while still
     depending on this exact alias layer for every var(--text-1)/
     var(--pos)/etc. call it makes. The plan's "debt shrinks per phase as
     files migrate" framing silently assumed those two measures were the
     same thing — they are not, and the 4-28-file counts below are the
     actual, measured depth of the token migration, not the debt list's.
     Not a session failure; nobody had taken this measurement before now.
     Next session: migrating a file off migrationDebt does not migrate it
     off the alias layer — check var() usage separately.
     ============================================================ */

  /* Accent hues (OKLCH hue channel) — read/written directly by
     shell/account/customization.js (S.applyCust() in shell/account/state.js, out of this
     session's scope). Values, names and consumers are all unchanged
     EXCEPT --hue-neg: see the Option C note below. */
  --hue-pos: 180;
  --hue-neg: 25.5;   /* was 12 — design/PLAN.md §4.5 "Option C", see --neg below */
  --hue-neg-light: 10;
  --hue-info: 250;

  /* NOT a gap (verified against the live file, not assumed stale — a
     previous version of this comment claimed one and was wrong, caught
     during P5 review): applyCust() stamps --hue-neg onto
     document.documentElement.style on every boot, which would normally
     win over this :root default as an inline style regardless of load
     order. S.applyCust() (shell/account/state.js) already guards this — a stored hueNeg of 12
     (the pre-2.0 legacy default) is treated as "unset" via
     r.removeProperty('--hue-neg') instead of being stamped, so
     tokens.css's 25.5 below reaches the DOM for a non-customised user.
     A hue the user actually chose is still stamped normally. */

  /* Background tint */
  --bg-hue: 250;
  --bg-chroma: 0.015;

  /* Surfaces — SURVIVES: --bg-0 10 files, --bg-1 13, --bg-2 14, --bg-3 7,
     --surf-hi 2. (--surf itself: 0 — deleted below, not here, so the
     deletion sits next to its own light-theme counterpart instead of
     splitting the two.) */
  --bg-0: var(--ra-surface-base);
  --bg-1: var(--ra-surface-panel);
  --bg-2: var(--ra-surface-tape);
  --bg-3: var(--ra-surface-card);
  --surf-hi: var(--ra-surface-card-elevated);
  /* --surf DELETED (design/PLAN.md §8.1 #15) — 0 consumers found for
     var(--surf) anywhere in platform/frontend, in either theme. Its
     [data-theme="light"] counterpart (css/widgets.css) deleted in the
     same commit. --surf-hi above is NOT dead (2 consumers) — this is a
     single-name deletion inside a two-name group, not a whole-group call. */

  /* SURVIVES: --line called by 21 files, --line-strong by 13. */
  --line:        var(--ra-border-default);
  --line-strong: var(--ra-border-card);

  /* Text ramp — SURVIVES: --text-1 28 files, --text-2 21, --text-3 24,
     --text-4 26 — the widest-used names in this section. */
  --text-1: var(--ra-text-primary);
  --text-2: var(--ra-text-secondary);
  --text-3: var(--ra-text-tertiary);
  --text-4: var(--ra-text-faint);

  /* Semantic colors — SURVIVES: --pos 18 files, --neg 22, --info 18,
     --warn 9, --pos-bg 4, --neg-bg 4, --info-bg 10. */
  --pos:     oklch(0.78 0.14 var(--hue-pos));               /* -> #0FD4BB, exact match to market.up, unchanged */
  --pos-light: oklch(0.84 0.097 var(--hue-pos));
  --pos-bg:  oklch(0.30 0.08 var(--hue-pos) / 0.18);

  /* design/PLAN.md §4.5 "Option C": free L, C AND H (not hue-only) is the
     only route that hits market.down #EF4444 exactly while keeping the
     hue-based Customization slider alive. L 0.72->0.638, C 0.18->0.208,
     hue 12->25.5 (declared above). Verified independently by both the
     architect and this dev via oklch->sRGB conversion: exact byte match.
     features/chart/render.js's own down-formula sites (6 of them) get the same
     L/C re-pin in S4 — see design/PLAN.md §4.5 / §5 S4. */
  --neg:     oklch(0.638 0.208 var(--hue-neg));              /* -> #EF4444 exact */
  --neg-light:  oklch(0.615 0.115 var(--hue-neg-light));
  --neg-bg:  oklch(0.30 0.12 var(--hue-neg) / 0.18);

  --info:     oklch(0.74 0.15 var(--hue-info));
  --info-bg:  oklch(0.32 0.10 var(--hue-info) / 0.22);

  --warn: oklch(0.82 0.15 85);

  /* --pos-dim/--neg-dim/--info-dim DELETED (design/PLAN.md §8.1 #15, the
     literal act of the P5 phase): grepped every var(--pos-dim),
     var(--neg-dim), var(--info-dim) call site across platform/frontend —
     zero consumers found for any of the three, in either theme. Their
     [data-theme="light"] counterparts (css/widgets.css) deleted in the
     same commit. Genuinely orphaned, not just off migrationDebt; deleting
     them changes nothing on screen. */

  /* --glow-pos/--glow-info/--glow-neg/--shadow-lg DIE here — design/PLAN.md
     §4.3: zero DROP_SHADOW effects anywhere in the 2.0 section, surfaces
     are separated by a 1px border instead. Not aliased, not carried
     over: any `box-shadow: var(--glow-pos)` etc. left in an unmigrated
     file (css/components.css, css/widgets.css, css/login.css,
     panels/screener/screener.css — none owned by this session) simply
     resolves to no shadow, which IS the 2.0 look. No crash, no console
     error: an undefined custom property with no fallback makes the
     `box-shadow` declaration invalid at compute time and the property
     falls back to its initial value (none). */

  /* Hoisted UI-gradient formulas (design/PLAN.md §9.1: this file is the
     only file allowed a colour literal). Each VALUE is unchanged from its
     old inline site — only its home moved, so the consuming file can drop
     off migrationDebt. --scrollbar-thumb-hover shipped in S1 (base.css's
     :root block emptied out that commit); the rest are hoisted here in S2,
     when shell.css/app.css are next edited for the header/tape/workspace
     restyle — not preemptively in S1, per the team-lead's instruction not
     to mix steps in one commit. */
  --scrollbar-thumb-hover: oklch(0.35 0.01 var(--bg-hue));                 /* css/base.css */
  /* --topbar-gradient-top / --topbar-underline-glow DELETED (design/PLAN.md
     §8.1 #6): their only consumers were .topbar's 1.0 gradient background
     and the .topbar::after underline glow, both deleted in this commit
     (css/shell.css). An orphan token is the seed of the next stacked UI. */
  --avatar-ring-info: oklch(0.7 0.12 var(--hue-info));                     /* css/shell.css .avatar */
  --avatar-ring-pos: oklch(0.7 0.12 var(--hue-pos));
  --mask-edge-opaque: #000;                                                /* css/shell.css .ticker-bar mask-image; alpha-only, not a design colour */
  --tape-flash-glow: oklch(0.5 0.14 var(--hue-info) / 0.45);               /* css/app.css @keyframes tape-flash */
  --search-focus-border: oklch(0.55 0.12 var(--hue-info) / 0.6);          /* css/app.css .search-pill:focus-within */
  --placeholder-glow: oklch(0.5 0.12 var(--hue-info) / 0.18);              /* css/app.css .placeholder-card::before */
  /* D16 (the shared features, 2026-10-05): the alerts drawer and card, the squeeze review card, the SEC form tag, hoisted
     the same way: each VALUE is unchanged from its inline site, only its home moved, so the files drop their colour
     literals (the post-login ident, shell/ident.js, reads the brand splash's --ra-ident-* tokens, below). Hue-relative values compute here on the root, where applyCust() and the
     light theme set the hues, as they did inline. */
  --ra-sig-alert-up-line: oklch(0.55 0.12 var(--hue-pos) / 0.7);           /* .sig-alert.up (features/alerts/alerts.js) */
  --ra-sig-alert-down-line: oklch(0.55 0.14 var(--hue-neg) / 0.7);
  --ra-sig-accent-up: oklch(0.82 0.16 var(--hue-pos));                     /* .sig-alert-accent, top of the gradient */
  --ra-sig-accent-down: oklch(0.78 0.2 var(--hue-neg));
  --ra-sqz-rule: rgba(255,255,255,0.08);                                   /* features/squeeze-overlay/squeeze_overlay.js .sqz-flags */
  --ra-sqz-flag-bear: oklch(0.7 0.18 25);
  --ra-sqz-flag-bull: oklch(0.78 0.16 145);
  --ra-sqz-bull-bg: oklch(0.20 0.07 145 / 0.92);                           /* .sqz-ov.bull */
  --ra-sqz-bull-line: oklch(0.55 0.16 145);
  --ra-sqz-bull-ink: oklch(0.95 0.10 145);
  --ra-sqz-bear-bg: oklch(0.20 0.10 25 / 0.94);                            /* .sqz-ov.bear */
  --ra-sqz-bear-line: oklch(0.58 0.20 25);
  --ra-sqz-bear-ink: oklch(0.95 0.10 25);
  --ra-sqz-neutral-bg: oklch(0.18 0.02 260 / 0.92);                        /* .sqz-ov.neutral */
  --ra-sqz-neutral-line: oklch(0.45 0.05 260);
  --ra-form-tag-line: oklch(0.55 0.12 var(--hue-info) / 0.4);              /* features/sec-forms/sec-forms.js .form-tag */

  /* S11-R21 (OWNER, granted to dev-B): panels/news/news.css's one
     migrationDebt literal, hoisted here per the same "each VALUE
     unchanged, only its home moved" pattern as the rest of this block --
     token_lint's exemption is per-FILENAME (tokens.css / tokens.*.css),
     not per-declaration-site (token_lint.py:67), so materialising the
     literal inside news.css itself would move it, not remove it. */
  --news-item-hover-bg: oklch(0.22 var(--bg-chroma) var(--bg-hue) / 0.55); /* panels/news/news.css .news-item:hover */

  /* The account chrome's colour literals (task D4 of design/behavior/PLAN_FULL_ATOMIC.md, 2026-10-05), hoisted from
     ds/organisms/account-{avatar,popover,settings,adjust}.css by the same "each VALUE unchanged, only its home moved"
     rule as the rest of this block, so those four files leave migrationDebt. Theme-agnostic, as the literals were:
     they read --hue-info / --bg-hue / --bg-chroma, which the Customization section stamps on this same element. */
  --acct-mark-ink:          oklch(0.98 0.01 250);                         /* account-avatar .am-mark letter */
  --acct-mark-ring:         oklch(1 0 0 / 0.12);                          /* account-avatar .am-mark inset ring */
  --acct-head-tint:         oklch(0.3 0.08 var(--hue-info) / 0.22);       /* account-popover .acct-head */
  --acct-item-hover-bg:     oklch(0.3 0.05 var(--hue-info) / 0.18);       /* account-popover .acct-item:hover */
  --set-head-tint:          oklch(0.2 var(--bg-chroma) var(--bg-hue) / 0.8);   /* account-settings .set-head */
  --set-foot-tint:          oklch(0.18 var(--bg-chroma) var(--bg-hue) / 0.85); /* account-settings .set-foot */
  --set-nav-on-top:         oklch(0.28 0.06 var(--hue-info) / 0.35);      /* account-settings .set-side .nav.on */
  --set-nav-on-bottom:      oklch(0.22 0.04 var(--hue-info) / 0.15);
  --set-nav-on-border:      oklch(0.5 0.12 var(--hue-info) / 0.5);
  --set-tog-thumb:          oklch(0.55 0.01 var(--bg-hue));               /* account-settings .tog::after */
  --set-tog-on-bg:          oklch(0.5 0.14 var(--hue-info) / 0.6);        /* account-settings .tog.on */
  --set-tog-on-border:      oklch(0.55 0.14 var(--hue-info) / 0.6);
  --set-tog-on-ring:        oklch(0.55 0.14 var(--hue-info) / 0.25);
  --set-card-on-border:     oklch(0.55 0.12 var(--hue-info) / 0.7);       /* .av-item.on, .th-card.on, .ac-card.on, .bg-card.on */
  --set-th-on-tint:         oklch(0.28 0.06 var(--hue-info) / 0.3);       /* .th-card.on */
  --set-ac-on-tint:         oklch(0.28 0.06 var(--hue-info) / 0.25);      /* .ac-card.on, .bg-card.on */
  --set-swatch-border:      oklch(0 0 0 / 0.25);                          /* .ac-card .sw, .bg-card .sw, .hue-row .chip */
  --set-swatch-ring:        oklch(1 0 0 / 0.1);                           /* .ac-card .sw, .bg-card .sw inset ring */
  --set-del-hover-border:   oklch(0.55 0.14 var(--hue-neg) / 0.5);        /* .del-btn:hover */
  --set-hue-track: linear-gradient(90deg, oklch(0.7 0.16 0), oklch(0.7 0.16 60), oklch(0.7 0.16 120), oklch(0.7 0.16 180), oklch(0.7 0.16 240), oklch(0.7 0.16 300), oklch(0.7 0.16 360));   /* .hue-row .track::before */
  --set-card-light-bg:      oklch(1 0 0);                                 /* account-adjust, light theme: .fp, .av-preview and the cards */
  /* The shell/account/ parts' own colour literals (task f1b-account-settings of design/behavior/PLAN_FULL_ATOMIC.md,
     2026-10-06), hoisted from shell/account/state.js and customization.js by the same rule, so both leave migrationDebt.
     Where a preview's hue is the user's (an accent card, a background card, the hue chip, the accent preview), the token
     holds the lightness and chroma that preview always used ("L C", composed as oklch(var(--set-sw-lc) <hue>)). */
  --acct-av-aurora-from:    oklch(0.7 0.12 180);                          /* shell/account/state.js S.AV, the avatar presets */
  --acct-av-aurora-to:      oklch(0.7 0.14 250);
  --acct-av-ember-from:     oklch(0.78 0.14 40);
  --acct-av-ember-to:       oklch(0.6 0.18 20);
  --acct-av-violet-from:    oklch(0.7 0.18 300);
  --acct-av-violet-to:      oklch(0.55 0.18 270);
  --acct-av-moss-from:      oklch(0.7 0.14 150);
  --acct-av-moss-to:        oklch(0.45 0.12 160);
  --acct-av-dune-from:      oklch(0.8 0.1 80);
  --acct-av-dune-to:        oklch(0.55 0.08 60);
  --acct-av-glacier-from:   oklch(0.82 0.08 220);
  --acct-av-glacier-to:     oklch(0.5 0.12 240);
  --acct-av-monogram: conic-gradient(from 120deg, oklch(0.65 0.14 var(--hue-info)), oklch(0.7 0.12 var(--hue-pos)), oklch(0.65 0.14 var(--hue-info)));   /* the Monogram preset */
  --set-thp-dark-bg:        oklch(0.14 0.015 250);                        /* customization.js theme cards: the Dark preview */
  --set-thp-dark-line:      oklch(0.3 0.01 250);
  --set-thp-dark-panel:     oklch(0.2 0.015 250);
  --set-thp-dark-t1:        oklch(0.96 0.005 250);
  --set-thp-dark-t3:        oklch(0.58 0.01 250);
  --set-thp-dark-chart:     oklch(0.17 0.01 250);
  --set-thp-light-bg:       oklch(0.955 0.008 75);                        /* ... the Light preview */
  --set-thp-light-line:     oklch(0.45 0.01 75 / 0.2);
  --set-thp-light-panel:    oklch(1 0 0);
  --set-thp-light-t1:       oklch(0.22 0.01 70);
  --set-thp-light-t3:       oklch(0.52 0.012 70);
  --set-thp-light-chart:    oklch(0.12 0.01 250);
  --set-thp-spark:          oklch(0.72 0.14 var(--hue-info));             /* ... both previews' sparkline */
  --set-sw-lc:              0.72 0.15;                                    /* .ac-card .sw, positive and info */
  --set-sw-neg-lc:          0.72 0.17;                                    /* .ac-card .sw, negative */
  --set-chip-lc:            0.72 0.16;                                    /* .hue-row .chip and its hex sample */
  --set-bgsw-dark-l:        0.14;                                         /* .bg-card .sw: lightness (the preset's chroma and hue) */
  --set-bgsw-light-l:       0.955;
  --set-prev-up-lc:         0.78 0.14;                                    /* #acPrevChart up candles (--pos's L C) */
  --set-prev-down-lc:       0.638 0.208;                                  /* ... down candles (--neg's L C) */
  --set-bub-prev-fallback:  rgb(170 170 170);                             /* #cBubPrev: a colour that is not a 6-digit hex */

  /* The Automations canvas and run history's colour literals (task D5 second pass, automations-b, of
     design/behavior/PLAN_FULL_ATOMIC.md, 2026-10-06), hoisted from ds/organisms/automations-{canvas,bottom}.css by the
     same "each VALUE unchanged, only its home moved" rule, so both files leave migrationDebt. Theme-agnostic, as the
     literals were; the --hue-* they read are stamped on this same element (shell/account/state.js applyCust). */
  --auto-canvas-glow-info:   oklch(0.22 0.04 var(--hue-info) / 0.18);   /* .auto-canvas-wrap, the top-left glow */
  --auto-canvas-glow-pos:    oklch(0.22 0.04 var(--hue-pos)  / 0.10);   /* .auto-canvas-wrap, the bottom-right glow */
  --auto-edge-and:           oklch(0.72 0.14 var(--hue-info) / 0.55);   /* path.edge-real.edge-and (cond to cond) */
  --auto-edge-then:          oklch(0.78 0.14 70 / 0.6);                 /* path.edge-real.edge-then (into an action) */
  --auto-edge-trigger:       oklch(0.72 0.14 var(--hue-pos)  / 0.6);    /* path.edge-real.edge-trigger */
  --auto-node-highlight:     oklch(1 0 0 / 0.03);                       /* .auto-node inset top highlight */
  --auto-node-shadow:        oklch(0 0 0 / 0.30);                       /* .auto-node drop shadow */
  --auto-node-shadow-lifted: oklch(0 0 0 / 0.4);                        /* .auto-node.selected, .auto-node.edge-target */
  --auto-node-selected-glow: oklch(0.5 0.14 var(--hue-info) / 0.35);    /* .auto-node.selected halo */
  --auto-node-trigger-tint:  oklch(0.22 0.04 var(--hue-pos) / 0.10);    /* .auto-node.trigger fill */
  --auto-node-cond-tint:     oklch(0.22 0.04 var(--hue-info) / 0.10);   /* .auto-node.cond fill */
  --auto-node-act-tint:      oklch(0.22 0.04 320 / 0.10);               /* .auto-node.act fill */
  --auto-act-accent:         oklch(0.72 0.16 320);                      /* .auto-node.act stripe */
  --auto-act-ink:            oklch(0.78 0.16 320);                      /* .auto-node.act kind label, + Action hover */
  --auto-socket-glow:        oklch(0.6 0.14 var(--hue-info) / .6);      /* .auto-socket:hover */
  --auto-menu-shadow:        oklch(0 0 0 / .45);                        /* .auto-socket-menu */
  --auto-run-skip-ink:       oklch(0.78 0.14 60);                       /* run history: skipped status, pill and filter dot */
  --auto-run-skip-bg:        oklch(0.65 0.12 60 / 0.16);                /* .hist-action-pill.refused */
  --auto-run-skip-border:    oklch(0.7 0.14 60 / 0.5);

  /* Rail nav-item icons (design/DESIGN_SPEC.md §1.3): mask-source SVGs.
     S3-11 (design/PLAN.md §S3 D-S3-3 REVISED) inlines the 4 Figma-exported
     tabs + 3 rail-utils via foundation/icons.js's hydrateRail() — these
     `--icon-nav-*`/`--icon-rail-*` masks now serve only as the PER-BUTTON
     FALLBACK for a tab that never hydrates (JS blocked, a name the
     registry doesn't know), so the rail can never blank. Re-pointed from
     the 7 legacy PNGs (deleted, platform/frontend/icons/) to the real
     vectors under assets/icons/ — same files hydrateRail() embeds, so a
     fallen-back button and a hydrated one read identically at rest. 4 come
     from Figma (321:222/225/228/231 Screener/Intelligence/Stats/Trading +
     the bottom group 321:235/239/242 Keyboard Shortcuts/Settings/Log Out);
     the app has 9 tabs and the 2.0 frame draws icons for 4 (design/PLAN.md
     §4.8 — all 9 render, 5 have no Figma icon). Those 5 (Stream/News/
     Composer/Automations/Watchlists) are new SVGs drawn in this session's
     house line-icon style (the existing topbar buttons' 1.4-2px stroke,
     currentColor, round caps) — derived from the system per design/PLAN.md
     §4.2's precedent for gaps the file doesn't answer, not invented from
     nothing. They stay mask-only (no registry entry, no frame vector).
     CD-607 (owner ruling: keep all 9 rail items — the 5 the frame doesn't
     model are real panels and keep their entry point): this comment
     already claimed all nine live "under assets/icons/", but five of the
     six url()s below still pointed at the pre-migration /static/icons/
     path — the one second-vocabulary directory CD-607 asks to retire.
     Moved the files (git mv, same content, not re-drawn) and re-pointed
     the URLs so the comment finally matches what it always described. */
  --icon-nav-stream: url(/static/assets/icons/nav-stream.svg);
  --icon-nav-screener: url(/static/assets/icons/nav-screener.svg);
  --icon-nav-news: url(/static/assets/icons/nav-news.svg);
  --icon-nav-composer: url(/static/assets/icons/nav-composer.svg);
  --icon-nav-automations: url(/static/assets/icons/nav-automations.svg);
  --icon-nav-watchlists: url(/static/assets/icons/nav-watchlists.svg);
  --icon-nav-intelligence: url(/static/assets/icons/nav-intelligence.svg);
  --icon-nav-stats: url(/static/assets/icons/nav-stats.svg);
  --icon-nav-trading: url(/static/assets/icons/nav-trading.svg);
  --icon-rail-kbd: url(/static/assets/icons/nav-hotkeys.svg);
  --icon-rail-settings: url(/static/assets/icons/nav-settings.svg);

  /* ============================================================
     2b. Gradient exceptions (design/PLAN.md §3 D1 / design/DESIGN_SPEC.md
     §8.7). TOKENS.json is frozen, so #067D6A and #99F0E2 (both gradient
     stops with no token) are minted HERE instead — the one file token_lint
     exempts and the one place a colour literal is legitimate. Four
     documented exceptions, not a system-wide gradient primitive (escalated
     to the team-lead per PLAN §3 D1 for a session-3 mint). Second stop of
     --ra-gradient-chart-area is written as an explicit rgb()-with-alpha-0,
     not the bare `transparent` keyword — `transparent` resolves to
     rgba(0,0,0,0), and CSS gradients interpolate the RGB channels even at
     shrinking alpha, so fading toward black-transparent instead of
     067D6A-transparent would tint the fade grey partway down.
     ============================================================ */
  --ra-gradient-chart-area: linear-gradient(     /* 331:3727, 461:2584 */
    180deg,
    color-mix(in oklch, var(--ra-accent-primary) 30%, transparent),
    rgb(6 125 106 / 0)                            /* #067D6A @ alpha 0 */
  );
  --ra-gradient-ai-button: linear-gradient(90deg, #99F0E2, var(--ra-accent-primary));   /* 321:2108, button stroke */
  --ra-gradient-ai-sparkle: linear-gradient(90deg, #99F0E2, var(--ra-accent-primary));  /* 321:3676, sparkle icon fill */
  --ra-gradient-gauge-arc: linear-gradient(90deg, var(--ra-market-sell), var(--ra-accent-primary)); /* 321:1795, both stops tokenised */

  /* ============================================================
     2c. Unbound type scale (design/PLAN.md §3 D3 / design/DESIGN_SPEC.md
     §8.6): 170 text nodes across the five frames carry no Figma text style,
     and TOKENS.typography is frozen so none can be minted. These five
     clusters cover 131 of the 170; measured, not designed — flagged for a
     session-3 mint. Every line-height is size × 1.2102 (Figma's auto).
     `m` suffix = Inter 500 (medium); no suffix = Inter 400 (regular).
     ============================================================ */
  /* .t-u-13:  Base 321:306 "08:39" */
  /* .t-u-11m: Base 321:285 "TIME" */
  /* .t-u-12:  status bar 321:2117 "NY 14:07:21 EDT" */
  /* .t-u-12m: status chips 321:2123/2125 "RTH"/"ETH" */
  /* .t-u-11:  the grip glyph 419:316 */

  /* ============================================================
     3. Chart canvas tokens (--ch-*), read by name via chTC() in
        features/chart/render.js and features/chart/trade-markers.js (see file header).
        S1 moved these out of css/base.css verbatim; S4 (this section) now
        maps each one with a 2.0 equivalent to its value per
        design/CHANGE_MAP.md §3.2 — BY VALUE, names untouched. Colours with
        no 2.0 equivalent stay legacy verbatim (design/TOKENS.json's
        legacy.chart_* group; entries for the ones mapped below are removed
        from that group in this same commit — the ratchet CHANGE_MAP §5
        describes).
     ============================================================ */
  /* Chart canvas typeface (session 2 QA F2 follow-up, human ruling): the
     canvas's ~26 ctx.font call sites (features/chart/render.js,
     features/chart/trade-markers.js, panels/screener/tpswave.js) hardcoded
     "JetBrains Mono" literally — canvas can't read CSS custom properties,
     so the --font-mono token switch (base.css:33) never reached them. QA
     confirmed the 2.0 frame's own chart axis glyphs are drawn in Inter
     (no base serif on the "1"), so this isn't a deliberate terminal-mono
     convention being overridden, it was the one region still on the old
     family. Read once by chTC() into its cached palette (same mechanism
     already serving every --ch-* colour) and interpolated into each
     ctx.font string — not theme-scoped (no [data-theme="light"] entry
     below): font-family isn't a themed property, and duplicating it there
     would just be more dead-alias debt (see P5's --surf/--pos-dim finding
     for what that costs later). */
  --ch-font: var(--font-sans);
  /* S30 item 4 (owner: "make the candle tails slightly thicker") — the
     high/low wick, in canvas device px. One step up from the previous
     1px (measured: WebGL's gl.lineWidth is clamped to [1,1] on this
     bench's ANGLE/GL backend per ALIASED_LINE_WIDTH_RANGE, so features/chart/
     gl.js draws the wick as a filled quad of this width rather than a
     GL_LINE). Not theme-scoped, same reasoning as --ch-font above: a
     width isn't a themed property, so no [data-theme="light"] entry. */
  --ch-wick-width: 2px;
  --ch-ax: var(--ra-chart-axis);                 /* was #888 -> darkens */
  --ch-text: var(--ra-text-primary);             /* was #ddd -> lightens */
  --ch-label-bg: var(--ra-surface-card);         /* was #333 */
  --ch-sep: var(--ra-chart-grid);                /* was #1a1a1a -> shifts to blue */
  --ch-draw: #fff; --ch-draw-label: #fff; --ch-draw-label-fg: #000;
  --ch-tip-bg: rgba(0,0,0,0.85); --ch-crosshair: var(--ra-chart-crosshair); /* was #555 -> strong change: white */
  --ch-tip-border: rgba(255,255,255,0.20);       /* new: shared tooltip border, features/chart/trade-markers.js */
  --ch-sub-line: var(--ra-chart-grid-sub);       /* was #2a2f38 -> darkens */
  /* Fitts & Miller 20 (Organisms screener-chart, delta-pane 740:5617 and the mode captions 740:18977 / 740:18978): the
     words a pane or a mode writes on the plot (a title, a scale, a caption) in text/secondary, which flips with the theme. */
  --ch-pane-text: var(--ra-text-secondary);
  /* Fitts & Miller 21 (Organisms screener-chart position-line 741:5609): the position line and its label's ring in
     accent/primary, the label on the tape surface (panels/trading/chart-lines.js); both flip with the theme. */
  --ch-position: var(--ra-accent-primary); --ch-position-bg: var(--ra-surface-tape);
  /* The Buy/Sell TPS wave (panels/screener/tpswave.js), the literals that file drew (D8, 2026-10-05): its buy and
     sell lines are the candles' formulas (features/chart/render.js _CP / _CN, PLAN.md §4.5 Option C), its "Waiting for
     trades" text and scale maximum two white washes. Not theme-scoped: the light theme draws them the same. */
  --ch-wave-buy: oklch(0.78 0.14 var(--hue-pos)); --ch-wave-sell: oklch(0.638 0.208 var(--hue-neg));
  --ch-wave-empty: rgba(255,255,255,0.28); --ch-wave-max: rgba(255,255,255,0.3);
  --ch-guide: rgba(90,90,90,0.5);
  --ch-scrim: rgba(10,12,16,0.62); --ch-scrim-fg: #d8d8e0;
  --ch-on-chip: #000; --ch-on-mark: #fff;
  --ch-macd: var(--ra-accent-blue);              /* was #4aa3ff */
  --ch-macd-sig: var(--ra-chart-vwap);           /* was #ff9f45 */
  --ch-rsi: var(--ra-chart-ema-fast);            /* was #c58af9 */
  --ch-delta-up-rgb: 40,100,220; --ch-delta-dn-rgb: 200,40,40;
  --ch-vah: #5dc88a; --ch-poc: #ffb84d; --ch-val: #e36b6b;   /* no 2.0 equivalent — 2.0 draws no volume profile */
  --ch-marker-buy: var(--ra-semantic-success);   /* was #3ddc84 */
  --ch-marker-sell: var(--ra-market-down-bright); /* was #ff5a5a -> nearly the same */
  --ch-order-buy: var(--ra-accent-blue);         /* was #4aa3ff */
  --ch-order-sell: var(--ra-market-up);          /* was #0fd4bb -> already matched */
  --ch-order-stop: var(--ra-market-down);        /* was #ff5870 */
  --ch-order-entry: var(--ra-semantic-warning);  /* was #f0b000 */
  --ch-order-trail: var(--ra-chart-ema-fast);    /* was #c060ff */

  /* EMA/VWAP palette (features/chart/render.js's chart-legend indicators, node
     321:3709). EMA50/EMA100/VWAP have real 2.0 tokens (design/DESIGN_SPEC.md
     §1.7: legend EMA100->chart.ema-slow, EMA50->chart.ema-fast). EMA9/200
     have no 2.0 legend entry at all — legacy carry-over, verbatim values,
     now tokenised instead of inline oklch() literals.
     SUPERSEDED 2026-08-25 (S27-R6, owner ruling on S27 addendum B item 1,
     "the indicators in the chart, the lines, are not consistent with the
     legends" -> "Lines adopt design colours"): this comment used to read
     "the DRAWN VWAP curve uses chart.vwap-line per design/CHANGE_MAP.md
     §3.1's documented legend/curve discrepancy — do not unify with
     chart.vwap on your own, it's an open design decision" and EMA21 was
     "no 2.0 legend entry" — both now RESOLVED, not still open: the design
     (the legend's own colour, panels/screener/screener.css's
     .chart-legend, CD-176/G29) is the source of truth the DRAWN line now
     matches, both here. --ch-vwap-line reads --ra-chart-vwap (was
     --ra-chart-vwap-line, now unconsumed by any --ch-* token — left
     defined at its own declaration as a palette value, not deleted).
     --ch-ema21 reads --ra-chart-ema-slow, same design teal EMA100 USED TO
     draw (CD-176 measured the FRAME itself paired them) — see S27-R9
     below, which overturns this specific pairing as a declared PRODUCT
     divergence from the frame, not a mistake in either prior finding.
     SUPERSEDED AGAIN 2026-08-25 (S27-R9, owner dialog): the paragraph
     above called EMA21==EMA100 "deliberately the same colour, a design
     fact, not a bug" — the owner has since ruled the two lines must be
     tellable apart at a glance, overturning CD-176's pairing as a
     declared product divergence from the frame (cited here so a future
     parity pass does not silently "fix" this back to match Figma).
     --ch-ema100 now reads the newly-minted --ra-chart-ema100 (a vivid
     magenta, measured min sRGB distance ~79 from every other overlay/
     sub-pane colour, both themes — closest is ema50/rsi's shared
     lavender-purple, every other line ≥92; design/s27b/dev-a/
     indicator_legend_readings.json task_22 entry has the full table)
     instead of --ra-chart-ema-slow. EMA21 itself is untouched by R9 and
     still reads --ra-chart-ema-slow per S27-R6. */
  --ch-ema9: oklch(0.78 0.18 70);
  --ch-ema21: var(--ra-chart-ema-slow);
  --ch-ema50: var(--ra-chart-ema-fast);
  --ch-ema100: var(--ra-chart-ema100);
  --ch-ema200: oklch(0.72 0.05 260);
  --ch-vwap-line: var(--ra-chart-vwap);
  --ch-bb-band: rgba(170,170,210,0.75);
  --ch-bb-mid: rgba(190,190,220,0.55);

  /* Psychological-level / bid-ask spread markers (features/chart/render.js
     "$"/"S$"/"BA" chart buttons) — a legacy feature with no 2.0 frame at
     all. LCH triple with no alpha, so call sites vary opacity per
     emphasis level same as before; values are verbatim, just named. */
  --ch-psych-lch: 0.68 0.15 235;
  --ch-psych-label-bg: oklch(0.5 0.16 235 / 0.92);
  --ch-psych-label-border: oklch(0.68 0.15 235 / 0.8);
  --ch-psych-tag-big: oklch(0.85 0.18 235);
  --ch-psych-tag-whole: oklch(0.8 0.16 235);
  --ch-psych-tag-minor: oklch(0.72 0.14 235 / 0.7);
  /* Psych price-level gridlines (features/chart/render.js s.psych "$" toggle) —
     a distinct legacy feature from the psych-touches group above (gold, not
     blue). RGB triple, same pattern as --ch-delta-up/dn-rgb below, since
     call sites vary the alpha per emphasis (whole-dollar vs half-dollar). */
  --ch-psych-gold-rgb: 255,200,0;
  /* Live NBBO bid/ask spread lines (features/chart/render.js s.spread "BA"
     toggle). Fixed alpha at every call site, so a flat rgba() is enough. */
  --ch-spread-bid: rgba(60,200,140,0.85);
  --ch-spread-ask: rgba(230,80,110,0.85);
  /* Big-trade bubble neutral fallback (features/chart/render.js _bc()) — used
     when no buy/sell direction and no user hex override. RGB triple: the
     call site multiplies the user's opacity slider onto this. */
  --ch-bubble-neutral-rgb: 170,170,170;
  /* Drawing-tool hover/preview overlays (features/chart/render.js, rvp/rect/line
     tools) — theme-dependent (light needs a dark wash, dark needs a light
     wash), so dark values live here and the [data-theme="light"] block
     below carries the light variants, same pattern as the rest of --ch-*.
     This replaces the old inline `_lt ? '...' : '...'` ternary in JS. */
  --ch-hover-fill: rgba(255,255,255,0.06);
  --ch-hover-crosshair: rgba(255,255,255,0.6);
  --ch-hover-preview: rgba(255,255,255,0.5);
  /* Extended-hours shading band. Same theme-dependent-wash pattern. */
  --ch-eth-shade: oklch(0.05 0.02 250 / 0.45);
  /* Chart patterns (features/chart/patterns.js, 2026-09-16). The bias colours alias
     the trade-marker pair and Entry/SL alias the trading overlay's order
     vocabulary, so one colour keeps one meaning on the canvas; TP reads the
     market-up role. Aliases re-resolve under [data-theme="light"] through
     their targets (the --ch-gex-wall-* precedent below), so no light entry.
     The picker swatches (chart/indicators-dd.js) read the same var()s. */
  --ch-pat-bull: var(--ch-marker-buy);
  --ch-pat-bear: var(--ch-marker-sell);
  --ch-pat-entry: var(--ch-order-entry);
  --ch-pat-sl: var(--ch-order-stop);
  --ch-pat-tp: var(--ra-market-up);
  /* The candle roles on the user's accent hues (chart-b, 2026-10-06): the literals features/chart/render.js and its
     layers built from --hue-pos/neg/info, hoisted with the same L, C and alpha, so no pixel moves and the
     Customization sliders still move them (chTC() rereads them on _chThemeInvalidate()). Theme-independent,
     as the literals were, so no light entry. --ch-up/--ch-dn/--ch-info are the default candle colours (a hex
     override in Customization wins) and the bubbles' base, painted at the opacity setting via rgba(). */
  --ch-up: oklch(0.78 0.14 var(--hue-pos));
  --ch-dn: oklch(0.638 0.208 var(--hue-neg));
  --ch-info: oklch(0.74 0.15 var(--hue-info));
  --ch-last-up: oklch(0.78 0.14 var(--hue-pos) / 0.55);      /* the dashed last-price line */
  --ch-last-dn: oklch(0.638 0.208 var(--hue-neg) / 0.55);
  --ch-area-up-top: oklch(0.78 0.14 var(--hue-pos) / 0.05);  /* the area gradient under the closes: top, 35 %, bottom */
  --ch-area-up-mid: oklch(0.78 0.14 var(--hue-pos) / 0.22);
  --ch-area-up-end: oklch(0.78 0.14 var(--hue-pos) / 0);
  --ch-area-dn-top: oklch(0.78 0.18 var(--hue-neg) / 0.05);
  --ch-area-dn-mid: oklch(0.78 0.18 var(--hue-neg) / 0.22);
  --ch-area-dn-end: oklch(0.78 0.18 var(--hue-neg) / 0);
  --ch-blocks-buy: oklch(0.72 0.14 var(--hue-info));         /* the blocks mode's buy share */
  --ch-line: oklch(0.78 0.14 var(--hue-info));               /* the line mode */
  --ch-vol-up: oklch(0.7 0.13 var(--hue-pos) / 0.55);        /* the volume histogram */
  --ch-vol-dn: oklch(0.538 0.198 var(--hue-neg) / 0.55);
  --ch-hist-up: oklch(0.7 0.13 var(--hue-pos) / 0.65);       /* the MACD histogram */
  --ch-hist-dn: oklch(0.538 0.198 var(--hue-neg) / 0.65);

  /* ==========================================================================
     GEX — gamma exposure. S40 P0, owner ruling OR1 (2026-08-31).

     THIS WHOLE BLOCK IS THE SWAPPABLE GROUP. The owner ruled the ramp is
     DERIVED from existing tokens now, rather than authored in Figma, and
     accepted that it will not match his reference screens exactly. If a real
     ramp is authored in Figma later, replace THIS BLOCK — nothing in gex.js,
     gex.css, render.js or any panel changes. That is the whole reason the
     stops are color-mix() over two role tokens instead of eight literals:
     eight literals would be eight edits and eight allowlist entries.

     The two role tokens are HAND_SOURCED in design/build_tokens.py and
     design/TOKENS.json (gex.positive = Accent/Blue, gex.negative =
     Semantic/Error) — values unchanged from the frame, only the naming is
     new. Filing them as DIVERGENCES would ABORT the token build; see
     design/s40/token_table_dryrun.out for that run.

     COLOUR IS NEVER THE ONLY CHANNEL (design/PLAN_S40.md A-2). Bar LENGTH
     carries magnitude, an explicit +/- glyph carries sign, and chart levels
     are told apart by a reserved dash pattern. Text NEVER sits on a heat
     cell: a ramp runs from near-background to full saturation, so no single
     text colour passes at both ends. If removing all colour from these
     surfaces would lose information, the design is wrong.

     EVERY STOP IS MIXED TOWARD --ra-text-primary, AND THAT IS THE WHOLE
     TRICK. Two constructions were measured and rejected before this one:
       1. Fading each stop toward the card background. Only ONE step per side
          clears WCAG 1.4.11's 3:1 non-text floor (20% -> 3.87, then 2.77 /
          1.93 / 1.35). A ramp whose faint end has no findable edge is not a
          ramp.
       2. Mixing toward a FIXED light colour. Clears dark theme easily and
          FAILS light theme outright: on the light card (--bg-3 #DBD7D0) the
          role tokens measure 1.92 and 1.93, and only the hottest stop
          recovers. "Upward in lightness" is not theme-invariant.
     --ra-text-primary already flips with the theme (#EEF1F1 dark, #111
     light), so mixing toward it means "more prominent against THIS theme's
     background" in BOTH themes — lighter on dark, darker on light — and the
     ramp points the right way in each without a single [data-theme="light"]
     override. Measured, re-runnable via design/s40/ramp_contrast.py:
                     dark (on #23282C)      light (on #DBD7D0)
         pos  lo 30%   7.21                   3.54
         pos  mid 45%  8.24                   4.90
         pos  hi 60%   9.43                   6.86
         neg  lo 30%   7.20                   3.56
         neg  mid 45%  8.26                   4.92
         neg  hi 60%   9.42                   6.87
     All six clear 3.0 in both themes, and the two sides stay CIEDE2000
     25-34 apart at every stop, so a positive can never be read as a negative.

     THE COST, STATED: the hot end is progressively DESATURATED. That is
     acceptable here and only here, because bar length already carries the
     magnitude — colour is the redundant channel (A-2). If colour were the
     carrier this construction would be wrong.

     Hue is untouched at every stop: the correction is entirely lightness,
     because the hue is the owner's ruling. */
  /* OWNER-RULED RE-PICK — S44, OR-S44-24. Derived by the ux-designer in
     design/s44/GAMMA_COLOUR_S44.md; every figure below is a PAINTED PIXEL
     through design/s40/ramp_contrast.py, alpha preserved, not a token read.

     WHY BOTH SIDES MOVED:
     - the old positive was byte-identical to --ch-order-buy (dE00 0.00,
       both themes) — gamma bars drawn in the reader's own buy-order colour.
       The owner had chosen that hue himself in S40 OR1 precisely so gamma
       could not be confused with a CANDLE; it dodged that neighbour and
       landed on this one.
     - the old negative failed in BOTH themes, and the binding neighbour was
       --ch-marker-sell (9.26 light, 14.39 dark neg-lo), not the candle the
       first report named. A red negative could not be kept.

     WHAT THIS IS: not a new hue — the SAME olive/violet pair the walls,
     chips and gutter already draw (OR-S44-5). After it, Net, Split, the
     overlays and the gutter all speak one gamma colour.

     MEASURED, across all six neighbours (both candles, both order colours,
     both markers) in BOTH themes, and INDEPENDENTLY RE-VERIFIED by design-qa
     (task #68, QA_VERDICT_S44.md section 16) — every figure below is a
     painted pixel unless it says otherwise:

     - Worst NEIGHBOUR distance, token-level: 22.01, dark pos-mid vs
       marker-buy, against the 14.93 floor. Worst among stops a reader can
       ACTUALLY SEE: 22.24 — pos-mid is never painted by this chain, so the
       visible margin is wider than the token-level worst, not narrower.
     - Lowest contrast 4.90:1 is measured on --ra-surface-card, which
       tokens.css:1444 documents as this family's ground. THE LADDER PAINTS
       ITS ROWS ON --ra-surface-panel, where the same stop measures 6.15.
       Both clear the 3.0 floor; the 4.90 is the conservative reading.
       THIS FAMILY SERVES TWO GROUNDS — quote the one you measured on.
     - Painted vs token: 10 of 10 identical. No antialiasing erosion at
       these colours. Three stops (pos-mid, pos-hi, call-hi) are never
       rendered by a normal dataset — halfHtml() tiers at pct>=66/33, so a
       single dominant wall leaves nearly every row at `lo`.

     DO NOT confuse an INTERNAL distance with a NEIGHBOUR distance. The
     Split ramp's 20.23 is call-hi vs put-hi in LIGHT — the ramp's own
     positive-vs-negative separation at its tightest stop, NOT a distance to
     anything external. Split's worst against the six neighbours is 22.24
     dark / 25.39 light. Conflating the two is exactly what produced the
     stale "worst 24.22" claim this re-pick had to correct.

     NOTE the neighbour list is effectively FIVE, not six: --ch-order-sell
     and the up-candle are the same value (dE00 0.00, both themes), so
     "far from the up-candle" and "far from order-sell" are one constraint.
     It does not bind this pick; it removes a degree of freedom a future one
     may need. */
  --ch-gex-pos: var(--ch-gex-wall-call);
  --ch-gex-neg: var(--ch-gex-wall-put);
  --ch-gex-zero: var(--ra-surface-card); /* no exposure reads as background */
  --ch-gex-pos-lo: color-mix(in oklab, var(--ch-gex-pos) 70%, var(--ra-text-primary));
  --ch-gex-pos-mid: color-mix(in oklab, var(--ch-gex-pos) 55%, var(--ra-text-primary));
  --ch-gex-pos-hi: color-mix(in oklab, var(--ch-gex-pos) 40%, var(--ra-text-primary));
  --ch-gex-neg-lo: color-mix(in oklab, var(--ch-gex-neg) 70%, var(--ra-text-primary));
  --ch-gex-neg-mid: color-mix(in oklab, var(--ch-gex-neg) 55%, var(--ra-text-primary));
  --ch-gex-neg-hi: color-mix(in oklab, var(--ch-gex-neg) 40%, var(--ra-text-primary));
  /* `in oklab`, NOT `in oklch` — S44, same derivation. `oklch` interpolates
     along a HUE PATH, and light theme's --ra-text-primary is a WARM
     near-black (hue ~70). At the `hi` stop the ink is 60% of the mix, so a
     violet rotates THROUGH RED to reach it: the light negative high stop
     came out brown instead of dark violet. `oklab` interpolates
     rectangularly and has no hue path to travel. Dark moves by dE00 ~0.3 —
     it costs nothing there.
     THE SPLIT RAMP BELOW IS DELIBERATELY LEFT ON `oklch`: it shares this
     construction and PASSES today (worst 20.23, call-vs-put at light `hi`,
     against the 14.93 floor). `oklab` would widen that to 35.56 — a SHOULD,
     not a must, and changing it would alter a mode the owner has already
     reviewed. Recorded rather than taken. */

  /* CHART-SIDE gamma tokens — REVISED S41 (owner ruling HANDOFF_S41 §0b,
     2026-08-31, implemented 2026-09-01 at his direct instruction): colour is
     now the PRIMARY channel for the gamma levels — SOLID, FULL-WIDTH,
     HVL-style — reversing A-2's dash-carries-identity for this group only.
     Identity still never rides hue alone (the greyscale test survives):
     every level draws a text chip (HVL / C1..C3 / P1..P3) plus its coloured
     axis tag (render.js _gexChip/_yTagQ).
     MEASURED, NEVER PICKED — same instrument as the S40 env pass; the pick
     search and every number below re-run via
     `./venv/bin/python design/s41/gexline_hue_pick.py` (.out committed).
       FLIP: the owner's reference cyan is "the direction" — but a BRIGHT
       cyan cannot clear the palette's own 14.93 CIEDE2000 floor on this
       canvas: candle-up #0FD4BB and price-line #7EE0CD own that region,
       the same measured wall the S40 hue search hit at 215-290. This is
       the brightest FLOOR-CLEAN cyan: dark #597C7C (min dE00 15.62,
       nearest --ch-ax/ema21; bg 4.09:1; black tag ink 4.60:1), light
       #086B6C (min dE00 20.21; bg 6.31:1). Emphasis rides line WIDTH
       (2px vs the walls' 1.2) — the recorded fallback channel. Wanting it
       hotter means accepting a sub-floor distance to the teals: an owner
       trade, one edit here.
       WALLS split per SIDE and alias the two S40-measured survivor hues
       VERBATIM via the env tokens kept below for exactly this revival
       (olive 110 = call, violet 320 = put; per-theme values + light
       overrides ride along for free): min dE00 22.22/18.89 dark,
       24.31/24.42 light; bg 5.91/3.69 dark, 4.76/7.43 light. Mutual
       flip/call/put distances 24-69. Violet is not the reference's
       literal red because red is measurably banned here (gex.negative
       sits dE00 9.78 from candle-down — the recorded trap). */
  --ch-gex-flip: oklch(0.56 0.04 195);
  --ch-gex-wall-call: var(--ch-gex-env-pos);
  --ch-gex-wall-put: var(--ch-gex-env-neg);

  /* S43 -- the CARD ramp for the diverging CALL/PUT ladder (design/PLAN_S43.md
     A-1). NO NEW HUE: these are the two S40-measured survivor hues above,
     --ch-gex-wall-call / --ch-gex-wall-put, put through the SAME three-stop
     lift the pos/neg ramp already uses at :1433-1438 --
     color-mix(in oklch, <hue> {70,55,40}%, var(--ra-text-primary)). Minted
     HERE, once, because gex.css only ever references a ramp (its own header
     rule) and a Figma-authored ramp must be able to replace this block in
     one file. Both inputs are already theme-overridden, so the stops follow
     light/dark for free -- no [data-theme="light"] entry is needed or wanted.

     RE-RUN THE NUMBERS BELOW, DO NOT QUOTE THEM.
       ./venv/bin/python design/s43/token_stops_check.py
     Raw output design/s43/token_stops_check.out. The first version of THIS
     COMMENT quoted figures that disagreed with that output on every value
     (err:d9439589035b) -- a comment in tokens.css is what the next sessions
     read instead of re-running, which is exactly why it has to be re-run.

     HOW THE STOPS ARE MEASURED, because the obvious way is wrong twice over.
     They are NOT modelled in Python. design/s43/computed_stops_dump.js paints
     each var() into a 1x1 canvas and reads the device pixel back; only the
     JUDGEMENT (contrast, CIEDE2000) uses design/s40/ramp_contrast.py. Two
     independent defects sit in the modelled route, and BOTH inflate the
     readings, i.e. both make this ramp look safer than it is:
       1. mix_oklab(h1, h2, pct) weights pct toward h2 -- its own docstring
          says color-mix(in oklch, h1, h2 <pct>%). Passing the CSS's own 70
          as mix_oklab(hue, text, 70) therefore asks for 70% TEXT, the
          inverse of what the CSS declares. Dark call-lo: #D7D5B0 (9.94:1)
          modelled, #AFB165 (6.59:1) painted.
       2. `in oklch` is POLAR and interpolates HUE; mix_oklab is rectangular.
          Small in dark, but in light it ROTATES the put ramp 320 -> 26, i.e.
          violet drifts toward red -- the one family this palette bans.

     WHY THE LIFT IS NOT OPTIONAL. RAW violet reads 2.94:1 on the dark card
     #23282C, UNDER the 3.0 non-text floor (WCAG 1.4.11). Lifted, every stop
     clears it -- card contrast, DARK: call 6.59/7.74/8.97 (lo/mid/hi), put
     4.88/6.19/7.71; LIGHT: call 5.16/6.35/7.82, put 6.99/8.00/9.14. Contrast
     RISES as the stop lifts toward the text colour, in both themes; a table
     here that falls is a transcription error, no instrument needed to see it.
     Every stop also clears the 14.93 CIEDE2000 floor against BOTH candles,
     worst 24.22 (dark call-hi vs candle-up). Call-vs-put at the same stop:
     54.34 lo / 46.56 mid / 38.28 hi dark, 43.04 / 30.97 / 20.23 light -- the
     distance that makes a diverging bar readable.

     THESE STOPS MUST NEVER MIGRATE ONTO THE CHART CANVAS. Measured, same
     run, section [C]: dark put-mid sits 6.40 from --ch-ema50, put-hi 8.98
     and put-lo 9.54, all far under the canvas floor of 14.93. The canvas
     uses the RAW hues above (18.89 clear) for exactly that reason. Card
     device and chart device are different measurements of the same hue --
     GEX_CONTRACT.md section 6 says the same thing about the existing red
     ramp. (In LIGHT theme the stops happen to clear the canvas; the ban
     stands on the dark reading, and a rule that bites in one theme is
     still a rule.)

     ONE CONSEQUENCE FOR THE CARD, not for this file: the ladder keeps the
     pos/neg ramp above for its net-only fallback row, so both ramps appear
     on one card, and in LIGHT theme three cross-ramp pairs fall under the
     floor (call-hi vs neg-hi 12.90, put-hi vs pos-hi 11.41, put-hi vs
     neg-hi 13.14). Accepted ONLY because colour is never the carrier there
     -- the fallback row is told apart by GEOMETRY (one bar from an edge vs
     two from the centre). PLAN_S43.md section 8d. Do not "fix" it with a
     fourth hue. */
  --ch-gex-call-lo:  color-mix(in oklch, var(--ch-gex-wall-call) 70%, var(--ra-text-primary));
  --ch-gex-call-mid: color-mix(in oklch, var(--ch-gex-wall-call) 55%, var(--ra-text-primary));
  --ch-gex-call-hi:  color-mix(in oklch, var(--ch-gex-wall-call) 40%, var(--ra-text-primary));
  --ch-gex-put-lo:   color-mix(in oklch, var(--ch-gex-wall-put) 70%, var(--ra-text-primary));
  --ch-gex-put-mid:  color-mix(in oklch, var(--ch-gex-wall-put) 55%, var(--ra-text-primary));
  --ch-gex-put-hi:   color-mix(in oklch, var(--ch-gex-wall-put) 40%, var(--ra-text-primary));
  --ch-gex-dash: 6 3;      /* reserved for delayed-source overlays ONLY —
                              no gex reader since S41 (levels went solid) */

  /* GAMMA ENVIRONMENT BAND (S40 task #9). The band down the chart edge that
     says whether the day is positive- or negative-gamma. Measured, never
     picked: every reading below is re-runnable via
     `./venv/bin/python design/s40/ramp_contrast.py` section [5], and the hue
     search behind the choice is in design/s40/env_hue_search.out.

     WHY THESE TWO HUES, and the finding that decided it. The search measures
     every 5-degree hue at every lightness and chroma the sRGB gamut allows,
     in both themes, against the 38 tokens ALREADY DRAWN on this canvas
     (candles, volume, the EMA set, VWAP, psych levels, Bollinger, spread,
     extended-hours shade, liquidity heatmap, volume profile, markers, orders,
     grid, axis, crosshair). Three results:
       1. BLUE IS UNAVAILABLE ON THIS CANVAS IN DARK THEME. Not one value
          between hue 215 and 290 clears the palette's 14.93 CIEDE2000 floor
          at ANY lightness or chroma -- the corridor is fully occupied by the
          psych-level family (235), --ch-ema200 (260), --ch-order-buy/--ch-macd
          and the heatmap's delta-up. So the CARD ramp's gex.positive (Accent/
          Blue) cannot follow the band onto the chart. Same shape as the
          recorded gex.negative trap, at the other end of the wheel: the
          card's evidence does not transfer to the canvas.
       2. RED and GREEN/TEAL are excluded on MEANING, not distance. They do
          produce values that clear the floor -- a dark, low-chroma dusty red
          is numerically far from --ch-candle-down. They are still wrong.
          Gamma is a REGIME, not a direction: negative gamma means dealers
          AMPLIFY moves, it does not mean bearish. A band in the down-candle
          family would tell the trader something false in the channel he reads
          before he reads the words.
       3. What survives both is hue ~110 (olive) and hue ~320 (violet): the
          two families with no drawn token on this canvas and no direction
          attached to them, 210 degrees apart so they cannot be confused with
          each other either. Positive is the olive, negative the violet --
          neither is a "good/bad" colour, which is the point.

     HOW THE RENDERER USES ONE TOKEN TWICE, and where each floor is met:
       RAIL  -- the token at FULL opacity, the band's edge. This is where WCAG
                1.4.11's 3:1 non-text floor is satisfied: a region with a
                discernible boundary is a discernible region. Measured against
                the real chart background in each theme: 5.91 / 3.69 dark,
                4.37 / 6.82 light. Nearest drawn neighbour anywhere: 18.89
                (negative vs --ch-ema100 in dark), floor 14.93.
       FIELD -- the same token at alpha 0.12 over the chart background, the
                band body behind the tape. Recommended, not mandated: the
                geometry is the renderer's call, the colour is not.

     THE TRADE-OFF, STATED RATHER THAN SPLIT. A band is a large field, not a
     line. A field flat at 3:1 would compete with live candles; a field quiet
     enough not to compete cannot itself carry 3:1. So the field is quiet by
     design -- at alpha 0.12 it reads 7.0 to 12.9 CIEDE2000 from the
     background, deliberately BELOW the 14.93 mark floor so the band body can
     never be mistaken for something drawn -- and the RAIL carries 1.4.11.
     Both halves are measured; neither is a compromise between them.

     GREYSCALE, STATED PLAINLY AND NOT ROUNDED UP: the two bands are only
     1.6:1 apart in greyscale, and no lightness pair would reach 3:1 -- a
     value must clear 3:1 against a near-black background AND a near-white one
     at once, which pins it to roughly 18 L* of room. That is not a defect,
     because colour is not the carrier (A-2): the band ships with the WORDS
     beside it (R2) and the words are what survive greyscale. The ~13 L* split
     is a bonus, not the mechanism. If the words are ever dropped, these
     tokens stop being sufficient and the design is wrong, not the colour.

     NOT FILED IN design/TOKENS.json, deliberately, and this is the S20 rule
     being followed rather than skipped: TOKENS.json is the record of the
     FIGMA design system, and the frame authors no chart-canvas colour of any
     kind. Every app-minted chart colour already lives here and only here as
     an oklch literal -- --ch-ema9, --ch-ema200, --ra-chart-ema100,
     --ch-psych-*, --ch-eth-shade. Written in oklch (not hex) for that same
     reason: build_tokens.py's legacy scraper collects --ch-* HEX only, and
     that group is a ratchet that must shrink to zero. Nothing is hand-edited
     into TOKENS.json here, so there is nothing for a generator re-run to
     revert -- which is the failure S20 recorded. */
  --ch-gex-env-pos: oklch(0.65 0.14 110);
  --ch-gex-env-neg: oklch(0.56 0.16 320);
}
/* The one motion token reduced motion changes: ai-agent.js times [hidden] off it, so it collapses with the
   transitions it times (moved unchanged from screener-ai-card.css, F2). */
@media (prefers-reduced-motion: reduce) { :root { --mo-dur-ai-morph: 1ms; } }

/* ------------------------------------------------------------
   UNBOUND type scale — measured, not tokenised (design/PLAN.md §3 D3, see
   the :root comment above for provenance/nodeIds). Utility classes, not
   custom properties, since they carry font-family/size/weight/line-height
   together — the same shape as every other 2.0 utility in this file.
   ------------------------------------------------------------ */
.t-u-13  { font-family: var(--font-sans); font-weight: 500; font-size: 13px; line-height: 15.73px; }
.t-u-11m { font-family: var(--font-sans); font-weight: 500; font-size: 11px; line-height: 13.31px; }
.t-u-12  { font-family: var(--font-sans); font-weight: 400; font-size: 12px; line-height: 14.52px; }
.t-u-12m { font-family: var(--font-sans); font-weight: 500; font-size: 12px; line-height: 14.52px; }
.t-u-11  { font-family: var(--font-sans); font-weight: 400; font-size: 11px; line-height: 13.31px; }

/* ============================================================
   1i. The Intelligence panel's look, tokenised -- task D9 of
   design/behavior/PLAN_FULL_ATOMIC.md (second pass, 2026-10-06). Its 15
   organism sheets (ds/organisms/intelligence-*.css) and its panel scripts
   (panels/intelligence/) held about 460 type and spacing literals, 6 motion
   literals and about 300 colour literals. EVERY VALUE IS UNCHANGED: only its
   home moved here, so the panel renders exactly as before (S20-R2, no pixel
   moves; checked against the base tree on the harness).
   ============================================================ */
:root {
  /* Text metrics one property at a time: the panel reads task E1's
     section 1h (--ra-fs-*, --ra-lh-24, --ra-ls-*; same names and values,
     the copy this section carried on its branch was dropped at the merge).
     Two tracking values are this panel's alone. */
  --ra-ls-em-15: 0.15em; --ra-ls-em-18: 0.18em;

  /* The panel's own durations, named where they already ship (each value
     unchanged, so nothing moves faster or slower). */
  --mo-dur-tip:       120ms;  /* the kit tooltip's fade in and out (.ui-tip, ds/organisms/tooltip.js); the
                                 script hides it PV2.bt.timing.tooltipHide after the fade starts */
  --mo-dur-bar:       200ms;  /* the ticker percentile bar filling to its rank (.tk-pctile-fill) */
  --mo-dur-chev-turn: 150ms;  /* a collapsible briefing panel's chevron turning (.bio2-chev) */
  --mo-dur-live-dot:  1.6s;   /* one beat of the TODAY aside's RTH session dot (tdPulse) */

  /* The parts of a colour the scripts compute per row: an industry's or a
     therapy area's hue (rail.js, today.js, bio.js) or a heat cell's chroma
     and alpha (rail.js) is data, so only the fixed lightness, chroma and
     alpha around it are tokens. */
  --ra-intel-dot-l: 0.72;     --ra-intel-dot-c: 0.14;      /* today.js: the per-industry dot */
  --ra-intel-ta-l: 0.72;                                   /* bio.js: a therapy area's dot */
  --ra-intel-ta-lo-l: 0.45;   --ra-intel-ta-lo-a: 0.6;     /* bio.js: a therapy area's bar, its start */
  --ra-intel-ta-hi-l: 0.7;                                 /* and its end */
  --ra-intel-fda-bg-l: 0.3;   --ra-intel-fda-bg-c: 0.07;  --ra-intel-fda-bg-a: 0.5;   /* bio.js: an FDA category chip */
  --ra-intel-fda-ink-l: 0.84; --ra-intel-fda-ink-c: 0.12;
  --ra-intel-heat-l: 0.55;                                 /* rail.js: a lit heat cell */
}

/* The panel's colours, VALUE-NAMED (section 1e's rule: the frame never named
   them, so the value is the name): --ra-intel-<hue>-<L>-<C>[-a<alpha>], with
   <hue> one of pos, neg, info, warn (the --hue-* variables), bg (--bg-hue),
   hNN (a fixed hue) or rgb-R-G-B; <L> and <C> in hundredths (thousandths when
   that is what the value carries), bgc for --bg-chroma.
   WHY THE SECOND SELECTOR: `.oil2` (ds/organisms/intelligence-oil.css)
   re-declares --hue-pos, --hue-neg, --hue-warn and --hue-info for its own
   subtree, and a custom property resolves the var() inside it where it is
   declared. Declared on :root alone, every hue-relative colour below would
   take the root's hues inside .oil2, where the literals it replaced took the
   subtree's. Declared on .oil2 too, it resolves there, so each one computes
   exactly what its literal computed. --hue-warn exists ONLY inside .oil2: on
   :root the warn colours are invalid, as their literals were outside it. */
:root, .oil2 {
  --ra-intel-bg-15-005-a80: oklch(0.15 0.005 var(--bg-hue) / 0.8);
  --ra-intel-bg-16-005: oklch(0.16 0.005 var(--bg-hue));
  --ra-intel-bg-16-005-a50: oklch(0.16 0.005 var(--bg-hue) / 0.5);
  --ra-intel-bg-18-005: oklch(0.18 0.005 var(--bg-hue));
  --ra-intel-bg-18-bgc-a95: oklch(0.18 var(--bg-chroma) var(--bg-hue) / 0.95);
  --ra-intel-bg-20-bgc-a35: oklch(0.20 var(--bg-chroma) var(--bg-hue) / 0.35);
  --ra-intel-bg-20-bgc-a50: oklch(0.20 var(--bg-chroma) var(--bg-hue) / 0.5);
  --ra-intel-bg-20-bgc-a95: oklch(0.20 var(--bg-chroma) var(--bg-hue) / 0.95);
  --ra-intel-bg-24-bgc: oklch(0.24 var(--bg-chroma) var(--bg-hue));
  --ra-intel-bg-25-005-a30: oklch(0.25 0.005 var(--bg-hue) / 0.3);
  --ra-intel-bg-25-005-a40: oklch(0.25 0.005 var(--bg-hue) / 0.4);
  --ra-intel-bg-28-bgc: oklch(0.28 var(--bg-chroma) var(--bg-hue));
  --ra-intel-bg-30-01: oklch(0.3 0.01 var(--bg-hue));
  --ra-intel-bg-35-bgc-a50: oklch(0.35 var(--bg-chroma) var(--bg-hue) / 0.5);
  --ra-intel-bg-40-01-a50: oklch(0.4 0.01 var(--bg-hue) / 0.5);
  --ra-intel-bg-45-02: oklch(0.45 0.02 var(--bg-hue));
  --ra-intel-bg-55-02: oklch(0.55 0.02 var(--bg-hue));
  --ra-intel-bg-55-04: oklch(0.55 0.04 var(--bg-hue));
  --ra-intel-bg-55-05: oklch(0.55 0.05 var(--bg-hue));
  --ra-intel-bg-85-005-a70: oklch(0.85 0.005 var(--bg-hue) / 0.7);
  --ra-intel-bg-92-005: oklch(0.92 0.005 var(--bg-hue));
  --ra-intel-bg-95-01: oklch(0.95 0.01 var(--bg-hue));
  --ra-intel-bg-95-005: oklch(0.95 0.005 var(--bg-hue));
  --ra-intel-bg-97-005: oklch(0.97 0.005 var(--bg-hue));
  --ra-intel-bg-98-003: oklch(0.98 0.003 var(--bg-hue));
  --ra-intel-bg-99-005: oklch(0.99 0.005 var(--bg-hue));
  --ra-intel-h25-45-13-a32: oklch(0.45 0.13 25 / 0.32);
  --ra-intel-h25-45-16-a18: oklch(0.45 0.16 25 / 0.18);
  --ra-intel-h25-45-16-a20: oklch(0.45 0.16 25 / 0.2);
  --ra-intel-h25-45-16-a35: oklch(0.45 0.16 25 / 0.35);
  --ra-intel-h25-45-16-a55: oklch(0.45 0.16 25 / 0.55);
  --ra-intel-h25-68-18: oklch(0.68 0.18 25);
  --ra-intel-h30-32-10-a45: oklch(0.32 0.1 30 / 0.45);
  --ra-intel-h30-40-12-a30: oklch(0.4 0.12 30 / 0.3);
  --ra-intel-h30-45-10-a12: oklch(0.45 0.1 30 / 0.12);
  --ra-intel-h30-60-14-a40: oklch(0.6 0.14 30 / 0.4);
  --ra-intel-h30-68-18: oklch(0.68 0.18 30);
  --ra-intel-h30-70-18: oklch(0.7 0.18 30);
  --ra-intel-h30-72-15: oklch(0.72 0.15 30);
  --ra-intel-h30-78-16: oklch(0.78 0.16 30);
  --ra-intel-h30-82-16: oklch(0.82 0.16 30);
  --ra-intel-h30-84-15: oklch(0.84 0.15 30);
  --ra-intel-h30-85-16: oklch(0.85 0.16 30);
  --ra-intel-h40-45-14-a25: oklch(0.45 0.14 40 / 0.25);
  --ra-intel-h40-78-14: oklch(0.78 0.14 40);
  --ra-intel-h40-78-16: oklch(0.78 0.16 40);
  --ra-intel-h50-70-12: oklch(0.7 0.12 50);
  --ra-intel-h55-73-17: oklch(0.73 0.17 55);
  --ra-intel-h60-40-11-a32: oklch(0.4 0.11 60 / 0.32);
  --ra-intel-h60-70-16: oklch(0.7 0.16 60);
  --ra-intel-h60-72-14: oklch(0.72 0.14 60);
  --ra-intel-h60-78-16: oklch(0.78 0.16 60);
  --ra-intel-h60-82-16: oklch(0.82 0.16 60);
  --ra-intel-h60-85-13: oklch(0.85 0.13 60);
  --ra-intel-h70-28-08-a30: oklch(0.28 0.08 70 / 0.30);
  --ra-intel-h70-30-08-a40: oklch(0.30 0.08 70 / 0.40);
  --ra-intel-h70-30-08-a50: oklch(0.30 0.08 70 / 0.5);
  --ra-intel-h70-30-10-a40: oklch(0.30 0.10 70 / 0.40);
  --ra-intel-h70-45-10-a55: oklch(0.45 0.10 70 / 0.55);
  --ra-intel-h70-48-12-a55: oklch(0.48 0.12 70 / 0.55);
  --ra-intel-h70-55-10: oklch(0.55 0.10 70);
  --ra-intel-h70-55-14-a15: oklch(0.55 0.14 70 / 0.15);
  --ra-intel-h70-55-14-a40: oklch(0.55 0.14 70 / 0.4);
  --ra-intel-h70-55-14-a45: oklch(0.55 0.14 70 / 0.45);
  --ra-intel-h70-65-14: oklch(0.65 0.14 70);
  --ra-intel-h70-68-16: oklch(0.68 0.16 70);
  --ra-intel-h70-68-16-a55: oklch(0.68 0.16 70 / 0.55);
  --ra-intel-h70-78-13: oklch(0.78 0.13 70);
  --ra-intel-h70-78-14: oklch(0.78 0.14 70);
  --ra-intel-h70-78-16: oklch(0.78 0.16 70);
  --ra-intel-h70-80-14: oklch(0.80 0.14 70);
  --ra-intel-h70-82-13: oklch(0.82 0.13 70);
  --ra-intel-h70-82-16: oklch(0.82 0.16 70);
  --ra-intel-h70-86-14: oklch(0.86 0.14 70);
  --ra-intel-h75-18-012: oklch(0.18 0.012 75);
  --ra-intel-h75-84-14: oklch(0.84 0.14 75);
  --ra-intel-h75-97-012: oklch(0.97 0.012 75);
  --ra-intel-h80-45-12-a25: oklch(0.45 0.12 80 / 0.25);
  --ra-intel-h80-78-14: oklch(0.78 0.14 80);
  --ra-intel-h80-82-13: oklch(0.82 0.13 80);
  --ra-intel-h90-70-13: oklch(0.7 0.13 90);
  --ra-intel-h90-75-14: oklch(0.75 0.14 90);
  --ra-intel-h90-82-13: oklch(0.82 0.13 90);
  --ra-intel-h145-68-15: oklch(0.68 0.15 145);
  --ra-intel-h145-70-16: oklch(0.7 0.16 145);
  --ra-intel-h155-45-13-a18: oklch(0.45 0.13 155 / 0.18);
  --ra-intel-h155-45-13-a20: oklch(0.45 0.13 155 / 0.2);
  --ra-intel-h155-45-13-a35: oklch(0.45 0.13 155 / 0.35);
  --ra-intel-h155-45-13-a55: oklch(0.45 0.13 155 / 0.55);
  --ra-intel-h155-70-14: oklch(0.7 0.14 155);
  --ra-intel-h165-28-10-a30: oklch(0.28 0.10 165 / 0.30);
  --ra-intel-h165-48-13-a55: oklch(0.48 0.13 165 / 0.55);
  --ra-intel-h165-62-16: oklch(0.62 0.16 165);
  --ra-intel-h165-78-16: oklch(0.78 0.16 165);
  --ra-intel-h165-80-15: oklch(0.80 0.15 165);
  --ra-intel-h170-45-14-a25: oklch(0.45 0.14 170 / 0.25);
  --ra-intel-h170-78-12: oklch(0.78 0.12 170);
  --ra-intel-h195-80-13: oklch(0.80 0.13 195);
  --ra-intel-h200-40-10-a32: oklch(0.4 0.1 200 / 0.32);
  --ra-intel-h200-58-10: oklch(0.58 0.10 200);
  --ra-intel-h200-82-13: oklch(0.82 0.13 200);
  --ra-intel-h230-65-14: oklch(0.65 0.14 230);
  --ra-intel-h230-70-16: oklch(0.7 0.16 230);
  --ra-intel-h230-70-16-a18: oklch(0.7 0.16 230 / 0.18);
  --ra-intel-h230-78-14: oklch(0.78 0.14 230);
  --ra-intel-h235-68-18: oklch(0.68 0.18 235);
  --ra-intel-h240-32-04-a40: oklch(0.32 0.04 240 / 0.4);
  --ra-intel-h240-65-12: oklch(0.65 0.12 240);
  --ra-intel-h240-70-14: oklch(0.7 0.14 240);
  --ra-intel-h240-74-13: oklch(0.74 0.13 240);
  --ra-intel-h250-28-01-a50: oklch(0.28 0.01 250 / 0.5);
  --ra-intel-h250-30-01-a40: oklch(0.3 0.01 250 / 0.4);
  --ra-intel-h250-30-01-a50: oklch(0.3 0.01 250 / 0.5);
  --ra-intel-h250-32-01: oklch(0.32 0.01 250);
  --ra-intel-h250-32-01-a35: oklch(0.32 0.01 250 / 0.35);
  --ra-intel-h250-34-01: oklch(0.34 0.01 250);
  --ra-intel-h250-45-12-a18: oklch(0.45 0.12 250 / 0.18);
  --ra-intel-h250-45-12-a25: oklch(0.45 0.12 250 / 0.25);
  --ra-intel-h250-45-12-a30: oklch(0.45 0.12 250 / 0.3);
  --ra-intel-h250-45-12-a50: oklch(0.45 0.12 250 / 0.5);
  --ra-intel-h250-65-13: oklch(0.65 0.13 250);
  --ra-intel-h250-78-12: oklch(0.78 0.12 250);
  --ra-intel-h250-78-14: oklch(0.78 0.14 250);
  --ra-intel-h290-62-14: oklch(0.62 0.14 290);
  --ra-intel-h290-65-18: oklch(0.65 0.18 290);
  --ra-intel-h290-78-12: oklch(0.78 0.12 290);
  --ra-intel-info230-60-12: oklch(0.6 0.12 var(--hue-info, 230));
  --ra-intel-info-20-025-a40: oklch(0.2 0.025 var(--hue-info) / 0.4);
  --ra-intel-info-24-04-a60: oklch(0.24 0.04 var(--hue-info) / 0.6);
  --ra-intel-info-30-05-a40: oklch(0.30 0.05 var(--hue-info) / 0.4);
  --ra-intel-info-30-07-a40: oklch(0.30 0.07 var(--hue-info) / 0.40);
  --ra-intel-info-30-08-a50: oklch(0.30 0.08 var(--hue-info) / 0.5);
  --ra-intel-info-32-06-a70: oklch(0.32 0.06 var(--hue-info) / 0.7);
  --ra-intel-info-32-08-a55: oklch(0.32 0.08 var(--hue-info) / 0.55);
  --ra-intel-info-40-04-a08: oklch(0.4 0.04 var(--hue-info) / 0.08);
  --ra-intel-info-45-10-a60: oklch(0.45 0.10 var(--hue-info) / 0.6);
  --ra-intel-info-55-12-a40: oklch(0.55 0.12 var(--hue-info) / 0.4);
  --ra-intel-info-55-12-a45: oklch(0.55 0.12 var(--hue-info) / 0.45);
  --ra-intel-info-55-12-a50: oklch(0.55 0.12 var(--hue-info) / 0.5);
  --ra-intel-info-55-14: oklch(0.55 0.14 var(--hue-info));
  --ra-intel-info-55-14-a40: oklch(0.55 0.14 var(--hue-info) / 0.4);
  --ra-intel-info-55-14-a50: oklch(0.55 0.14 var(--hue-info) / 0.5);
  --ra-intel-info-55-14-a55: oklch(0.55 0.14 var(--hue-info) / 0.55);
  --ra-intel-info-55-14-a65: oklch(0.55 0.14 var(--hue-info) / 0.65);
  --ra-intel-info-60-14-a40: oklch(0.6 0.14 var(--hue-info) / 0.4);
  --ra-intel-info-60-14-a60: oklch(0.6 0.14 var(--hue-info) / 0.6);
  --ra-intel-info-62-14: oklch(0.62 0.14 var(--hue-info));
  --ra-intel-info-62-16: oklch(0.62 0.16 var(--hue-info));
  --ra-intel-info-62-16-a40: oklch(0.62 0.16 var(--hue-info) / 0.4);
  --ra-intel-info-65-13: oklch(0.65 0.13 var(--hue-info));
  --ra-intel-info-65-13-a55: oklch(0.65 0.13 var(--hue-info) / 0.55);
  --ra-intel-info-65-14: oklch(0.65 0.14 var(--hue-info));
  --ra-intel-info-70-13: oklch(0.70 0.13 var(--hue-info));
  --ra-intel-info-70-14: oklch(0.7 0.14 var(--hue-info));
  --ra-intel-info-72-12-a70: oklch(0.72 0.12 var(--hue-info) / 0.7);
  --ra-intel-info-74-13-a25: oklch(0.74 0.13 var(--hue-info) / 0.25);
  --ra-intel-info-74-13-a50: oklch(0.74 0.13 var(--hue-info) / 0.5);
  --ra-intel-info-74-15: oklch(0.74 0.15 var(--hue-info));
  --ra-intel-info-78-13: oklch(0.78 0.13 var(--hue-info));
  --ra-intel-info-78-14: oklch(0.78 0.14 var(--hue-info));
  --ra-intel-info-80-12: oklch(0.80 0.12 var(--hue-info));
  --ra-intel-info-82-11: oklch(0.82 0.11 var(--hue-info));
  --ra-intel-info-86-10: oklch(0.86 0.10 var(--hue-info));
  --ra-intel-neg-25-10-a45: oklch(0.25 0.10 var(--hue-neg) / 0.45);
  --ra-intel-neg-28-08-a50: oklch(0.28 0.08 var(--hue-neg) / 0.5);
  --ra-intel-neg-28-10-a30: oklch(0.28 0.10 var(--hue-neg) / 0.30);
  --ra-intel-neg-30-06-a40: oklch(0.3 0.06 var(--hue-neg) / 0.4);
  --ra-intel-neg-30-10-a40: oklch(0.30 0.10 var(--hue-neg) / 0.40);
  --ra-intel-neg-32-06-a40: oklch(0.32 0.06 var(--hue-neg) / 0.4);
  --ra-intel-neg-40-10-a55: oklch(0.40 0.10 var(--hue-neg) / 0.55);
  --ra-intel-neg-45-13-a70: oklch(0.45 0.13 var(--hue-neg) / 0.70);
  --ra-intel-neg-45-15-a40: oklch(0.45 0.15 var(--hue-neg) / 0.4);
  --ra-intel-neg-48-14-a55: oklch(0.48 0.14 var(--hue-neg) / 0.55);
  --ra-intel-neg-55-14-a50: oklch(0.55 0.14 var(--hue-neg) / 0.5);
  --ra-intel-neg-62-16: oklch(0.62 0.16 var(--hue-neg));
  --ra-intel-neg-62-18: oklch(0.62 0.18 var(--hue-neg));
  --ra-intel-neg-62-18-a55: oklch(0.62 0.18 var(--hue-neg) / 0.55);
  --ra-intel-neg-70-16: oklch(0.7 0.16 var(--hue-neg));
  --ra-intel-neg-74-14: oklch(0.74 0.14 var(--hue-neg));
  --ra-intel-neg-74-16: oklch(0.74 0.16 var(--hue-neg));
  --ra-intel-neg-74-17: oklch(0.74 0.17 var(--hue-neg));
  --ra-intel-neg-74-18: oklch(0.74 0.18 var(--hue-neg));
  --ra-intel-neg-75-14: oklch(0.75 0.14 var(--hue-neg));
  --ra-intel-neg-78-15: oklch(0.78 0.15 var(--hue-neg));
  --ra-intel-neg-78-16: oklch(0.78 0.16 var(--hue-neg));
  --ra-intel-neg-80-14: oklch(0.80 0.14 var(--hue-neg));
  --ra-intel-pos-22-05-a30: oklch(0.22 0.05 var(--hue-pos) / 0.3);
  --ra-intel-pos-30-06-a40: oklch(0.3 0.06 var(--hue-pos) / 0.4);
  --ra-intel-pos-32-06-a40: oklch(0.32 0.06 var(--hue-pos) / 0.4);
  --ra-intel-pos-50-13: oklch(0.5 0.13 var(--hue-pos));
  --ra-intel-pos-55-14-a50: oklch(0.55 0.14 var(--hue-pos) / 0.5);
  --ra-intel-pos-70-14: oklch(0.7 0.14 var(--hue-pos));
  --ra-intel-pos-74-14: oklch(0.74 0.14 var(--hue-pos));
  --ra-intel-pos-74-16: oklch(0.74 0.16 var(--hue-pos));
  --ra-intel-pos-78-14: oklch(0.78 0.14 var(--hue-pos));
  --ra-intel-pos-78-15: oklch(0.78 0.15 var(--hue-pos));
  --ra-intel-pos-78-16: oklch(0.78 0.16 var(--hue-pos));
  --ra-intel-pos-80-15: oklch(0.8 0.15 var(--hue-pos));
  --ra-intel-rgb-0-0-0-a50: rgba(0, 0, 0, 0.5);
  --ra-intel-rgb-255-255-255-a02: rgba(255, 255, 255, 0.02);
  --ra-intel-rgb-255-255-255-a03: rgba(255, 255, 255, 0.03);
  --ra-intel-warn-70-14: oklch(0.7 0.14 var(--hue-warn));
  --ra-intel-warn-74-13: oklch(0.74 0.13 var(--hue-warn));
  --ra-intel-warn-74-14: oklch(0.74 0.14 var(--hue-warn));
  --ra-intel-warn-78-14: oklch(0.78 0.14 var(--hue-warn));
  --ra-intel-warn-82-13: oklch(0.82 0.13 var(--hue-warn));
  --ra-intel-inset-deep-a20: oklch(from var(--ra-surface-inset-deep) l c h / 0.2);   /* the peers table's even-row stripe */
}

/* ============================================================
   1j. The Composer and Watchlists panels' colours, and the Stats,
   Composer and Watchlists text metrics -- task D15 of
   design/behavior/PLAN_FULL_ATOMIC.md (2026-10-06). Their organism sheets
   (ds/organisms/composer-*.css, watchlists-*.css, stats-*.css) held 43
   colour, 54 type and spacing and 2 motion literals. EVERY VALUE IS
   UNCHANGED: a colour moved here under its value's name, a size or a
   spacing went onto the existing token of the same value (sections 1d, 1e,
   1h), so the panels render exactly as before (S20-R2, no pixel moves).
   VALUE-NAMED (section 1e's rule: the frame never named them, so the value
   is the name): --ra-<cmp|wl>-<hue>-<L>-<C>[-a<alpha>], <hue> one of info,
   pos, neg (the --hue-* variables), bg (--bg-hue) or hNN (a fixed hue); <L>
   and <C> in hundredths (thousandths when the value carries them), bgc for
   --bg-chroma. Declared on :root, where --hue-* and --bg-* are declared
   and where the Customization section stamps them, so each resolves to what
   its literal resolved to. No subtree of either panel redeclares a hue.
   ============================================================ */
:root {
  /* a disclosure caret turning: a watchlist sublist's caret (.wl-sec-head .cv), shipped .12s */
  --mo-dur-caret-turn: 120ms;

  /* Composer (ds/organisms/composer-*.css; composer-light.css's three are the light theme's chip and value
     grounds, used under [data-theme="light"] there) */
  --ra-cmp-bg-12-bgc: oklch(0.12 var(--bg-chroma) var(--bg-hue));
  --ra-cmp-bg-12-bgc-a70: oklch(0.12 var(--bg-chroma) var(--bg-hue) / 0.7);
  --ra-cmp-bg-14-bgc: oklch(0.14 var(--bg-chroma) var(--bg-hue));
  --ra-cmp-bg-14-bgc-a50: oklch(0.14 var(--bg-chroma) var(--bg-hue) / 0.5);
  --ra-cmp-bg-14-bgc-a70: oklch(0.14 var(--bg-chroma) var(--bg-hue) / 0.7);
  --ra-cmp-bg-15-bgc-a50: oklch(0.15 var(--bg-chroma) var(--bg-hue) / 0.5);
  --ra-cmp-bg-16-bgc-a50: oklch(0.16 var(--bg-chroma) var(--bg-hue) / 0.5);
  --ra-cmp-bg-16-bgc-a60: oklch(0.16 var(--bg-chroma) var(--bg-hue) / 0.6);
  --ra-cmp-bg-17-bgc-a50: oklch(0.17 var(--bg-chroma) var(--bg-hue) / 0.5);
  --ra-cmp-bg-22-bgc-a30: oklch(0.22 var(--bg-chroma) var(--bg-hue) / 0.3);
  --ra-cmp-bg-23-bgc-a50: oklch(0.23 var(--bg-chroma) var(--bg-hue) / 0.5);
  --ra-cmp-h0-100-0: oklch(1 0 0);
  --ra-cmp-h70-50-04-a30: oklch(0.5 0.04 70 / 0.3);
  --ra-cmp-h70-97-006: oklch(0.97 0.006 70);
  --ra-cmp-h75-98-006: oklch(0.98 0.006 75);
  --ra-cmp-h85-60-12-a18: oklch(0.6 0.12 85 / 0.18);
  --ra-cmp-info-17-012-a40: oklch(0.17 0.012 var(--hue-info) / 0.40);
  --ra-cmp-info-18-018-a45: oklch(0.18 0.018 var(--hue-info) / 0.45);
  --ra-cmp-info-40-06-a35: oklch(0.40 0.06 var(--hue-info) / 0.35);
  --ra-cmp-info-50-08-a40: oklch(0.50 0.08 var(--hue-info) / 0.40);
  --ra-cmp-info-55-12-a18: oklch(0.55 0.12 var(--hue-info) / 0.18);
  --ra-cmp-info-55-12-a20: oklch(0.55 0.12 var(--hue-info) / 0.2);
  --ra-cmp-info-55-14-a18: oklch(0.55 0.14 var(--hue-info) / 0.18);
  --ra-cmp-info-55-14-a25: oklch(0.55 0.14 var(--hue-info) / 0.25);
  --ra-cmp-info-55-14-a40: oklch(0.55 0.14 var(--hue-info) / 0.4);
  --ra-cmp-info-55-14-a45: oklch(0.55 0.14 var(--hue-info) / 0.45);
  --ra-cmp-info-55-14-a55: oklch(0.55 0.14 var(--hue-info) / 0.55);
  --ra-cmp-neg-55-14-a20: oklch(0.55 0.14 var(--hue-neg) / 0.2);
  --ra-cmp-pos-55-12-a20: oklch(0.55 0.12 var(--hue-pos) / 0.2);

  /* Watchlists (ds/organisms/watchlists-*.css) */
  --ra-wl-bg-13-bgc: oklch(0.13 var(--bg-chroma) var(--bg-hue));
  --ra-wl-bg-17-bgc: oklch(0.17 var(--bg-chroma) var(--bg-hue));
  --ra-wl-bg-18-bgc: oklch(0.18 var(--bg-chroma) var(--bg-hue));
  --ra-wl-bg-20-bgc: oklch(0.20 var(--bg-chroma) var(--bg-hue));
  --ra-wl-bg-21-bgc: oklch(0.21 var(--bg-chroma) var(--bg-hue));
  --ra-wl-h0-0-0-a50: oklch(0 0 0 / .5);
  --ra-wl-info-55-10-a14: oklch(0.55 0.10 var(--hue-info,250) / .14);
  --ra-wl-info-60-12-a70: oklch(0.6 0.12 var(--hue-info,250) / .7);
  --ra-wl-info-72-14: oklch(0.72 0.14 var(--hue-info,250));
  --ra-wl-info-78-14: oklch(0.78 0.14 var(--hue-info,250));
}

/* Chart-token light theme. FROZEN verbatim per design/PLAN.md §4.6: the 2.0
   section has not one light frame (dark-only), so this block is carried
   over unchanged rather than migrated or deleted — deleting it would
   remove shipped, user-selectable functionality (the theme toggle), and
   inventing a light 2.0 palette would be exactly the "fill the gap by eye"
   design/DESIGN_SPEC.md §5 forbids. Escalated to the team-lead for
   ratification per design/PLAN.md §10.8. Never remapped, in S4 or later. */
[data-theme="light"] {
  /* S19 §2.1: no light-theme frame exists for any composition node, so
     light cannot be frame-derived -- --bg-0 is exactly what these fields
     already render today in light, so light is byte-identical after this
     token lands and only dark moves to the frame's #1A1A1A. A repoint,
     never an invention -- the precedent S18 set twice for this same file. */
  --ra-surface-field: var(--bg-0);
  --ra-text-hover: var(--text-1);
  /* Figma round 2026-10-09 (fig-automations review): --ra-accent-primary-soft had no light value, so its mint
     #7EE0CD read 1.43:1 on --surf-hi (the counter badge's digits, the topbar's on icon, the chart sidebar's on
     button, the stats values). Light repoints it at the accent's deep step, 5% darker for AA: 4.84:1 on
     --surf-hi, 5.28:1 on --bg-1. Fills that carry --ra-surface-tape (--bg-2) on it read 4.43:1 (were 1.31:1); the one
     fill with --ra-text-on-accent on it (the harness chat's allow-once) drops from 13.28:1 to 3.93:1. */
  --ra-accent-primary-soft: color-mix(in oklch, var(--ra-accent-deep), black 5%);
  /* seal/checkbox has no light frame: the unchecked box's greys repoint at the
     light ramp (fill --bg-2 flips on its own); the blues and the white mark hold. */
  --ra-control-checkbox-border: var(--text-2);
  --ra-control-checkbox-hover-bg: color-mix(in oklch, var(--bg-2), var(--bg-3));
  --ra-control-checkbox-hover-border: var(--text-2);
  --ra-control-checkbox-pressed-bg: var(--bg-3);
  --ra-control-checkbox-disabled-border: var(--line-strong);
  /* seal/button Neutral has no light frame either: stroke and label repoint at the light ramp. */
  --ra-control-button-border: var(--line-strong);
  --ra-control-button-ink: var(--text-1);
  /* S46 task #35 — the four news-screen light derivations, measured live
     (design/s46/qa/rg_colors_s46.js), not modelled (HANDOFF_S46.md §3.1
     records two modelled OKLCH predictions failing, one of them the
     team-lead's own). border.hairline/border.control have NO entry here —
     their dark hex already passes 15.6:1/14.4:1 carried unchanged into
     light (:root comment above them explains why a dark line needs no
     light-specific derivation the way a dark FILL does), and text.inactive
     likewise carries unchanged (5.23:1, passes; its failure is dark-theme
     only, see its :root comment — not this block's problem to solve).
     --ra-surface-chip-count: one tier darker than the light page, same
     derivation --ra-surface-inset already uses (var(--bg-2)) — a light chip
     that's actually recessed, not the dark hex's near-black blob on white
     that measures a technical pass task #38 explicitly warned isn't a real
     one (NEWS_CONTRAST_S46.md §7.2).
     --ra-signal-bull/bear/neutral: dark hex carried unchanged fails in
     light (task #38 §7.3: 1.71 / 2.75 / 2.42 against floor) — each darkened
     by binary search (color-mix in oklab) until its measured floor clears
     with margin: bull/bear against a modelled 14%-tint impact-bar
     background (this file's own .pill.pos/.pill.neg tint convention, 3:1,
     landed at 3.20-3.22 — #24 has not built the real component yet, so
     re-verify once it exists); neutral against BOTH its floors at once —
     4.5:1 as the duplicate-chip's text (the stricter of its two roles,
     satisfies the impact bar's 3:1 for free) — landed 4.53 on
     --ra-surface-chip-count's own new light value above and 4.35 on its own
     impact-bar tint. Raw numbers: design/s46/RED_GREEN_COLOR_S46.md. */
  --ra-surface-chip-count: var(--bg-2);
  --ra-signal-bull: #218F56;
  --ra-signal-bear: #D94C54;
  --ra-signal-neutral: #616B75;
  /* S19 §2.2: the light ink, derived not chosen -- see the :root comment
     above (10.05:1 on white). */
  --ra-caret-select: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M2 4 L5 7 L8 4' stroke='%2347413C' stroke-width='1.2' fill='none' stroke-linecap='round'/></svg>");
  /* F9 (QA_VERDICT_S22.md): --ra-text-bright had NO light value anywhere, so
     index.html's footer message-rate digit (inline style, `color:var(
     --ra-text-bright)`) stayed #E6EDF3 (near-white) over the light topbar
     pill (measured live: oklch(0.97 0.006 75) background) -- contrast
     ~1.05:1, the digit effectively invisible (qa/parity_light_theme.png).
     No light-theme frame exists for this node either, so same "derived not
     chosen" move as the caret ink two lines up: reuse #47413C, the SAME
     resolved light value already measured at 10.05:1 on white for
     --ra-text-secondary (S19 §2.2's own derivation) -- "bright" is the
     highest-prominence text tier in both themes, and in light mode that
     means the darkest ink, not a second arbitrary colour. */
  --ra-text-bright: #47413C;
  /* R10 (design/UX-REVIEW-recorder.md, owner ruling S24-R5 CORRECTION, task
     #21): --ra-surface-inset had NO light value anywhere, so every surface
     painted with it stayed the dark #1C2024 under the light theme while the
     ink around it went dark too. Measured through the renderer (a 1x1 canvas
     fill + getImageData — never by parsing the colour string, AGENTS.md
     S23-Q1a), light theme, BEFORE: recording-row title 1.06:1, row meta
     2.98:1, the recorder's permission banner 1.63:1 — three consumers, all
     three unreadable, not the one the review found.
     A REPOINT, never an invention — the move S19 §2.1 and F9 above both made
     for the same shape of gap (no light frame exists to arbitrate): --bg-2 is
     already this theme's card surface (it is what `.snd-pop .snd-card` paints
     and what S19-R7's owner-approved rule colours were measured against).
     Chosen by measuring all four plausible candidates on the real elements,
     not by eye: --bg-1 (white) makes every ratio higher but the row vanishes
     into the white panel (1.0:1 against it — no card left); --bg-3 and
     --ra-surface-card resolve to the same oklch(0.88 0.01 75) and leave the
     row meta at 3.83:1, still failing. --bg-2 is the only candidate where all
     three consumers clear 4.5:1 (14.44 / 4.59 / 8.39) AND the row still reads
     as an inset against the panel (1.2:1). Readings: design/s24/arch/
     r8r10_before.json and r8r10_after.json.
     CENSUS — CORRECTED. The first version of this comment claimed "exactly
     TWO rules, both in the Recorder tab, so nothing outside it can regress".
     That was WRONG: it came from a grep of *.css only, and the team-lead
     caught the third consumer. The token is also read from JS, in an inline
     style — panels/intelligence/rail.js:51, the intel peers
     heat-map's UNLIT cell (its own comment at :44 names the token). A
     CSS-only grep is not a census in this repo; S20 §F already recorded that
     ~400 colour literals live in JS. The command that produces the real list
     is `grep -rn -- "--ra-surface-inset\b" platform/frontend` across ALL
     file types. Three consumers:
       1. panels/recorder/player.css:41   — recording row
       2. panels/recorder/recorder.css:51 — permission/unsupported banner
       3. panels/intelligence/rail.js:51 — heat-map unlit cell
     The third one was then measured on the live Intelligence tab (240 cells,
     14 of them unlit), and it IMPROVED — in the direction its own CD-426
     comment intends ("the UNLIT cell is an opaque NEUTRAL"). Light theme,
     unlit cell against the strip it sits in: 11.43:1 BEFORE (the no-data cell
     was the loudest mark in the strip, 3.3x louder than a lit one at 3.5:1)
     -> 1.20:1 AFTER, with lit cells unchanged at 3.5:1. Dark theme: identical
     before and after (1.04:1). Pictures: design/s24/arch/r10_intel_*_strip_*.png.
     SEPARATE, NOT FIXED HERE: --ra-surface-inset-DEEP (line 44) is a different
     token and it has NO light value either. Measured the same way on
     `.data-table thead th` (components.css:1192, used by Intelligence and
     Stats): 4.45:1 in dark, 3.57:1 in LIGHT — below the 4.5:1 minimum. It
     needs its own census over every .data-table consumer, so it is filed, not
     folded into this task. */
  --ra-surface-inset: var(--bg-2);
  /* S19 §17 / OWNER RULING S19-R7 ("Yes, derive darker versions."): the 8
     per-sound rule colours have no light value anywhere -- 4 of 8 sit below
     1.9:1 on the light rule-card surface. No light-theme frame exists for
     any composition node, so the frame cannot arbitrate; the owner accepted
     deriving readable variants of HIS eight colours, same hues, shown to him
     before landing (design/s19/sbs/s19r7_rule_colors_dark_vs_light.png).
     Derivation (design/s19/dev_a/rule_colors_light.js, colour math reused
     verbatim from design/s13/qa/qa_s13_a6e_verify.js, self-tested 8/8
     against Sharma et al. published CIEDE2000 reference pairs before this
     script runs anything else): each dark value walked toward L=0 at fixed
     hue until contrast >= 4.5:1 against the real paint surface (the
     background `[data-theme="light"] .snd-pop .snd-card` sets via
     var(--bg-2), screener.css:1903 -- oklch(0.94 0.008 75) = #EDEAE6). Independent
     per-colour darkening alone made TWO pairs (mint/green, tangerine/peach)
     converge well below the dark palette's own closest pair (4.36 dE2000,
     mint/green) -- both pairs' dark-palette separation is mostly CHROMA, and
     the higher-chroma member of each pair loses chroma to sRGB gamut
     clipping at the L needed for 4.5:1, drifting onto its partner. Fixed by
     a joint coordinate-ascent hue offset per colour (same +-10deg
     "recognisably preserved" budget the S13-A6e precedent used), maximising
     the GLOBAL minimum pairwise dE2000 across all 28 pairs rather than just
     the single worst pair (which only relocates the problem). Result: light
     palette's closest pair is 9.55 dE2000 (tangerine/peach) -- BETTER than
     dark's own 4.36 floor, not just "no worse". All 8 hue shifts <=10deg,
     all 8 contrasts 4.50-4.55:1 (measured against the real surface, not a
     rounder floor). Full measurements: design/s19/dev_a/
     rule_colors_light_report.json. */
  --ra-rule-orange: #975C01;
  --ra-rule-violet: #8B4EB9;
  --ra-rule-sky: #046EB0;
  --ra-rule-mint: #0B794E;
  --ra-rule-green: #067B11;
  --ra-rule-tangerine: #C03A1A;
  --ra-rule-peach: #A8521A;
  --ra-rule-steel: #546B8C;
  --ra-chart-overlay-icon: #1F8FBF;   /* S17-R31: ours, measured 3.66:1 on white (frame is silent) */
  --ch-ax: #666; --ch-text: #111; --ch-label-bg: #d8d8d8; --ch-sep: #e2e2e2;
  --ch-draw: #1a1a1a; --ch-draw-label: #1a1a1a; --ch-draw-label-fg: #fff;
  --ch-tip-bg: rgba(255,255,255,0.92); --ch-crosshair: #aaa;
  --ch-tip-border: rgba(0,0,0,0.15);
  --ch-sub-line: #d8dade; --ch-scrim: rgba(240,240,244,0.62); --ch-scrim-fg: #333;
  --ch-marker-buy: #1b9e5a; --ch-marker-sell: #d23b3b;
  /* Not part of the original frozen carry-over set above (§4.6) — these 3
     are newly tokenised out of features/chart/render.js's old `_lt ? a : b`
     ternary (never a CSS var before), so both variants are declared fresh
     here rather than "frozen", following the same dark/light split every
     other --ch-* token already uses. */
  --ch-hover-fill: rgba(0,0,0,0.05);
  --ch-hover-crosshair: rgba(0,0,0,0.55);
  --ch-hover-preview: rgba(0,0,0,0.45);
  --ch-eth-shade: oklch(0.85 0.01 60 / 0.35);

  /* Gamma environment band, light half (S40 task #9). Same hue per role as
     the dark block -- only lightness and chroma move, the discipline the
     --ra-rule-* light variants used in S19 -- so the band keeps its identity
     across the theme switch.
     THE LIGHT SURFACE IS NOT --ra-chart-bg, and measuring against that token
     would have measured a surface nobody sees: [data-theme="light"] never
     overrides --ra-chart-bg, it overrides --ra-surface-panel to --surf-hi
     (widgets.css) and the canvas is transparent over the panel. So these two
     are measured on oklch(0.97 0.006 75), the surface actually painted.
     Rail contrast 4.37 positive / 6.82 negative; nearest drawn neighbour
     23.93 and 24.42 against a 14.93 floor. Full readings, both themes:
     design/s40/ramp_contrast.out section [5]. */
  --ch-gex-env-pos: oklch(0.55 0.12 110);
  --ch-gex-env-neg: oklch(0.47 0.155 320);
  /* S41 gamma flip, light half — same hue as the dark value, only L/C move
     (the S19 discipline above). #086B6C: min dE00 20.21 to every drawn
     neighbour, 6.31:1 on the painted light surface — the pick search is
     design/s41/gexline_hue_pick.py, .out committed. The wall tokens need no
     light entry: they alias --ch-gex-env-* and follow these two overrides. */
  --ch-gex-flip: oklch(0.48 0.08 195);

  /* F-13 / S16-R1 (OWNER RULING: "fix the whole piece") — R4, THE TOKEN LINE.
     --ra-surface-panel had NO light value anywhere, so [data-theme="light"]
     .panel set its background DIRECTLY to white while every surface built
     FROM the token stayed dark on it: 9 selectors across 4 of the 5 parity
     routes, including .rail — the 81px global left navigation, dark on a
     white page, app-wide.
     WHY THIS VALUE AND NOT A NEW ONE: --surf-hi is an EXISTING light-theme
     token (css/widgets.css:999, oklch(0.97 0.006 75)) — nothing is minted
     here. It is also the exact value dev-b's throwaway-override prototype
     was measured under, so this ships the value that was measured rather
     than one that merely computes the same today.
     ORDER IS THE INTENT: the dependent text/value/badge colours (R1-R3)
     landed FIRST, in 49cad708 / 47b4ad03 / ac733429, because flipping this
     line alone was measured to trade one visible defect for two new ones
     (.st-metric values and .intel-today headings, found BY SCREENSHOT).
     The gate for this commit was dev-b's falsifiable 9/9-clean sweep at
     design/s16/dev_b/f13_sweep.9of9.json — not a conversational handoff.
     NOT INCLUDED, deliberately: --ra-surface-panel-alt has the identical
     gap. R3 measured the Intelligence badges to be #intel-today .badge on
     --ra-surface-card, NOT -alt, so -alt's only consumer app-wide remains
     #intel-today .intel-empty — an EMPTY STATE a page-load sweep does not
     reach. Flipping it here would ship an unmeasured change under cover of
     a measured one. */
  --ra-surface-panel: var(--surf-hi);
}

/* ============================================================
   4. The light theme's legacy palette and repoints -- moved VERBATIM from ds/themes/light.css by E1
   (2026-10-05, design/behavior/PLAN_FULL_ATOMIC.md): that file was on migrationDebt for these literals, and
   this is the one file allowed to hold them. Same selector, and still the LAST [data-theme="light"]
   declaration of every name below (light.css loaded after tokens.css, and this block sits after the
   chart-token light block above), so every computed value is unchanged. Comments below that say "this
   file" or "below" were written in ds/themes/light.css and refer to it; its rules now consume the
   --ra-lt-* tokens declared at the end of this block.
   ============================================================ */
/* design/PLAN.md §8.1 #15 (P5): --surf/--pos-dim/--neg-dim/--info-dim
   deleted from this block — 0 consumers for each anywhere in
   platform/frontend (dark-theme counterparts deleted the same commit,
   tokens/tokens.css §2). A light-theme value for a variable nobody reads is
   dead code a future reader would otherwise assume is live. Not a remap
   — no surviving light-theme value changed, D10's "frozen verbatim"
   still holds for everything this block still renders. */
[data-theme="light"] {
  --hue-info: 235;
  --bg-0: oklch(0.955 0.008 75);
  --bg-1: oklch(1 0 0);
  --bg-2: oklch(0.94 0.008 75);
  --bg-3: oklch(0.88 0.01 75);
  --surf-hi: oklch(0.97 0.006 75);
  --line: oklch(0.45 0.01 75 / 0.18);
  --line-strong: oklch(0.35 0.012 75 / 0.3);
  --text-1: oklch(0.22 0.01 70);
  --text-2: oklch(0.38 0.012 70);
  /* S33 #53 (owner ruling, brief SESSION 33 item 2 -- "fix the light theme
     too", same rigour as S32 #42's dark pass): both raised BY DARKENING
     (light theme puts dark text on light grounds, so more contrast = lower
     L, the mirror of dark theme's "raise = lighten"). Canvas-normalized
     binary search (S16-B1 -- oklch() must be resolved via a canvas
     fillStyle/getImageData round-trip, never regex-matched) against the
     worst REAL composited ground found live across all 6 baseline panels
     (design/s33/dev_a/instrument/light_grey_probe.js + design-qa's own
     banked run-20260827T-grey-light), not a synthetic --bg-0/--bg-1 pair:
     --text-3 was 3.83:1 on rgb(219,215,208) (news "ACCESS Newswire" et al
     buttons); --text-4 was 2.82:1 on rgb(238,234,229)
     (screener's timeframe chip / currency code). Chose the DARKEST real
     ground either token actually sits on, same discipline as #42.
     --text-3: 0.52 -> 0.48 clears 4.60:1 on its worst ground (was 3.83).
     --text-4: 0.64 -> 0.52 clears 4.60:1 on its worst ground (was 2.82).
     --ra-text-dim aliases var(--text-2) two lines up (widgets.css light
     block, F-13-as-a-class repoint) so its own 4.24 near-miss (named in
     this same ruling) closes for free -- no second edit.
     HIERARCHY HELD: new order by L is text1 .22 < text2 .38 < text3 .48 <
     text4 .52 -- both still fainter than --text-2, still ordered the same
     as before (text3 darker/more-prominent than text4), gap to --text-2
     compresses from .14/.26 to .10/.14 but does not invert or collide.
     Dark theme untouched -- these two names exist ONLY in this
     [data-theme="light"] block; tokens.css's :root values are a SEPARATE
     declaration this edit never touches, re-verified byte-identical after
     (design/s33/dev_a/T53_dark_after_readings.json).
     FILED, NOT FIXED (measured, this same pass): three sites style text
     with these two token names against a FIXED dark background that does
     NOT flip with theme (screener.css:424 `.scr-act table.data th`,
     watchlists.css:59 `.wl-sec-head`, widgets.css:936 `.sig-chip-count`, since removed) --
     the identical "F-13 as a class" cascade defect this file's own history
     already names repeatedly, one tier removed: these three read the
     LIGHT-theme value of a token whose actual background never left dark
     theme. All three were ALREADY below 3:1 before this edit (3.57 / 3.48
     / 3.25) and get WORSE by darkening further (3.01 / 2.93 / 1.99) as an
     unavoidable side effect of raising the shared token for its correct
     (theme-following) consumers. The real fix is a repoint, not a token
     value -- these three selectors want the DARK-theme reading of
     --ra-text-tertiary/--ra-text-faint regardless of the active theme,
     which is a code change to three files across two other devs' panels,
     out of this task's scope (raise the palette, not decouple a cascade).
     Numbers: design/s33/dev_a/T53_fixed_dark_surfaces.md. */
  --text-3: oklch(0.48 0.012 70);
  --text-4: oklch(0.52 0.01 70);
  --pos: oklch(0.5 0.14 155);
  --pos-bg: oklch(0.88 0.08 155 / 0.45);
  --neg: oklch(0.5 0.19 25);
  --neg-bg: oklch(0.88 0.08 25 / 0.45);
  --info: oklch(0.46 0.17 235);
  --info-bg: oklch(0.9 0.05 235 / 0.55);
  --warn: oklch(0.58 0.15 70);
  /* --ra-link-ring (S15, W1/target-marker colour): pure white (its own
     :root value, tokens.css) against a white .panel background (:1015,
     below) is contrast 1.00 -- the ring does not render in light theme
     at its :root value at all, measured live rather than assumed from
     the token's own definition. Reused var(--ra-surface-black) (pure
     black, tokens.css) rather than mint a new token or write a literal
     here (widgets.css is on migrationDebt, the ratchet blocks new colour
     literals -- avoiding the hex spelling in this comment on purpose,
     same trap F4's own comment already names: token_lint's HEX_RE flags
     it even inside a comment describing an already-allowed value) --
     pure black is also the MEASURED CEILING at the marker's fixed 40%
     opacity against this white
     background (~2.7-2.84, not the dark-theme reading's 3.80 -- no
     darker tone exists to close that gap at this opacity, it is a
     property of the alpha value, not the colour). components.css names
     the full comparison (--ra-text-primary doesn't flip; the legacy
     --text-1 above flips but renders lighter than this ceiling). */
  --ra-link-ring: var(--ra-surface-black);
  /* F-13 AS A CLASS, DEPENDENCY CLOSURE (S16-R14, revised): the text-token
     cascade below (--ra-text-primary/-secondary/-tertiary) is UNSHIPPABLE
     standalone -- measured live (design/s16/dev_b/cascade_contrast.json),
     10 selectors across 5 routes went to ~1.0-1.9 contrast (effectively
     invisible) because their text goes dark under the cascade while their
     OWN background sits on a DIFFERENT unflipped --ra-surface-* token.
     THE TIERING IS RECURSIVE: R1-R3-before-R4 was this same pattern at the
     text-on-panel tier (dependent text colours had to flip before the
     surface they sit on); this is the identical pattern one tier deeper,
     text-on-tape/-card/-bubble/-header. A "safe one-line cascade" earns
     that description only after its own dependents are checked -- it is
     not a property of using an existing alias, which was the reasoning
     that missed this the first time.
     Preserves the dark-theme RELATIVE ORDER onto the light aliases rather
     than picking values (same reasoning as --bg-3 on the Intelligence
     selected row, S16-R14): dark lightness order is base < panel < tape <
     card < card-elevated; --ra-surface-panel already flips to --surf-hi
     (R4), so tape/card land one and two steps further from it,
     respectively -- --bg-2 (a half-step off --surf-hi) for tape,
     --bg-3 (a full step) for card. --ra-surface-bubble-user (#24282E,
     a chat "user" bubble) sits within one hex step of --ra-surface-card
     itself (#23282C) -- takes --bg-3 to match, not a separate guess.
     --ra-surface-header (#0D1114, darker than panel's own #0D1319 in
     dark theme) takes --bg-2, the same half-step below --surf-hi tape
     got, since header sits between panel and tape in the dark ladder.
     No new token minted, all four already exist above in this same
     block. */
  --ra-surface-tape: var(--bg-2);
  --ra-surface-card: var(--bg-3);
  --ra-surface-bubble-user: var(--bg-3);
  --ra-surface-header: var(--bg-2);
  /* --ra-surface-panel-alt -- DEFERRAL REVERSED (S16-R1 deferred this as a
     NEUTRAL change nobody had measured; the trio below measures it WORSE:
     its one consumer, #intel-today .intel-empty, was readable
     white-on-dark before the trio and goes invisible dark-on-dark after
     it -- the deferral's premise (leaving it alone changes nothing) no
     longer holds, so the deferral does not survive it, S16-R10's class
     applied to a ruling rather than a code path). --surf-hi, matching
     --ra-surface-panel's OWN light value exactly, not --bg-2/-3: panel
     and panel-alt sit within 2 hex units of each other in dark theme
     (#0D1319 vs #0D1117) -- closer to each other than either is to tape
     or card, so panel-alt tracks panel's own flip rather than getting a
     visually distinct one the dark values never had either. */
  --ra-surface-panel-alt: var(--surf-hi);
  /* F-13 AS A CLASS (S16-R14, the cascade trio) -- this file's own
     comment above already named the mechanism in passing while picking
     the ring colour ("--ra-text-primary doesn't flip") and nobody closed
     it. --ra-text-primary/-secondary/-tertiary are read DIRECTLY (bypassing
     --text-1/-2/-3) by 25-28 sites each across the app (measured,
     design/s16/dev_b/f13_class_census.json) -- every default value colour,
     panel title, table header, placeholder etc that doesn't route through
     the legacy alias. A TOKEN-LEVEL repoint here, not a per-site fix:
     these three ALREADY have an existing, already-verified light value one
     line above (the --text-N family) -- pointing the --ra- name at its own
     alias closes every direct consumer in one line each, the same shape
     R4 used for --ra-surface-panel. Lands AFTER the five surface repoints
     above, in the SAME commit -- the dependency, not a coincidence of
     file position. LIGHT-SCOPED ONLY (this block, not tokens.css's :root)
     -- the dark-theme value is untouched, --ra- and --text-N stay two
     names for one value exactly as they are today. No new token minted. */
  --ra-text-primary: var(--text-1);
  --ra-text-secondary: var(--text-2);
  --ra-text-tertiary: var(--text-2);
  /* F-13 AS A CLASS (S16-R14, cascade-shaped members of the top-8, ruled
     GO after triage by shape rather than site count). Four tokens,
     lower-risk than the text/surface tiers above: three are BORDER roles
     (thin lines, not fills -- a wrong pick reads as slightly-off, not
     invisible, the risk profile the lead named), the fourth is a surface
     one hex-step up from --ra-surface-card.
     --ra-border-default (#1A222B) / --ra-border-card (#23282C, identical
     to --ra-surface-card's own dark value): both take --line, the
     existing default light-theme border token every other border in this
     file already uses (.btn/.input/.segmented/.ch-sb button etc) --
     --ra-border-card's own largest consumer, .panel, ALREADY carries its
     own direct light border-color override (widgets.css:1033, close to
     --line's own value, L .4 vs .45) so this repoint is a no-op there and
     a real fix everywhere else (search-pill, table borders, Intelligence
     rail dividers).
     --ra-border-strong (#1A2332, identical to --ra-surface-chip's own
     dark value -- the Intelligence selected-row token from this same
     session, already mapped to --bg-3): takes --line-strong, the
     existing STRONGER light border pair to --line's default, matching
     both the name and the emphasised/hover consumers it actually has
     (the cind/cols-chip menu borders).
     --ra-surface-card-elevated (#2A2F33, one hex-step lighter than
     --ra-surface-card's own #23282C in dark theme): takes --bg-3, the
     SAME value as --ra-surface-card. No fourth light surface tier exists
     beyond --bg-2/--bg-3/--surf-hi without minting one (against this
     session's own rule) -- card and card-elevated's subtle dark-theme
     distinction collapses to one shade in light theme, an accepted
     simplification, not a defect: nothing goes invisible, two adjacent
     surfaces (mostly hover/popout chip backgrounds) simply render
     identically instead of one shade apart. Recorded so a later session
     does not "fix" this by minting a --bg-4 nobody asked for.
     No new token minted anywhere in this rule -- four repoints, all
     pointing at values already in this light-theme block. */
  --ra-border-default: var(--line);
  --ra-border-card: var(--line);
  --ra-border-strong: var(--line-strong);
  --ra-surface-card-elevated: var(--bg-3);
  /* F-13 AS A CLASS -- three more, triaged cascade-shaped:
     --ra-text-axis (#606060): CHECKED against the hypothesis that it
     rides the --ch-* alias layer for chart-canvas (--ch-ax already has a
     light value) -- it does NOT. --ch-ax aliases --ra-chart-axis, a
     DIFFERENT token that merely shares this hex by coincidence;
     --ra-text-axis's 14 sites are plain DOM colour (icon buttons, grip
     handles, modal-close, rail SVG fills) with no path through chTC().
     Real gap, uniform role (muted UI-chrome text/icon colour, dimmer
     than --ra-text-tertiary in dark theme). --text-4, the dimmest
     rung of the --text-N ramp, preserving that it's the dimmest tier
     in dark theme too.
     --ra-surface-subtle (#50565A): background AND border-color on a
     handful of drag-handle/hover/chip sites -- same dual-role shape
     --ra-surface-card-elevated had. --bg-3, same reasoning: one surface
     value serving both roles reasonably, consistent with the tier
     already established for the adjacent surface tokens.
     --ra-text-dim (#6B7A88): rail icon fills + Intelligence secondary
     text/table headers -- sits within one hex step of
     --ra-text-tertiary (#6B7276) in dark theme, near-identical role.
     --text-3, matching its closest sibling's already-verified value.
     No new token minted -- three repoints, all existing values. */
  /* S20-R13 / TH-1, 2026-08-21 -- --ra-text-axis's repoint above was NEVER
     LIVE: the comment this sits inside used to close itself early, three
     paragraphs up, on the glob naming the chart-canvas alias family (an
     asterisk immediately followed by a slash reads as a real comment
     close), so this whole block parsed as CSS prose and the malformed
     declaration recovered at the semicolon that would have set
     --ra-text-axis, discarding it. Comment now defused with a space
     inserted into that glob -- which would otherwise ACTIVATE this
     repoint and move light-theme --ra-text-axis off its current,
     accidental, dark-identical grey to the notably lighter grey
     --text-4 resolves to in that theme, moving 19 var(--ra-text-axis)
     sites across 7 files (ai-agent.css x10, components.css x4, app.css,
     shell.css, screener.css, stats.css, intelligence.css) from roughly
     6:1 down to roughly 3:1 contrast on a light background (exact
     values: design/s20/TOKEN_HYGIENE.md sec.4 -- not repeated here
     verbatim, since a hex/oklch literal in THIS comment would itself
     count against the colour ratchet). That is a real, unreviewed
     appearance change, so S20-R2/R4 require it stay OUT until someone
     judges the contrast on purpose. PRESERVED by omission: the repoint
     is deleted here rather than reactivated, so --ra-text-axis keeps
     resolving to the dark value in light theme, exactly as it already,
     accidentally, does today. Filed for a future session to activate in
     one line once judged: design/s20/TOKEN_HYGIENE.md §4 has the full
     measurement (both themes, both before/after this fix). */
  --ra-surface-subtle: var(--bg-3);
  --ra-text-dim: var(--text-2);
  /* S18 #12 (POPUPS_GRADE_S18.md §6.1/§6.2): three more tokens S17 pinned
     from the (dark-only) frame in d2f42dc6, alongside --ra-text-axis above
     -- same disease, same fix shape: a one-line repoint into THIS block,
     never a frame-derived guess (HANDOFF_S18 §A -- no light frame exists
     for these nodes at all, so an absent value stays absent, not invented).
     --ra-text-slate (#C7D1DE, .ctf-pop .cmode-opt .glyph -- the TF code
     column, 1s/5m/1h/1d): measured 1.54:1 in light, the row's only content
     going invisible. screener.css:1727's own comment records this rule's
     PRE-rename colour as "secondary" -- --text-2 is the exact rung
     --ra-text-secondary already sits on two lines up; same tier, restored.
     --ra-state-star (#598CD9, .cmode-opt .pin.on): measured 3.40:1. This
     rule read var(--info) before S17 pinned it -- --info already carries
     its OWN light redeclaration in this same block (see above) and is
     proven live one selector away (.cmode-opt.active also reads --info).
     Restoring it is not a new choice, it undoes S17 replacing a working
     theme-aware token with a dark-only one.
     --ra-surface-scrollbar-thumb (#737A85, .cmode-section -- the section
     label; also .cind-dd .hdr's identical 11/500 ls0.2 uppercase label,
     and the real scrollbar thumb): measured 4.33:1 at 11px. Nearest hex
     sibling in dark theme is --ra-text-tertiary (#6B7276), already mapped
     to --text-3 two lines up -- the same "closest sibling" reasoning this
     block already used for --ra-text-dim. */
  --ra-text-slate: var(--text-2);
  --ra-state-star: var(--info);
  --ra-surface-scrollbar-thumb: var(--text-2);
  /* S18 #14 (WIDTH_SWEEP_S18.md §8): --ra-accent-tint (#E6FBF7, a pale
     dark-theme "active chip" mint) has never had a light repoint anywhere
     -- unlike --ra-text-axis/--ra-text-slate/etc above, this isn't a
     cascade collision, it's a plain token gap, so it fixes the same way
     task #12's did: a one-line repoint, not a frame-derived guess (no
     light frame exists, HANDOFF_S18 §A). --ra-accent-tint colours
     .cpsych/.cpsyl/.ctrades/.cgrad.on (chart-head-row2 toggles,
     screener.css) at 1.11-1.33:1 in light. THE PRECEDENT, already shipped
     and proven: this exact token, on this exact "accent text on an active
     chip" problem, is ALREADY overridden two lines below --
     `.segmented button.on` (screener.css:1530/1531 set
     color:var(--ra-accent-tint) unconditionally; this block's own
     `[data-theme="light"] .segmented button.on` two lines down replaces
     it with --text-1) -- proof nobody had to invent a value here either.
     Repointing the token itself, not patching each consumer, closes
     .cpsych/.cpsyl/.ctrades/.cgrad.on together with the two already-fixed
     .segmented buttons and .cliq.on/.cind.on (same family, same token,
     untested by this readout but no longer latent). */
  --ra-accent-tint: var(--text-1);

  /* The light theme RULES' colours (ds/themes/light.css and the light half of
     ds/organisms/login-adjust.css), hoisted VERBATIM by E1 (2026-10-05): each value is the literal that
     rule painted, only its home moved. VALUE-NAMED on purpose (section 1e's rule: the exception is
     about the value): --ra-lt-h<hue>-<L>-<C>[-a<alpha %>], L and C in hundredths (L .985 = 985,
     C .005 = 005); -info = var(--hue-info); white = L 1, C 0. Declared only here, so they exist only
     in light, where their only consumers live. Consolidating them into roles is a design call, not a
     migration's. */
  --ra-lt-h155-88-08-a25:   oklch(0.88 0.08 155 / 0.25);
  --ra-lt-h235-35-16-a70:   oklch(0.35 0.16 235 / 0.7);
  --ra-lt-h235-45-17:       oklch(0.45 0.17 235);
  --ra-lt-h235-50-14-a30:   oklch(0.5 0.14 235 / 0.3);
  --ra-lt-h235-50-14-a85:   oklch(0.5 0.14 235 / 0.85);
  --ra-lt-h235-55-16:       oklch(0.55 0.16 235);
  --ra-lt-h25-55-18-a85:    oklch(0.55 0.18 25 / 0.85);
  --ra-lt-h25-88-08-a25:    oklch(0.88 0.08 25 / 0.25);
  --ra-lt-h70-58-17-a85:    oklch(0.58 0.17 70 / 0.85);
  --ra-lt-h70-90-006-a50:   oklch(0.9 0.006 70 / 0.5);
  --ra-lt-h70-94-008:       oklch(0.94 0.008 70);
  --ra-lt-h70-94-008-a70:   oklch(0.94 0.008 70 / 0.7);
  --ra-lt-h70-96-006:       oklch(0.96 0.006 70);
  --ra-lt-h70-96-008:       oklch(0.96 0.008 70);
  --ra-lt-h70-97-006:       oklch(0.97 0.006 70);
  --ra-lt-h70-98-004:       oklch(0.98 0.004 70);
  --ra-lt-h70-98-005:       oklch(0.98 0.005 70);
  --ra-lt-h75-20-02-a15:    oklch(0.2 0.02 75 / 0.15);
  --ra-lt-h75-20-02-a6:     oklch(0.2 0.02 75 / 0.06);
  --ra-lt-h75-20-02-a8:     oklch(0.2 0.02 75 / 0.08);
  --ra-lt-h75-40-015-a10:   oklch(0.4 0.015 75 / 0.1);
  --ra-lt-h75-40-015-a14:   oklch(0.4 0.015 75 / 0.14);
  --ra-lt-h75-40-015-a15:   oklch(0.4 0.015 75 / 0.15);
  --ra-lt-h75-82-01:        oklch(0.82 0.01 75);
  --ra-lt-h75-97-006:       oklch(0.97 0.006 75);
  --ra-lt-h75-985-005-a60:  oklch(0.985 0.005 75 / 0.6);
  --ra-lt-white:            oklch(1 0 0);
  --ra-lt-white-a90:        oklch(1 0 0 / 0.9);
}

/* ============================================================
   5. Kit colours -- the atoms', molecules' and login organisms' colour literals, hoisted VERBATIM by E1
   (2026-10-05, design/behavior/PLAN_FULL_ATOMIC.md): each value is the literal its rule painted, only
   its home moved, so no pixel moves (owner ruling S20-R2) and the files leave migrationDebt.
   VALUE-NAMED on purpose (section 1e's rule: the exception is about the value, and these are the
   values the 2.0 frame never named): --ra-k-<hue>-<L>-<C>[-a<alpha %>], L and C in hundredths
   (L .55 = 55, C .005 = 005); hue = info / pos / neg (var(--hue-*), so the Customization sliders and
   the light theme still move them), bg = var(--bg-hue), or h<degrees> for a fixed hue;
   --ra-k-bg-<L>[-a] is the tinted surface oklch(L var(--bg-chroma) var(--bg-hue)). Resolved on the
   root element AND on .oil2, the one subtree that sets its own --hue-pos/neg/info
   (ds/organisms/intelligence-oil.css): a literal used to read those hues where it was painted, so a kit
   atom inside an oil briefing keeps them. Folding them into role tokens is a design call for
   the token owner, not a migration's.
   ============================================================ */
:root, .oil2 {
  --ra-k-bg-12:             oklch(0.12 var(--bg-chroma) var(--bg-hue));
  --ra-k-bg-13:             oklch(0.13 var(--bg-chroma) var(--bg-hue));
  --ra-k-bg-14:             oklch(0.14 var(--bg-chroma) var(--bg-hue));
  --ra-k-bg-14-01:          oklch(0.14 0.01 var(--bg-hue));
  --ra-k-bg-14-a70:         oklch(0.14 var(--bg-chroma) var(--bg-hue) / 0.7);
  --ra-k-bg-14-a85:         oklch(0.14 var(--bg-chroma) var(--bg-hue) / 0.85);
  --ra-k-bg-16-a70:         oklch(0.16 var(--bg-chroma) var(--bg-hue) / 0.7);
  --ra-k-bg-16-a90:         oklch(0.16 var(--bg-chroma) var(--bg-hue) / 0.9);
  --ra-k-bg-18-a60:         oklch(0.18 var(--bg-chroma) var(--bg-hue) / 0.6);
  --ra-k-bg-18-a80:         oklch(0.18 var(--bg-chroma) var(--bg-hue) / 0.8);
  --ra-k-bg-20:             oklch(0.2 var(--bg-chroma) var(--bg-hue));
  --ra-k-bg-20-015:         oklch(0.2 0.015 var(--bg-hue));
  --ra-k-bg-20-a60:         oklch(0.2 var(--bg-chroma) var(--bg-hue) / 0.6);
  --ra-k-bg-23-a50:         oklch(0.23 var(--bg-chroma) var(--bg-hue) / 0.5);
  --ra-k-bg-24-02-a70:      oklch(0.24 0.02 var(--bg-hue) / 0.7);
  --ra-k-bg-30-02-a18:      oklch(0.3 0.02 var(--bg-hue) / 0.18);
  --ra-k-bg-55-05:          oklch(0.55 0.05 var(--bg-hue));
  --ra-k-black-a40:         oklch(0 0 0 / 0.4);
  --ra-k-h165-62-16:        oklch(0.62 0.16 165);
  --ra-k-h200-74-13:        oklch(0.74 0.13 200);
  --ra-k-h240-30-01:        oklch(0.3 0.01 240);
  --ra-k-h250-32-01-a60:    oklch(0.32 0.01 250 / 0.6);
  --ra-k-h260-28-04-a50:    oklch(0.28 0.04 260 / 0.5);
  --ra-k-h30-70-20:         oklch(0.7 0.2 30);
  --ra-k-h30-78-16:         oklch(0.78 0.16 30);
  --ra-k-h320-30-10-a18:    oklch(0.3 0.1 320 / 0.18);
  --ra-k-h320-78-16:        oklch(0.78 0.16 320);
  --ra-k-h60-78-14:         oklch(0.78 0.14 60);
  --ra-k-h60-78-16:         oklch(0.78 0.16 60);
  --ra-k-h70-30-10-a55:     oklch(0.3 0.1 70 / 0.55);
  --ra-k-h70-55-04:         oklch(0.55 0.04 70);
  --ra-k-h70-68-16:         oklch(0.68 0.16 70);
  --ra-k-h70-80-15:         oklch(0.8 0.15 70);
  --ra-k-h70-82-16:         oklch(0.82 0.16 70);
  --ra-k-h70-82-16-a60:     oklch(0.82 0.16 70 / 0.6);
  --ra-k-h85-55-12-a50:     oklch(0.55 0.12 85 / 0.5);
  --ra-k-info-30-06-a18:    oklch(0.3 0.06 var(--hue-info) / 0.18);
  --ra-k-info-30-10-a35:    oklch(0.3 0.1 var(--hue-info) / 0.35);
  --ra-k-info-50-20:        oklch(0.5 0.2 var(--hue-info));
  --ra-k-info-55-12-a10:    oklch(0.55 0.12 var(--hue-info) / 0.1);
  --ra-k-info-55-12-a15:    oklch(0.55 0.12 var(--hue-info) / 0.15);
  --ra-k-info-55-12-a55:    oklch(0.55 0.12 var(--hue-info) / 0.55);
  --ra-k-info-55-12-a60:    oklch(0.55 0.12 var(--hue-info) / 0.6);
  --ra-k-info-55-12-a70:    oklch(0.55 0.12 var(--hue-info) / 0.7);
  --ra-k-info-55-14-a50:    oklch(0.55 0.14 var(--hue-info) / 0.5);
  --ra-k-info-55-14-a55:    oklch(0.55 0.14 var(--hue-info) / 0.55);
  --ra-k-info-55-14-a60:    oklch(0.55 0.14 var(--hue-info) / 0.6);
  --ra-k-info-65-13:        oklch(0.65 0.13 var(--hue-info));
  --ra-k-info-66-14:        oklch(0.66 0.14 var(--hue-info));
  --ra-k-info-72-14:        oklch(0.72 0.14 var(--hue-info));
  --ra-k-info-78-14-a60:    oklch(0.78 0.14 var(--hue-info) / 0.6);
  --ra-k-info-85-14:        oklch(0.85 0.14 var(--hue-info));
  --ra-k-neg-40-12-a24:     oklch(0.4 0.12 var(--hue-neg) / 0.24);
  --ra-k-neg-50-16-a6:      oklch(0.5 0.16 var(--hue-neg) / 0.06);
  --ra-k-neg-50-22-a28:     oklch(0.5 0.22 var(--hue-neg) / 0.28);
  --ra-k-neg-55-14-a12:     oklch(0.55 0.14 var(--hue-neg) / 0.12);
  --ra-k-neg-55-14-a15:     oklch(0.55 0.14 var(--hue-neg) / 0.15);
  --ra-k-neg-55-14-a50:     oklch(0.55 0.14 var(--hue-neg) / 0.5);
  --ra-k-neg-55-14-a60:     oklch(0.55 0.14 var(--hue-neg) / 0.6);
  --ra-k-neg-55-20-a50:     oklch(0.55 0.2 var(--hue-neg) / 0.5);
  --ra-k-neg-70-18-a60:     oklch(0.7 0.18 var(--hue-neg) / 0.6);
  --ra-k-pos-40-10-a28:     oklch(0.4 0.1 var(--hue-pos) / 0.28);
  --ra-k-pos-50-14-a6:      oklch(0.5 0.14 var(--hue-pos) / 0.06);
  --ra-k-pos-50-20-a28:     oklch(0.5 0.2 var(--hue-pos) / 0.28);
  --ra-k-pos-55-12-a50:     oklch(0.55 0.12 var(--hue-pos) / 0.5);
  --ra-k-pos-55-12-a60:     oklch(0.55 0.12 var(--hue-pos) / 0.6);
  --ra-k-pos-55-18-a55:     oklch(0.55 0.18 var(--hue-pos) / 0.55);
  --ra-k-pos-55-20:         oklch(0.55 0.2 var(--hue-pos));
  --ra-k-pos-60-15:         oklch(0.6 0.15 var(--hue-pos));
  --ra-k-pos-70-14-a60:     oklch(0.7 0.14 var(--hue-pos) / 0.6);
  /* The screener organisms' colours (ds/organisms/screener-*.css), hoisted VERBATIM by D8 screener-b
     (2026-10-06) on the same value-named scheme; --ra-k-hex-<hex> is a literal written as a hex. */
  --ra-k-bg-10-a70:              oklch(0.10 var(--bg-chroma) var(--bg-hue) / 0.7);
  --ra-k-bg-20-a85:              oklch(0.20 var(--bg-chroma) var(--bg-hue) / 0.85);
  --ra-k-bg-22-a50:              oklch(0.22 var(--bg-chroma) var(--bg-hue) / 0.5);
  --ra-k-h200-30-12-a50:         oklch(0.30 0.12 200 / .5);
  --ra-k-h260-14-01-a92:         oklch(0.14 0.01 260 / 0.92);
  --ra-k-h260-18-02-a92:         oklch(0.18 0.02 260 / 0.92);
  --ra-k-h260-45-05:             oklch(0.45 0.05 260);
  --ra-k-hex-caa54b:             #caa54b;
  --ra-k-info-65-20:             oklch(0.65 0.20 var(--hue-info));
  --ra-k-neg-22-12-a96:          oklch(0.22 0.12 var(--hue-neg) / 0.96);
  --ra-k-neg-30-12-a22:          oklch(0.3 0.12 var(--hue-neg) / 0.22);
  --ra-k-neg-55-20:              oklch(0.55 0.20 var(--hue-neg));
  --ra-k-neg-55-20-a55:          oklch(0.55 0.20 var(--hue-neg) / 0.55);
  --ra-k-neg-55-22-a35:          oklch(0.55 0.22 var(--hue-neg) / 0.35);
  --ra-k-neg-60-22:              oklch(0.60 0.22 var(--hue-neg));
  --ra-k-neg-72-15:              oklch(0.72 0.15 var(--hue-neg));
  --ra-k-neg-92-18:              oklch(0.92 0.18 var(--hue-neg));
  --ra-k-neg-97-16:              oklch(0.97 0.16 var(--hue-neg));
  --ra-k-pos-100-12:             oklch(1 0.12 var(--hue-pos));
  --ra-k-pos-34-15-a60:          oklch(0.34 0.15 var(--hue-pos) / 0.6);
  --ra-k-pos-55-16-a24:          oklch(0.55 0.16 var(--hue-pos) / 0.24);
  --ra-k-pos-62-20-a85:          oklch(0.62 0.2 var(--hue-pos) / 0.85);
  --ra-k-pos-70-24-a55:          oklch(0.70 0.24 var(--hue-pos) / 0.55);
  --ra-k-pos-75-22-a70:          oklch(0.75 0.22 var(--hue-pos) / 0.7);
  --ra-k-pos-97-16:              oklch(0.97 0.16 var(--hue-pos));
  /* The muted caret two kit selects still draw (ds/atoms/input.variants.css) -- the pre-S19 ink that
     --ra-caret-select (section 1c) replaced elsewhere, hoisted verbatim with the same SVG. */
  --ra-caret-select-muted: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M2 4 L5 7 L8 4' stroke='%2388929c' stroke-width='1.2' fill='none' stroke-linecap='round'/></svg>");
  /* The brand splash, design/rally-alpha-ident.html: its own surface, absent from the 2.0 section
     (design/CHANGE_MAP.md row 4), hoisted with the same values. Written as rgb(), not hex, ON PURPOSE:
     token_lint allows every hex declared in this file, so a hex here would silently clear the same two
     literals shell/ident.js still writes (its overlay) and hide that debt from the ratchet. */
  --ra-ident-bg:            rgb(10 12 18);
  --ra-ident-bg-edge:       rgb(6 7 11);
  --ra-ident-hint-ink:      rgba(255, 255, 255, 0.28);
  --ra-xsp-ident-stage-x:   6vw;      /* the stage's side inset, a share of the viewport */
}

/* ============================================================
   6. Developer tools -- the colours of dev/ (the ?perf=1 HUD), owner decision 2026-10-06: a dev-only tool is
   not exempt from the colour rule, it gets its own tokens. App-only by nature (no frame draws a dev tool): each is
   a declared app-only entry in design/TOKENS.json (dev.hud-*, dev.hud-*-light; design/build_tokens.py
   DIVERGENCES, so a regeneration keeps them). Dark = the literals the HUD painted, unchanged (its bg was
   rgba(0,0,0,0.82); D1 = 0.82 of 255). Light = the same roles on a white ground, picked for contrast: WCAG
   ratios of every ink on its ground, composited over each theme's page grounds, are recorded in TOKENS.json's
   colorSources (lowest: dark dim 5.51, light ok 5.70). Borders are decoration, not text. Nothing outside dev/
   reads them.
   ============================================================ */
:root {
  --ra-dev-hud-bg:     #000000D1;
  --ra-dev-hud-border: #2A3340;
  --ra-dev-hud-ink:    #CCDDEE;
  --ra-dev-hud-dim:    #7B8794;
  --ra-dev-hud-head:   #99FFCC;
  --ra-dev-hud-ok:     #7DDC8A;
  --ra-dev-hud-warn:   #FFCF5D;
  --ra-dev-hud-bad:    #FF6B6B;
}
[data-theme="light"] {
  --ra-dev-hud-bg:     #FFFFFFEB;
  --ra-dev-hud-border: #B4BEC9;
  --ra-dev-hud-ink:    #1F2933;
  --ra-dev-hud-dim:    #56616C;
  --ra-dev-hud-head:   #0B6E44;
  --ra-dev-hud-ok:     #1A7431;
  --ra-dev-hud-warn:   #835400;
  --ra-dev-hud-bad:    #B3232B;
}
/* Light values of section 1k's trading tokens (Figma round 2026-10-09, fig-trading). */
[data-theme="light"] {
  --ra-tx-side-buy:      var(--pos);
  --ra-tx-side-sell:     var(--neg);
  --ra-tx-side-sell-on:  var(--neg);
  --ra-state-danger-bg:  var(--neg-bg);
  --ra-state-danger-ink: var(--neg);
}
/* == ds/base/reset.css == */
/* base.css: design tokens + body reset — split out of rally.css. */
/* ============================================================
   Rally Alpha — Design System
   Tokens + primitives. Port of Rally Alpha design tokens.css,
   adapted for vanilla DOM (no React).
   ============================================================ */

/* Design-token materialisation (colours, --ch-*, hues, bg-tint) lives in
   tokens/tokens.css (design/PLAN.md §2 "alias layer" — loaded before this
   file, see index.html). This file keeps only the non-colour primitives
   that tokens/tokens.css does not own: density/radii/spacing/type sizing. */
:root {
  /* Density + radii */
  --density: 1;
  --r-sm: 4px;
  --r: 8px;
  --r-lg: 12px;

  /* Spacing scale */
  --sp-1: calc(4px  * var(--density));
  --sp-2: calc(6px  * var(--density));
  --sp-3: calc(8px  * var(--density));
  --sp-4: calc(12px * var(--density));
  --sp-5: calc(16px * var(--density));
  --sp-6: calc(24px * var(--density));

  --row:  calc(22px * var(--density));
  --ctrl: var(--ra-size-control-sm);   /* design-sync 2026-10-03: Figma size/control/sm, was 26px */
  --head: calc(48px * var(--density));

  /* Type */
  --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;
  /* --font-mono is Inter, not a monospace stack — human ruling, session 2
     QA F2: the 2.0 design defines NO mono family at all (checked: neither
     TOKENS.json nor any of the five frames declares one), and monospace
     covered 34.8% of Trading's visible text and 21% of Stats' — the frame
     doesn't. NAME kept as --font-mono deliberately (design/PLAN.md's own
     --ch-* precedent: renaming a custom property 292 call sites read by
     name, for zero benefit, risks the exact silent-failure mode that
     convention exists to avoid — an unrecognised name just falls through
     to nothing, no error).
     Digit alignment in price/quantity columns is the load-bearing risk of
     this switch: font-variant-numeric:tabular-nums is declared at every
     site where digits stack in a column (the per-file audit this same
     commit adds it to) — that declaration is the NECESSARY condition.
     Whether Google Fonts' Inter static build (wght@400;500;600) actually
     HONOURS tabular-nums (ships a tnum OpenType table) was a measured
     question, not an assumed one, because a declared-and-computed
     tabular-nums can still be inert if the loaded font has no tnum table,
     and system fallback fonts on some machines default to tabular figures
     regardless, which can mask a real Inter-specific regression in a quick
     local check. RESOLVED, 2026-08-21: YES — measured live at every weight
     the app requests (400/500/600) and every size 9-28px, 264 elements,
     0.0px spread between mismatched digit strings under tabular-nums
     (600px spread under font-variant-numeric:normal for the same strings);
     also holds at the synthesized 700/800/900 the app does not request but
     `screener.css`'s `.scr-velo-ratio` uses. Full measurement + method:
     design/s20/TABULAR_NUMS.md §1/§4. `JetBrains+Mono` has not been in
     index.html's font request since session 2 (see the comment there) —
     this file's own switch to Inter for --font-mono is what that question
     was blocking on, and it is no longer blocked. */
  --font-mono: var(--font-sans);
}

/* ------------------------------------------------------------
   Base reset
   ------------------------------------------------------------ */
* { box-sizing: border-box; margin: 0; padding: 0; }
/* overscroll-behavior-x: without it a horizontal touchpad swipe anywhere on the
   page reaches Chrome's back/forward overscroll navigation — a full reload. */
html, body { height: 100%; overflow: hidden; overscroll-behavior-x: none; }
body {
  font-family: var(--font-sans);
  font-size: var(--ra-fs-12);
  color: var(--text-2);
  background: var(--bg-0);
  letter-spacing: var(--ra-ls-em-01);
  -webkit-font-smoothing: antialiased;
  font-feature-settings: "cv11", "ss01", "ss03";
}
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; }
a { color: var(--info); text-decoration: none; }
a:hover { color: var(--text-1); }

/* .num.num (class repeated, not a typo — specificity hack, no !important):
   .num's whole purpose is "this text is numeric, stay tabular no matter
   what else styles this element," but at plain (0,1,0) specificity it was
   silently losing ties to same-specificity, later-loaded rules that reset
   the font shorthand — `font: inherit` on form controls / status chips /
   etc. (11 such rules across owned files). Session 2 QA F11: span.num,
   input.input.num read tabular-nums fine, but span.status-chip.num,
   input.search-input.num, b.num all computed `normal` — same class,
   different luck on cascade order, no error anywhere. Doubling the class
   raises specificity to (0,2,0), which beats any plain single-class or
   class+element selector regardless of load order, without enumerating
   (and re-finding, every time a new file adds one) each colliding rule.
   ST1 (CD-618, design/PLAN.md §U1): the doubling protects font-family and
   font-variant-numeric — the two properties this comment names. Six of
   this session's letter-spacing rows (CD-368, 495, 497, 516, 535, 624)
   were shipped as correct-reading per-component overrides that this rule
   silently out-specificities regardless, because letter-spacing rode along
   on the same doubled selector without ever being the reason for it.
   Demoted to plain (0,1,0) below so a same-or-higher-specificity, later-
   loaded rule can win it, same as it always could for font-family/
   font-variant-numeric's own callers. */
.num.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.num {
  letter-spacing: var(--ra-ls-em-n01);
}

/* Scrollbar */
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: 4px; }
*::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-corner { background: transparent; }

/* == ds/organisms/panel.css == */
/* components.css: panel, button, input, pill, segmented, kbd, tables — split out of rally.css. */
/* ------------------------------------------------------------
   Panel — Design 2.0 card anatomy (DESIGN_SPEC §3): radius 14, 1px border,
   surface.panel background, a 34px panel-head @ 16,14 -> 1px hairline (in
   surface.card, not border.default) -> content. Restyle of the existing
   .panel/.panel-head/.panel-body primitives — reused everywhere, not
   recreated (PLAN.md §3.1).
   ------------------------------------------------------------ */
.panel {
  background: var(--ra-surface-panel);
  /* Border colour is NOT a card-family axis (design/DESIGN_SPEC.md §6.6:
     "Not invariant... Name it per screen") — it's the --card-border custom
     property, set by the owning column/screen and defaulting to
     border.card. design/PLAN.md §3 D2. */
  border: 1px solid var(--card-border, var(--ra-border-card));
  border-radius: var(--ra-radius-card);
  /* CD-37 (G12): horizontal padding relocates from .panel-head/.panel-body
     (each independently declaring their own 16px) onto the card itself —
     "16,14" is the card's OWN inset, not two children's. Top-only stays
     14px (unaffected — CD-37's own row is scoped to horizontal padding;
     bottom stays 0, .panel-body keeps its own vertical padding so
     scrollable bodies don't lose their end-of-content inset, which a
     card-level bottom padding would sit OUTSIDE the scroll region and
     move behind, not scroll with). */
  padding: var(--ra-sp-14) var(--ra-sp-xl) 0 var(--ra-sp-xl);
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* border.strong (right column + chart) vs border.card (left column, the
   .panel default above) — DESIGN_SPEC §3's component table. Scoped here
   since components.css doesn't know column context; screener.css owns
   #scr-col-right/.trd-cp so it isn't duplicated. */
#scr-col-right .panel, .scr-panel.trd-cp.panel { --card-border: var(--ra-border-strong); }

/* SUPERSESSION RECORD -- S17 item 1 (owner order, addendum S17-A,
   design/PLAN.md §S17.3e/§S17.14e). The group colour's CARRIER changes
   from the panel's top stripe (this comment's original subject, deleted
   below) to the link tile, [data-pchrome=link] (the rules immediately
   following this comment). Rewritten as a supersession record rather than
   deleted outright (H1: a change that moves a subject re-points every
   artefact naming it) -- read this in full before touching either half.

   === WHAT THE STRIPE WAS, KEPT FOR THE RECORD =============================
   S13-A6f / owner ruling S13-R44b: "carry the group colour on something
   with AREA" -- before it, only a single 22x22 [data-pchrome=link] icon
   carried the colour, so a user had to hunt a specific glyph per panel to
   answer a question that is about GROUPING. DECLARED DIVERGENCE BY
   CONSTRUCTION even then, not a parity fix: the design system authors the
   colour PICKER (362:1608) and never a RESULT for a grouped panel --
   searched, no such node existed. The remedy shipped was a thin inset
   `background` stripe along the panel's own top edge (not border/padding/
   outline, so it added zero layout size), 3px, six --ra-link-* tokens, no
   new literal, on the generic `.panel[data-link-group]` primitive so any
   panel that can join a group gets it without a screener-specific touch.
   It was originally `box-shadow: inset ...` and was MOVED onto a
   `::before` overlay in `cf06961c` (F-11, S15, 2026-08-18): `widgets.css`
   [data-theme="light"] .panel` also sets `box-shadow` (the light-theme
   elevation shadow), tied specificity with this rule, decided by source
   order -- widgets.css loads after components.css and won, so the stripe
   SILENTLY STOPPED RENDERING in light theme until `cf06961c` moved it to
   a property light theme never touches. SUPERSEDED HERE, NOT WASTED: that
   fix was correct for the mechanism it targeted and stays a real repair
   in git history; only the carrier it repaired is being relocated now.

   `cf06961c`'s OWN acceptance term 3 is also why today's relocation is a
   CORRECTION, not merely a preference -- independent evidence, from the
   seat that had just repaired the mechanism: pixel-sampled with PIL, the
   stripe's magenta was found COMPLETELY INVISIBLE along the top edge on a
   panel that was both grouped AND the selection target, at the marker's
   then-4px width (`ddaf601c`) -- the target ring covered the whole 3px
   band. At today's shipped 2px ring (`ff116e79`) the occlusion is
   PARTIAL, not total (the weaker, honest claim, re-measured at ship
   rather than inherited from the 4px premise -- see `A1.6`). Either
   width: the stripe failed at its own job, at the exact moment the user
   was acting on the panel, measured by the person who had just fixed it.
   That is independent evidence the top edge was the wrong carrier for
   this signal, not only a different one the owner happened to prefer.

   THE STRIPE WAS NEVER FILED IN THE REGISTER: `design/COMPONENT_DIFF.md`
   (13,404 lines, checked) returns 0 hits for "link group" / "link-group" /
   "colour group" / "color group" / "F-11", in either direction. Deleting
   it below therefore orphans no register row.

   === WHERE THE BURDEN WENT =================================================
   THE DIVERGENCE BURDEN DOES NOT DISAPPEAR -- IT MOVES TO THE BUTTON,
   and that is the half an auditor will actually need after this ships:

   THE FILLED LINK BUTTON, BELOW, IS ITSELF A DECLARED DIVERGENCE. The
   frame does not draw it. Node `341:3788` (`action-link-active`, the link
   tile on `seal/card/chart-10S` and, identically, on all nine
   link-carrying panels) renders `#23282C` ×376px and ZERO cyan pixels in
   the frame -- 22×22, radius 6, padding 8, icon `341:3789` glyph
   `#606060`. THAT is what `341:3788` will show anyone who opens it after
   this ships. What this app draws instead, unconditionally on a grouped
   panel: the panel's own group colour (one of the six --ra-link-* tokens
   below) as the fill, `--ra-link-ink` (`#1A2332`, `S17-R1`, ratified by
   the owner) as the glyph, no border.
   Established by TWO INDEPENDENT ROUTES, not assumed from one: this
   file's own S13 search (kept below, verbatim) and, a session later,
   `FRAME_S17A.md` §0.5's pixel sample of the SAME node, re-run
   independently with the same zero-cyan result, plus a targeted search of
   the node's only twin (`461:2161`), the colour picker's own header link
   button (`402:203`, itself a flattened `#23282C` neutral even INSIDE the
   picker), and the whole KIT QA page (158k chars, 0 relevant hits). Both
   routes agree on the same reading: the design system authors the colour
   PICKER (`362:1608`, selection modelled as a white ring on a swatch) and
   has NEVER modelled a RESULT for a grouped panel. There is no frame
   state this app's filled link button corresponds to; it is our own,
   owner-ordered (the owner was shown both the frame's neutral and the
   measured cyan-fill alternative, with contrast numbers for both glyph
   directions, before ordering the fill -- `S17-R1`/`S17-R2`).
   An auditor who walks `341:3788` after this ships and finds a group
   colour there, without this paragraph, will file it as an unauthorised
   addition -- correctly, by this corpus's own rules, unless the
   divergence is recorded exactly where they will look. This paragraph is
   that record.

   === THE ORIGINAL S13 SEARCH, VERBATIM (the first of the two routes
       above) ====================================================
     - Lived HERE, on the generic `.panel[data-link-group]` primitive, not
       on `.scr-panel` in screener.css: a group colour is a property of
       ANY panel that can join one, not a screener-specific look, and
       every one of the nine link-roster panels already carries `.panel`
       (`class="panel scr-panel ..."`) -- confirmed by reach measurement,
       design/s13/dev_a/S13-A6f_evidence.md, all nine paint identically.
     - Six existing --ra-link-* tokens ONLY, one rule per colour, no new
       literal -- token_lint blocking count unaffected by this change.
     - Ungrouped panels (no data-link-group attribute at all) matched none
       of the six stripe selectors -- zero change, by construction, not by
       a default/fallback branch that could regress. The link-button
       cascade below inherits this same property.
     - Survived PiP: pip.js moves the ACTUAL panel DOM node (dataset
       attributes travel with it) into a document that copies this
       stylesheet (pip.js's _copyStyles) -- the rule just re-matched.
       Did NOT survive "Pop out" ([data-pchrome=pop]): that control opens
       a brand-new tab that re-mounts the panel from persisted layout
       state, and data-link-group/-sym are pure in-memory DOM state
       (cross-window.js's own header comment, §S13.14.2a) -- never
       serialized into popout()'s state payload. A grouped panel that pops
       out loses its WHOLE group membership today (routing included, not
       just this stripe) -- a pre-existing gap in S13-A4's own mechanism
       (S13-A8, cut). STILL TRUE OF THE BUTTON BELOW, unchanged by this
       session and NOT fixed here -- carried forward by name,
       `design/PLAN.md` §S17.14f, so it is not silently inherited. */

/* S17 item 1 (owner order, addendum S17-A, design/PLAN.md §S17.3b): the
   panel's group colour ALSO lands on the link tile, [data-pchrome=link] --
   a pure descendant cascade off .panel[data-link-group], the SAME
   attribute the stripe above already keys on. The signal population is
   therefore IDENTICAL to the stripe's by construction -- no roster to
   maintain, nothing for the two to drift against.
   Landed WHILE the stripe above still stands (S17.6 steps 2/3: the new
   signal must exist before the old one is removed, not the reverse --
   removing the stripe first would leave every grouped panel signal-less
   for however long the two commits are apart; both are deliberately live
   at once for one commit).
   REFUSED: stamping data-link-group (or anything matching
   [data-link-group]) on the button. shell/cross-window.js:65's
   _groupMembers is an UNSCOPED document.querySelectorAll('[data-link-
   group="X"]') -- a button carrying that attribute becomes a group member
   and PV2.link.applyGroup starts writing data-link-sym onto it, re-
   manufacturing the leak model this cascade exists to avoid. The selector
   below never puts the attribute on a new element, so it cannot reach that
   hazard by construction, not by care.
   Specificity, CHECKED not hoped -- and checked twice, because the first
   check (against .btn.on, (0,2,0)) is not the whole story: the six rules
   below also have to beat THIS FILE'S OWN `.phd-actions > .btn.ghost`
   background/color rules above ((0,3,0) -- an exact tie with the plan's
   bare `.panel[data-link-group] [data-pchrome=link]` shape) and their
   :hover sibling ((0,4,0)). Neither tie is visible from the addendum or
   the plan, only from reading this file -- measured live with a Playwright
   probe (design/s17/dev_b/probe_step2_cascade.js) before this landed,
   because this file has already lost exactly this kind of tie once
   (widgets.css's light-theme `.btn.ghost` override beating this block's
   own neutral fill on source order, an unrelated pre-existing gap filed
   separately, not fixed here). Rather than rely on order a second time,
   every selector below adds `.btn`: (0,4,0) resting / (0,5,0) hover+active,
   unconditionally above both phd-actions rules regardless of where in the
   cascade this block sits.
   Hover/active are not modelled by the frame (S17-R3, no variants anywhere
   in the cluster) -- the grouped fill MODULATES via filter rather than
   being replaced by a neutral: `.btn:hover` sets background:var(--bg-2)
   and `.phd-actions>.btn.ghost:hover` sets
   background:var(--ra-surface-card-elevated); either one replacing the
   group colour on hover would make a grouped panel look ungrouped at the
   exact moment the user reaches for its link control. `filter` introduces
   no colour value at all, so it is token-clean by construction and needs
   no per-colour duplication. `.btn.on`'s own
   `border-color:var(--ra-accent-primary)` is neutralised the same way --
   left alone it would be a teal ring around a group-coloured fill, a third
   meaning inside one 22x22 control; neutralising it also matches the
   frame, which paints no border at all on 341:3788. */
.panel[data-link-group] [data-pchrome=link].btn{
  color: var(--ra-link-ink);
  border-color: transparent;
}
.panel[data-link-group="cyan"]    { --link-fill: var(--ra-link-cyan) }
.panel[data-link-group="magenta"] { --link-fill: var(--ra-link-magenta) }
.panel[data-link-group="blue"]    { --link-fill: var(--ra-link-blue) }
.panel[data-link-group="orange"]  { --link-fill: var(--ra-link-orange) }
.panel[data-link-group="yellow"]  { --link-fill: var(--ra-link-yellow) }
.panel[data-link-group="purple"]  { --link-fill: var(--ra-link-purple) }
/* The group fill holds on hover / press too -- those states only modulate it (filter, below). The tiles'
   own hover fill (.phd-actions > .btn.ghost:hover; light.css's .btn.ghost:hover) tied the resting rule at
   (0,4,0) and, later in the cascade, replaced the group colour: dark ink on a dark grey tile, 1.22:1. */
.panel[data-link-group] [data-pchrome=link].btn,
.panel[data-link-group] [data-pchrome=link].btn:hover,
.panel[data-link-group] [data-pchrome=link].btn:active { background: var(--link-fill) }
.panel[data-link-group] [data-pchrome=link].btn:hover  { filter: brightness(1.12); color: var(--ra-link-ink) }
.panel[data-link-group] [data-pchrome=link].btn:active { filter: brightness(0.92) }

/* ------------------------------------------------------------
   S31-TICKER-MOTION (design/s31/TICKER_MOTION_SPEC.md §3/§4.1). A panel
   re-identifies its content when the symbol it displays changes (the former
   js/core/ui.js's symApplied() toggles .sym-swap on CHANGE only, never on a data
   tick — see that function for the full contract). Existing tokens only,
   opacity only (PLAN_S31 §3.6); fade IN only, no fade out -- the old value
   is never held on screen to be faded away, the content is already
   rewritten by the time this class lands. --mo-dur-view/--mo-ease-settle
   is UX-R13 class B (orientation), the same class a panel/tab swap uses,
   so a ticker change and a tab change move alike.
   The class is TRANSIENT (spec v2 §2.2, reversing an earlier draft on
   design-qa's flag): symApplied() removes it itself via an animationend
   listener PLUS a setTimeout net (a panel in a hidden tab or a closed PiP
   never fires animationend, so the class would otherwise rest exactly
   where nobody looks -- see that function for the full contract). NO
   animation-fill-mode is set here on purpose: the resting style is
   whatever the panel's own rules already say, so there is no faded state
   for anything to "release" even if both removal mechanisms failed.
   Precedent for a .scr-panel-scoped rule living here rather than in the
   frozen screener.css: the .panel[data-link-group] block immediately
   above. */
@keyframes sym-swap-in { from { opacity: 0 } to { opacity: 1 } }
.scr-panel.sym-swap .panel-body {
  animation: sym-swap-in var(--mo-dur-view) var(--mo-ease-settle);
}
.scr-panel.sym-swap .phd-title .sub {
  animation: sym-swap-in var(--mo-dur-view) var(--mo-ease-settle);
}
/* Chart canvases never animate (UX §4.1/PLAN §3.6: no opacity/transform/
   filter on .ch or any ancestor while it draws -- measured, a blur+scale
   on a live-canvas subtree cost 22 long frames in 3s). A chart panel's
   ticker transition is its symbol LABEL only (.phd-title .sub above still
   applies); the body -- canvas and every ancestor of the canvas -- gets
   nothing. Exact list, not :has() guesswork: pane.html carries exactly
   two <canvas> elements, one in data-panel=chart, one in data-panel=tpswave. */
.scr-panel[data-panel=chart].sym-swap .panel-body,
.scr-panel[data-panel=tpswave].sym-swap .panel-body { animation: none }

/* == ds/organisms/chart-veil.css == */
/* ------------------------------------------------------------
   S32 candle entrance (design/s32/CANDLE_ENTRANCE_SPEC.md §2.3/§3, owner
   ruling S31-R10). The veil is a SIBLING of the canvas, never the canvas
   or an ancestor of it -- features/chart/entrance.js creates/removes it, this
   file only styles it. Covering the panel's OWN background (--bg-1, the
   same ground pane.html's chart wrapper sits on) rather than transparent,
   so what's underneath reads as "not painted yet", not "see-through".
   z-index 6 -- above every other in-plot overlay this file's own
   screener.css: (verified) already stacks here: .chart-zoom/.chart-legend
   (z-index 2) and .chart-loading (z-index 4, screener.js's OWN separate
   "waiting for first trade" text, gated on the SAME bars-empty condition
   this veil covers) -- so nothing shows through or double-messages during
   a load; screener.js is frozen this session and untouched, this is a
   pure CSS-layering resolution.
   NO animation-fill-mode (spec §6 amendment boundary + the same TICKER_
   MOTION_SPEC lesson this session already paid for once, the tzMenu): the
   node is REMOVED by JS (entrance.js) right after the fade, whichever of
   animationend/its own setTimeout net fires first, so there is no resting
   state for a fill-mode to hold open. */
.ch-veil {
  position: absolute; inset: 0; z-index: 6;
  background: var(--bg-1);
  pointer-events: none;
  display: flex; align-items: center; justify-content: center;
}
.ch-veil.lifting {
  animation: ch-veil-lift var(--mo-dur-view) var(--mo-ease-settle);
}
@keyframes ch-veil-lift { from { opacity: 1 } to { opacity: 0 } }
/* The slow-load line (spec §4): static text only, no spinner/shimmer/pulse
   -- motion expresses arrival, never aliveness (UX-R6), and animating a
   "still loading" message makes a stalled feed look healthy (TENETS 3). */
.ch-veil-slow {
  font: var(--ts-label-form); letter-spacing: var(--ts-label-form-ls);
  color: var(--text-2);
}

/* S33-R3 (task #60, design/s33/CHART_REVEAL_REF.md) -- the blur-to-focus
   on the chart's WRAPPER (features/chart/entrance.js's c.parentNode, the bare
   position:relative div the veil above already lives in), added alongside
   the veil rather than replacing it: the veil still covers the WAIT for
   data (unchanged), this covers the RESOLVE once data has arrived, which
   is the one ingredient the reference has and the S32 veil didn't.
   `filter` lands on an ANCESTOR of `.ch`, never on `.ch` itself -- the one
   shape PLAN_S31 §3.6 reserved its single canvas-adjacent exception for
   (S31-R10), spent here exactly as S33-R3 authorised. No transform/scale
   (CHART_REVEAL_REF §3.2/§4.4 rules both out) and no width/height/opacity
   on this class -- opacity is still the veil's own job above, kept as two
   independent transitions on two different elements rather than one
   mechanism carrying both meanings.
   .ch-focus-done is added at the SAME moment .ch-veil gets .lifting
   (entrance.js's _lift()) and both classes are stripped together when the
   veil is torn down -- steady state carries no filter at all, matching
   the veil's own "no resting state" rule two comments up.
   ANIMATION, not transition -- same choice ch-veil-lift already made two
   comments up, and for the identical reason: a filter transition on
   .ch-focus-armed would itself animate the moment the class is FIRST
   added (blur(0) -> blur(N), sharp-to-blur, backwards), because the
   transition and the new value land in the same style recalculation --
   caught live, measured with design/s33/dev_a/instrument/
   blur_focus_verify.js sampling the resolved filter every 30ms (first
   attempt: `blur(1.28px) -> blur(8px)`, ramping UP, not down). A
   keyframe's `from{}` block fixes the START value explicitly regardless
   of the element's prior computed style, so .ch-focus-armed alone sets
   the resting blurred state with NO transition property to misfire, and
   the animation only exists on the SECOND class, added later once bars
   have actually arrived. */
.ch-focus-armed {
  filter: blur(var(--mo-blur-focus));
}
.ch-focus-armed.ch-focus-done {
  animation: ch-focus-in var(--mo-dur-focus) ease-out; /* motion_lint:allow-filter */
}
@keyframes ch-focus-in { from { filter: blur(var(--mo-blur-focus)); } to { filter: blur(0); } }

/* == ds/organisms/chart-draw-pop.css == */
/* ds/organisms/chart-draw-pop.css — the edit popover of a chart drawing (features/chart/drawing-popover.js, the face of
   chartDraw 'selected'). Moved out of the script's inline <style> and cssText (chart-a, 2026-10-05); tokens only.
   Elevation shadow removed (design/PLAN.md §3 D11): the 1px border already separates it from the chart. */
.draw-pop {
  position: fixed; z-index: 10001; white-space: nowrap;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: 5px;
  padding: var(--ra-sp-2xs) var(--ra-sp-xs);
  font: var(--ts-caption-10); letter-spacing: var(--ts-caption-10-ls); color: var(--text-1);
}
.draw-pop .dp-bar { display: flex; align-items: center; gap: var(--ra-sp-5); }
.draw-pop .dp-bar > * { flex: none; }
.draw-pop .dp-cap {
  font: var(--ts-caption-tight); letter-spacing: var(--ts-label-status-upper-ls);   /* 0.4px, the uppercase tracking (was 0.04em = 0.36px) */
  color: var(--text-2); text-transform: uppercase; white-space: nowrap;
}
.draw-pop .dp-color {
  width: 22px; height: 22px; padding: 0; border: 1px solid var(--line); background: none;
  border-radius: 3px; cursor: pointer;
}
.draw-pop .dp-px, .draw-pop .dp-lbl-in {
  height: 22px; padding: 0 var(--ra-sp-5); background: var(--bg-1); border: 1px solid var(--line);
  color: var(--text-1); border-radius: 3px;
  font: var(--ts-caption-10); letter-spacing: var(--ts-caption-10-ls);
}
.draw-pop .dp-px { width: 62px; font-family: var(--font-mono, monospace); }
.draw-pop .dp-lbl-in { width: 78px; }
.draw-pop .dp-sep { width: 1px; height: 16px; background: var(--line); margin: 0 calc(var(--ra-sp-3xs) / 2); }
.draw-pop .dp-del {
  display: flex; align-items: center; justify-content: center; width: 22px; height: 22px; padding: 0;
  border: 1px solid var(--line); background: transparent; color: var(--text-2); border-radius: 3px; cursor: pointer;
}
.draw-pop .dp-del:hover { color: var(--neg); border-color: color-mix(in oklch, var(--neg) 55%, transparent); }
/* == ds/atoms/drag.css == */
/* ------------------------------------------------------------
   S14-B1 -- the drag-and-drop / row-click-target affordances (design/PLAN.md
   S14.6). Four classes lane A (dev-a) writes; this is styling only --
   names are FIXED by the plan, neither lane renames them. No new node
   exists in Figma for any of these four states (S14.3: "not drawn," to be
   derived and the decision documented, not invented against the system or
   silently omitted).

   CORRECTED (component-auditor, flagged via the lead): this block
   originally said "nothing sets [these] yet" -- true when S14-B1 landed
   (styling shipped ahead of the caller, by the plan's own migration
   order), false since dev-a's S14-A1/A4/A5 landed: `ra-target-chart` is
   live (shell/cross-window.js:157-158, `_syncTargetMarker`), and
   `ra-dragging`/`ra-drop-ok`/`ra-drop-no` are live (screener.js
   :1537-1593). Left uncorrected, the next reader finds four classes
   nothing appears to use and "cleans up" a working marker -- H1's rule
   (a change that moves a subject re-points every artefact naming it)
   applies to a comment exactly as it does to a ledger row.

   ra-target-chart -- the chart panel a Top Gainers/News Scan row click will
   write (S14-R2, owner ruling RULE-B: ALWAYS the original/main chart, bare
   `data-panel`, never a `-N` duplicate). The owner must see which chart
   that is BEFORE he clicks -- load-bearing, not decorative -- and it must
   read as itself even on a panel that is ALSO in a colour group (S14-B1
   e4), which the six --ra-link-* group tokens above already make hard to
   guarantee by hue alone: they span most of the wheel, so no seventh
   colour sits reliably "far" from all of them (measured below, not
   assumed). Distinguished on property AND geometry, not colour alone:
     - PROPERTY: WAS `outline` (see the W1 fix, below, for why it moved to
       an `::after` overlay's `box-shadow`) -- never the SAME property the
       group stripe or the drop cue use, on the SAME paint layer, for the
       same reason each of those was itself kept off a colliding property:
       one declaration replaces another's value wholesale, it does not
       merge. `box-shadow` here is safe because it lives on `.ra-target-
       chart::after`, a DIFFERENT box from `.panel[data-link-group]::before`
       (the stripe) -- two different pseudo-elements, not two rules
       fighting over one.
     - GEOMETRY: a full 4-side perimeter, where the group stripe is a 3px
       TOP-ONLY inset. A ring cannot be read as "a thicker top rule" no
       matter how close the two hues land.
     - an INSET `box-shadow` (`inset 0 0 0 <width> <colour>`) is this
       codebase's other established "paints inside, zero layout size"
       idiom (the group stripe above uses the same shape of guarantee,
       now via `background` on an overlay rather than `box-shadow`
       directly -- see W1 below for why the marker couldn't stay on
       `outline` once it needed to paint ABOVE a child's own background).
       `box-shadow` never participates in box sizing, and is not clipped
       by `.panel`'s own `overflow:hidden` (overflow clips a box's
       descendant content, never its own border/outline/box-shadow
       decorations) -- verified live, not just argued from the spec,
       design/s14/dev_b/S14-B1_e2_layout.json (this term unchanged by the
       mechanism move: an inset box-shadow on an absolutely-positioned
       overlay has the identical zero-layout-shift guarantee an outline
       on the panel itself had).
     - `--info`, not `--ra-accent-primary` (`--pos` resolves to the exact
       same hex, #0FD4BB): that token is reserved below for ra-drop-ok,
       AND is the global `:focus-visible` ring colour a few rules down --
       reusing it here would make "this is the target chart" and "this
       element currently has keyboard focus" render identically. `--info`
       already carries "the noteworthy/selected one" meaning elsewhere in
       this file (`.ui-dd-item.on`, the rule-card left-rule) -- reused,
       not invented. Measured (design/s14/dev_b/S14-B1_colors.json):
       contrast 8.07:1 against --ra-surface-panel (every target/drop
       surface in this plan sits on it); nearest link token by RGB
       distance is --ra-link-blue at 44.9 -- close in hue family, which is
       exactly why the property+geometry split above is the real
       guarantee, not the colour pick.

   WIDENED, OWNER RULING (S15, "make it wider" -- 2px judged insufficient
   against the group stripes on his own monitor, on the before/after F-10
   capture): the width itself was never measured against its own stated
   competitor until now. `.panel[data-link-group]`'s stripe (above) is a
   PERSISTENT `3px` inset box-shadow; the marker was a PERSISTENT `2px`
   outline -- the cue meaning "a click writes HERE" was thinner than the
   cue meaning "this panel is merely in a colour group", on the same
   panel, at rest. Surveyed for anything else persistent on `.panel`
   before setting a floor: the card's own border is `1px` (`:15` above,
   `--card-border`), weaker than the stripe; every other outline in this
   codebase touching a `.scr-panel` (squeeze-flash `1.5px`, `fbd-fail`
   `1px`, a moved panel's 1 s `.scr-marked` `2px`) is a TRANSIENT state, not
   persistent competition. `3px` is the floor. `4px` (floor + 1, not a
   round number picked for safety) is the smallest integer strictly
   heavier than the strongest persistent edge on the panel -- at device
   scale factor 2 that is 8 device px against the stripe's 6, a 2-device-
   px margin rather than a 1 that could still tie on some displays'
   rounding. `outline-offset` moved with it at the time (`-4px`,
   preserving `offset = -width`) -- geometry verified not to clip or
   overlap the chart head/body at the new value, both an original and a
   real duplicate, design/s15/dev_b/. `ra-drop-ok`/`ra-drop-no` (below)
   were UNCHANGED at that step -- transient, drag-only, no competition
   with a persistent stripe.

   W1 -- MOVED OFF `outline` ENTIRELY (owner ruling, S15: "surrounding the
   full panel" -- his own words for the exact defect design-qa had
   already measured pixel-level). `outline` is painted as part of
   `.panel`'s OWN box rendering, which happens BEFORE its normal-flow
   descendants paint -- so any descendant with its own opaque background
   paints OVER the outline wherever they overlap on screen. `.ch-sb`
   (screener.css, the chart's drawing-tools rail) is exactly such a
   descendant: an intentional opaque "plate" (CD-158) sitting ~1 CSS px
   from the panel's own left edge, for the toolbar's full height -- inside
   the ring's own band. Measured: the ring read 8 device px wide outside
   `.ch-sb`'s vertical range and 2 device px inside it (W1, design-qa;
   reproduced here pixel-for-pixel, design/s15/dev_b/).
     - FIX VERIFIED, NOT ASSUMED: `position:absolute` alone on an overlay
       does NOT reliably paint above a `position:static` sibling in this
       Chromium build -- tested directly against the EXISTING, ALREADY-
       SHIPPED `.ra-drop-ok::after` (below), no `z-index` set, and it
       shows the IDENTICAL occlusion (design/s15/dev_b/) -- a second,
       previously unreported instance of the same defect class, found
       because the lead's suggested fix was verified against this exact
       reproduction rather than assumed correct because it looked like
       the established idiom. An EXPLICIT `z-index` is required; `1` was
       insufficient in this build, `2` was sufficient (matching this
       file's own existing in-panel overlay convention --
       `.chart-zoom`/`.chart-legend`/`.chart-protect`, screener.css, all
       `z-index:2`) -- the drop-cue rule below now carries the same fix,
       for the same reason, verified the same way.
     - MECHANISM: the ring moved from `.panel`'s own `outline` to
       `.ra-target-chart::after` -- an absolutely-positioned, `z-index:2`
       overlay carrying an INSET `box-shadow` ring (not `outline`, so it
       never re-triggers the paint-order problem above; not `background`,
       so it does not fight the group stripe's `::before` OR the drop
       cue's own `outline` on the very same `::after` -- see the drop-cue
       comment below for how three states now share one pseudo-element
       without any of them erasing another).
     - OWNER PICKED (S15), from the captured set (design/s15/dev_b/):
       `2px` at `40%` opacity, `--ra-link-ring` -- his own stated reason,
       independently surfaced by this seat and design-qa: it is the ONLY
       variant that leaves the colour-group stripe readable underneath it
       (`V4`: `false,false,false,TRUE`) -- selection AND group membership
       at once, and genuinely quieter than `V2` (`8.20`, essentially the
       shipped blue's own `8.07`). See the rule below and its own
       light-theme note for the value and the measurement.
   ------------------------------------------------------------ */

/* ra-target-chart / ra-drop-ok / ra-drop-no -- ALL THREE now share one
   `::after` overlay's base geometry (content/position/inset/z-index), and
   ra-target-chart joined this block at W1 (S15) when its own ring moved
   off `.panel`'s `outline` -- see the long comment above `.ra-target-
   chart::after` for why. Each still paints its OWN distinguishing
   property on that shared pseudo-element -- `ra-target-chart`'s inset
   `box-shadow`, `ra-drop-ok`/`ra-drop-no`'s `outline` -- so a panel
   carrying two or three of these classes at once (the target chart is
   always a legal drop target too -- RULE-B doesn't exempt it) shows BOTH
   effects, neither erasing the other: `box-shadow` and `outline` are
   different properties, so nothing here replaces anything the way a
   second `box-shadow` rule would. `.panel[data-link-group]::before` (the
   group stripe) is a DIFFERENT pseudo-element entirely -- three
   mechanisms, three panel decorations, none sharing a property with
   another. All three states can be visible on one panel at once.
     - `z-index: 2` (W1, S15) -- REQUIRED, not decorative. `.ch-sb` (the
       chart's drawing-tools rail, screener.css) is a normal `position:
       static` descendant with its own opaque background sitting near the
       panel's left edge; verified live that a `position:absolute`
       overlay WITHOUT an explicit z-index still gets painted OVER by such
       a descendant in this Chromium build (`z-index:1` was insufficient,
       `2` was sufficient) -- this is what silently broke `ra-drop-ok`'s
       own ring in the exact same toolbar region before this fix, an
       existing defect nobody had reproduced against `.ch-sb` until now.
       `2` matches this file's own in-panel overlay convention
       (`.chart-zoom`/`.chart-legend`/`.chart-protect`, screener.css).
     - `position:relative` is already `.panel`'s own base rule (top of this
       file) for every real target (chart/singleton/Order Ticket panels are
       all `.panel`); declared again here defensively in case a class this
       plan didn't anticipate ever carries these two, since a bare
       `position:relative` with no offsets moves nothing and shifts no
       layout either way.
     - `inset:0` on an `absolute` child never participates in `.panel`'s
       own `flex` sizing (spec: absolutely-positioned children are excluded
       from flex layout) -- zero layout shift by construction, confirmed
       live both ways, not just argued (e2,
       design/s14/dev_b/S14-B1_e2_layout.json). `border-radius:inherit`
       (a valid value for ANY property, not only naturally-inherited ones)
       keeps the overlay's own ring following `.panel`'s rounded corners.
     - ra-drop-ok: `--pos` (green/teal) -- the SAME up/positive pairing
       already used platform-wide for buy/sell and gain/loss; no new
       colour language for "yes, drop here."
     - ra-drop-no: `--neg` (red) -- the REFUSAL cue the owner will see on
       an ungrouped singleton and on the Order Ticket (S14-A5 g5/g6), and
       therefore the control reached for while already confused (S14-B1
       e3's own framing). Measured, not eyeballed, at its own rendered 2px
       stroke against --ra-surface-panel: **4.96:1** -- clears the 4.5
       text floor this session holds every colour to (F4's own 3.82-at-11px
       precedent, below), not merely the 3:1 non-text floor that would
       otherwise apply to a bare outline (design/s14/dev_b/S14-B1_colors.json).
   ------------------------------------------------------------ */
.ra-target-chart, .ra-drop-ok, .ra-drop-no { position: relative; }
.ra-target-chart::after, .ra-drop-ok::after, .ra-drop-no::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  z-index: 2;
}
/* ra-target-chart's own value (owner-picked, S15, see the block comment
   above): `--ra-link-ring` at `40%`, `2px` -- NOT `--info` any more.
   `40%` is a fixed opacity, not a colour, so it stays out of token_lint's
   scope regardless of theme (same reasoning as `.ra-dragging`'s opacity).
   `--ra-link-ring` itself now carries a LIGHT-THEME OVERRIDE
   (widgets.css, beside `--info`'s own -- light-theme token values live
   there, not here; this file and tokens.css are untouched by the theme
   split).
     - WHY A THEME VALUE IS NEEDED AT ALL, MEASURED NOT ASSUMED:
       `--ra-link-ring` is pure white (tokens.css) and the light-theme
       `.panel` background is ALSO pure white (widgets.css:1015) --
       contrast `1.00`, the number for "identical colour," i.e. the ring
       does not render in light theme at the token's own value. Not a
       margin call; the ring is simply not there.
     - THE LIGHT VALUE'S CEILING, MEASURED: at a FIXED `40%` opacity (the
       owner's own choice, not touched per-theme -- a translucency that
       changes between themes would be a ring that "changes character
       with the theme", the thing being avoided), the DARKEST possible
       result against a white background is bounded by `40%` of pure
       black -- measured live, `color-mix(in oklch, black 40%,
       transparent)` over the light panel background renders **~2.7-2.84**
       (device-px sampled, not read from a token), NOT `3.80` (the dark-
       theme reading of the owner's own pick). No colour choice can close
       that gap at this fixed opacity -- it is a property of the alpha
       value against a white surface, not of which dark tone is picked.
       Landed at the ceiling rather than short of it: `~2.8`, stated
       here rather than claimed as `3.80`.
     - TOKEN CHOSEN: `--ra-surface-black` (pure black, tokens.css) -- the
       CEILING value itself already exists as a token, so no new one was
       needed and none was minted. Named rather than assumed adequate: it
       is a SURFACE token being reused for a STROKE, the same shape of
       imperfect fit `--ra-link-ring` itself was minted to avoid when
       `--ra-text-inverse` was the numerically-identical-but-semantically-
       wrong candidate for THIS token, at S13-A5. Checked and rejected
       first: `--ra-text-primary` (does not flip per theme -- stays
       near-white, contrast ~1 in light, same defect as leaving
       `--ra-link-ring` unpatched); the legacy `--text-1` family
       (widgets.css's own light block, a dark warm-grey) -- flips
       correctly but renders LIGHTER than the ceiling (measured contrast
       ~2.4, further from 3.80 than the ceiling value, and a different
       token family from `--ra-*` besides).
     - STRIPE VISIBLE IN BOTH THEMES, MEASURED, NOT CARRIED OVER FROM
       DARK: the group stripe (`::before`) is TALLER (3px) than the ring
       (2px), so a band below the ring's own footprint shows the stripe
       undiminished -- confirmed live in light theme at the stripe's own
       exact rendered value (--ra-link-magenta's own hex, tokens.css),
       pixel-sampled, not
       assumed from the dark-theme reading (the ring's own colour and the
       backdrop it composites against both differ by theme, so this was
       re-measured rather than inherited). This is WHY the owner picked
       this variant -- selection and group membership together, in both
       themes, not only the one that was captured first. */
.ra-target-chart::after { box-shadow: inset 0 0 0 2px color-mix(in oklch, var(--ra-link-ring) 40%, transparent); transition: box-shadow var(--mo-dur-ring) ease; /* motion-lint-ignore: box-shadow: the link ring: ::after is the ring itself and ::before the group stripe, no layer is free */ }
/* S22-B item 9 (design/s22/PLAN_S22B.md §9, LEAD_RULINGS.md S22-R-selection):
   auto-hide the ring after 4s (shell/cross-window.js's `_RING_HIDDEN`
   marker) by fading its OWN box-shadow to transparent, not by touching this
   selector's shared `::after` (position/inset/z-index, above) or its
   opacity. `ra-drop-ok`/`ra-drop-no` paint on that SAME pseudo-element via
   `outline` -- a panel can carry `ra-target-chart` and a drop-state class at
   once (comment above `.ra-target-chart, .ra-drop-ok, .ra-drop-no`) -- so
   suppressing the whole pseudo-element's opacity would also hide an
   in-progress drop cue on the very panel whose ring just expired. Fading
   only the box-shadow's colour leaves `outline` (a different property)
   completely unaffected either way. */
.ra-target-chart.ra-ring-hidden::after { box-shadow: inset 0 0 0 2px transparent; }
.ra-drop-ok::after { outline: 2px solid var(--pos); outline-offset: -2px; }
.ra-drop-no::after { outline: 2px solid var(--neg); outline-offset: -2px; }

/* ra-dragging -- the source row mid-drag (S14-A4, lane A: rows in `.tgp`
   and `#scr-ns-t`). Opacity only: it never touches the row's own box, so a
   table reflow mid-drag moves nothing else (e2), and it is the same cue
   convention the native drag gesture already implies on its own OS-drawn
   ghost (a translucent copy of the source). No token involved -- opacity
   is a plain fraction, out of token_lint's colour-only scope. */
.ra-dragging { opacity: 0.5; }

/* == ds/organisms/panel-variants.css == */
/* ------------------------------------------------------------
   Card variants (design/PLAN.md §3 D2 / design/DESIGN_SPEC.md §3 correction
   table): T1 measured THREE head/padding families plus one exception inside
   Base itself, not one invariant card. `.panel` above IS family A (16/14,
   head 34 — Base, AI agent, Intelligence's LINKED CARD) and needs no
   variant class. The three additions below cover the rest; each overrides
   only the panel's own top inset, the head's horizontal padding + hairline
   offset, and the body's content padding — geometry only, never colour.
   ------------------------------------------------------------ */
/* Family A′ — Base's `buy-sell-tps` only (12/12, head 34; the exception T1
   found INSIDE Base — CD-39/G12: now applied to tpswave's markup
   (screener/pane.html), and moved to the card per the same G12 pattern as
   CD-37 — this consumer's body is `.panel-body.flush` (a canvas, not
   scrolling text/table content), so unlike the family-A default there is
   no scroll-region risk in also relocating the vertical component; all
   four sides now live on the card, none on the head or body. */
.panel.card-a-tight { padding: var(--ra-sp-lg); }
/* Fitts & Miller parity (Figma panel-head Card=a-tight 824:3): the head keeps the shared 8 / 12 rhythm below. */
.panel.card-a-tight .panel-head::after { left: -1px; right: -1px; }
.panel.card-a-tight .panel-body { padding: 0; }
/* Family B (the four Stats surfaces) went with the Stats view on 2026-10-18. */
/* Family C — Trading's three activity cards (24/24, head 24). `card-pnl`'s
   15px head is an authoring inconsistency (design/DESIGN_SPEC.md §8.5), not
   a fifth family — it renders as card-c per that section's ruling.
   CD-47 (G12, plan-corrected from a mis-numbered "CD-49" dispatch — see
   `design/CHANGELOG.md`): horizontal relocated to the card, same
   scroll-content reasoning as family B (CD-46) — .panel-body genuinely
   scrolls here (overflow:auto, base rule), so a card-level bottom padding
   would sit outside the scroll region as fixed chrome instead of
   travelling with the content; bottom stays on .panel-body. Top stays on
   the card (unaffected either way, head sits above any scroll region). */
.panel.card-c { padding: var(--ra-sp-2xl) var(--ra-sp-2xl) 0 var(--ra-sp-2xl); }
.panel.card-c .panel-head { padding: 0; min-height: 24px;
  /* design-qa: card-c's head/hairline/body are a real 3-child auto-
     layout in the frame, itemSpacing 16 between ALL of them (header,
     hairline, body) — not this rule's usual 8+1+8=17 hairline rhythm.
     32 = 16 (head-to-hairline, unchanged, see ::after below) + 16
     (hairline-to-content) + the hairline's own ~0 footprint here since
     it's a pseudo-element, not a real flex sibling consuming space.
     Verified against the frame's own absolute offsets (head bottom +48,
     hairline +64, body content +80 — all reproduced by this value). */
  margin-bottom: var(--ra-sp-3xl);
}
/* CD-608/609-class (design-qa A2): head-bottom-to-hairline is 16px in the
   frame (422:5363/5379/5405, all three read 16.00), not the shared rule's
   8px — top keeps its own override. left/right are DROPPED here (CD-47):
   with panel-head's own horizontal padding now gone, its ::after
   positioning context (the padding box) is the same box the base family's
   -1px/-1px is already derived for (card content width minus the card's
   own horizontal padding) — the 23px this rule used to carry was
   panel-head's now-obsolete 24px padding compensated by 1px for the same
   border-box-vs-strokeAlign:INSIDE reason CD-100/406 documents; the base
   rule (components.css, `.panel-head::after`) supplies the correct -1px
   on its own now, nothing card-c-specific left to say about it. */
.panel.card-c .panel-head::after { top: calc(100% + 16px); }
/* padding-top 0 (was 24): the card's own top inset (24, unchanged above)
   + head (24) + margin-bottom (32, above) already lands body content at
   the frame's own +80 — an additional 24 here would double it. Left/right
   0 (CD-47: relocated to the card above). Bottom stays 24 — it scrolls
   with .panel-body's content, not fixed card chrome (see family comment
   above). */
.panel.card-c .panel-body { padding: 0 0 var(--ra-sp-2xl) 0; }

/* CD-10 (G11, session 4): gap 8px, matching `321:248`'s own itemSpacing —
   reverses an earlier 4px landing (session 2) made when tpswave's head
   still wrapped at 8px. Re-measured live (Playwright) at the current
   geometry across all five routes' panel-heads, incl. the two cards that
   triggered the original wrap (tpswave 289px "Buy/Sell TPS", gainers
   593px): none wrap at 8px any more (headH stays at min-height, no
   scrollWidth overflow) — tpswave alone has 16px of slack to spare. The
   icon-tile/title fixes landed since session 2 (Amendment 3's 22px tiles,
   G13's grip glyph) closed the gap that forced 4px. CD-12's title x-offset
   (grip 8 + gap 8 = 16 from the head's content edge) falls out of this
   same change with no separate fix — verified: 16px live, matching. The
   ≤900px stack view and narrow-column wrap allowance (screener.css) keep
   their own tighter 4px row-gap for the wrapped case specifically; this is
   the single-row default only. */
.panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ra-sp-sm);
  /* CD-37/G12: horizontal padding removed — now supplied once by .panel
     itself, not duplicated here. */
  position: relative;   /* anchors the ::after hairline below */
  /* Fitts & Miller parity (Figma panel-head 824:92 Card=a, 2026-10-18): 48 tall -- 8 above the 28 px action row, 12
     below it, the hairline on the last pixel of those 12 (::after below). min-height, not height: a head whose title
     wraps its label (or, in a very narrow column, its cluster) grows instead of clipping (screener-panel-head.css). */
  padding: var(--ra-sp-sm) 0 var(--ra-sp-lg);
  min-height: calc(var(--ra-sp-sm) + var(--ra-size-control-sm) + 2 * var(--ra-sp-3xs) + var(--ra-sp-lg));
  /* the space before the next sibling (panel-body): Figma keeps it per card (screener-nbbo.css). */
  margin-bottom: var(--ra-xsp-17);
  flex-shrink: 0;
}
/* I14 (design-qa): Intelligence's peers column card is a plain family-A
   .panel (no card-b/c variant — its own border-radius:0 override lives
   in intelligence.css) but frame `391:4744` gives it 24px horizontal,
   not family A's default 16. Measured consequence: peers title sat at
   554.19 against the frame's 561 (538+24=562). Targeted by dev-b's
   existing .intel-col-peers class, same idiom as the card-b overrides
   above — no markup change needed from dev-b. Its own hairline
   (.intel-col-peers .panel-head::after, intelligence.css) already
   overrides left/right to 0 independently, so this doesn't touch it. */
.intel-col-peers > .panel { padding-left: var(--ra-sp-2xl); padding-right: var(--ra-sp-2xl); }
/* The hairline is NOT the head's own border: a pseudo-element, so every .panel-head draws it with no markup. Since the
   Fitts & Miller parity pass (Figma panel-head 824:92, `hairline` at y 47 of 48) it is the head's last pixel; before,
   it stood 8 below a 34 head (341:3828). A card that draws it elsewhere moves its `top` (Trading's card-c). */
.panel-head::after {
  content: "";
  position: absolute;
  /* CD-100/CD-406 + G12/CD-37 superseding it: the hairline is "w-full" in
     the frame, i.e. inset the CARD's own 16px padding from the CARD's
     border-box edge (595 -> 563, confirmed against 321:258/480 and
     intel's 421:512). Since CD-37 moved that 16px onto `.panel` itself,
     `.panel-head` (this pseudo-element's positioning context) is now a
     narrower box than the card — card_content(593) minus the card's own
     32px horizontal padding = 561 — so the hairline needs to extend
     BEYOND panel-head's own edges by 1px each side (negative inset) to
     land back at the frame's true 563: 561 - (-1) - (-1) = 563. (Prior to
     CD-37 this was left/right 15, positive, computed against panel-head's
     then-593px un-padded box — same target, different reference box.) */
  left: -1px; right: -1px;
  top: calc(100% - 1px);
  height: 1px;
  background: var(--ra-surface-card);
}
/* One row since 2026-10-11 (the owner: the label beside the title, "vertically center the panel titles"; Figma's head,
   panelhead Q1 option c): the title and its label (.sub) centred on each other, 6 apart (Figma phd-title: h, gap 6,
   counter-axis centre); the head keeps its own height and centres the row (.panel-head align-items). Where the head has
   no room the label wraps under the title, as the stacked head had it. */
.phd-title {
  display: flex; flex-wrap: wrap; align-items: center;
  min-width: 0; overflow: hidden; gap: 0 var(--ra-sp-xs);
}
/* S3-05a (design/PLAN.md §S4.1): the card-c head "info" affordance —
   14x14, sits BESIDE the title (not right-aligned into the actions
   cluster), inside the card's own title group. A plain icon, not a
   button: no tile, no hover state — the frame draws it as static
   supplementary chrome next to the h3, e.g. `<h3>…</h3><span
   class=card-info data-icon=info>`. Consumed by S3-05b (Trading's three
   activity cards) and any other card-c head that needs it; this class is
   the whole shared half — no other file defines it. */
.card-info {
  display: inline-flex; flex-shrink: 0;
  color: var(--ra-text-tertiary);
}
/* Panel-head's flexible pusher — was 15x inline style=flex:1 in
   panels/screener/pane.html (session 2 QA F12), now one class. Default
   behaviour is unchanged from the inline style: grow to absorb slack,
   pushing whatever sits after it (pill/badge/actions cluster) to the
   head's right edge. screener.css scopes an override for
   .scr-panel:not(.trd-cp), where .phd-title itself already grows into
   slack (its own F5 fix) — see that rule for why this one needs to yield
   there instead of competing with the title for shrink space. */
.phd-spacer { flex: 1; }
.panel-head h3, .phd-title h3 { font: var(--ts-h3-card-title); letter-spacing: var(--ts-h3-card-title-ls); /* Space Grotesk */
  color: var(--ra-text-primary);
  text-transform: uppercase;
  margin: 0;
  padding: 0;
  border: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* FOUND WHILE VERIFYING F-13's falsifiable 9/9 sweep (S16-R1) -- NOT part
   of the ruled R1/R2/R3 set, reported to the lead separately, own commit.
   --ra-text-primary has no [data-theme="light"] override anywhere (the
   same gap R1/R2/R3 already fixed elsewhere); .panel-head ITSELF already
   flips correctly (widgets.css:1034, its own direct light-theme rule) --
   unlike R1-R3, this is NOT contingent on R4 landing, it is a LIVE defect
   today, already shipped, on every route: measured live every panel title
   (News Scan/P&L Daily/Account/Industries/Open Orders/Positions/etc, all
   five routes) renders --ra-text-primary's own near-white value on that
   already-light head background -- invisible. --text-1, the same legacy
   alias every other fix this session routes through -- no new token
   minted. */
[data-theme="light"] .panel-head h3, [data-theme="light"] .phd-title h3 { color: var(--text-1); }
/* The label (symbol, sum) is Figma panel-head's `sub`, Caption/Helper 12/18, the app's (Figma's 9/16 is not built; Fitts & Miller parity: the head's spacing follows the
   proposal, owner 2026-10-17). */
.panel-head .sub, .phd-title .sub { font: var(--ts-caption-helper); letter-spacing: var(--ts-caption-helper-ls); /* Inter */
  color: var(--ra-text-secondary);
  text-transform: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Figma panel-head `meta` (Fitts & Miller parity; Ganadoras' «10 acciones»): 12 Regular in text/muted, after the free
   space and before the head's controls. */
.panel-head .phd-meta { font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls); color: var(--ra-text-muted); white-space: nowrap; }
/* Icon registry consumer (NEW-A1, foundation/icons.js — PV2.icon /
   PV2.icons.hydrate). `.ic` is the existing account.js idiom
   (css/account.css:54 scopes it narrower for its own nav); this is the
   general default so any `<span class=ic data-icon=NAME>` centers its
   hydrated <svg> without depending on a flex parent's own alignment. */
.ic { display: inline-flex; align-items: center; justify-content: center; }
/* design-sync 2026-10-03 (Figma icon set, 24 grid): icons.js sets no per-icon size; the container picks it through
   --ic-size (a button by its size class), 16 by default. Size only -- the stroke stays the icon's own token. */
.ic-svg { width: var(--ic-size, var(--ra-size-icon-lg)); height: var(--ic-size, var(--ra-size-icon-lg)); }

/* Panel-head action CLUSTER (PV2.ui.phdActions, panel-head.js): the tiles in their groups, the panel menu's `more`
   tile closing the window group, the close last; what a narrow head hides is in the panel menu, one click away.
   design-sync 2026-10-03 (Figma phd-actions 823:3): every action, the alerts bell included, is an icon-only
   24 square (--ra-size-control-sm, was 22) with a 16 icon (--ra-size-icon-lg) -- 4 gap, 5.33 px of air a side.
   min/max on both axes pin the square against any size class (.btn.xs 18, .panel-close 22, #p_review / Stats ids). */
.phd-actions {
  display: flex; align-items: center; gap: var(--ra-sp-2xs); flex-shrink: 0; height: var(--ra-size-control-sm);
}
/* Fitts & Miller 03 (Figma phd-actions 823:3, 2026-10-17): each tile takes clicks over a 28 square centred on it (an
   invisible box 2 px out on every side of the tile's 1 px border -- the box is placed from inside the border; it
   outranks the .btn.xs band), so the squares touch inside a group (the 4 gap); the window group starts 8 after the
   tools' last square (.phd-gs, set by phdActions); a 1 x 16 hairline (not clickable) stands 8 after the window group
   and 8 before the close's square. */
.phd-actions > * { position: relative; }
.phd-actions > *::after, .phd-actions > .btn.xs::after {
  content: ""; position: absolute; inset: calc(-1 * (var(--ra-sp-3xs) + var(--ra-border-hairline-w))); height: auto; transform: none;
}
.phd-actions > .phd-gs { margin-left: var(--ra-sp-sm); }
.phd-actions > .panel-close:not(:first-child) { margin-left: calc(2 * var(--ra-sp-sm) + var(--ra-border-hairline-w)); }
.phd-actions > .panel-close:not(:first-child)::before {
  content: ""; position: absolute; top: 50%; transform: translateY(-50%); pointer-events: none;
  left: calc(-1 * (var(--ra-sp-3xs) + var(--ra-sp-sm) + 2 * var(--ra-border-hairline-w)));
  width: var(--ra-border-hairline-w); height: var(--ra-size-icon-lg); background: var(--ra-surface-card);
}
.phd-actions > * {
  width: var(--ra-size-control-sm); min-width: var(--ra-size-control-sm); max-width: var(--ra-size-control-sm);
  height: var(--ra-size-control-sm); min-height: var(--ra-size-control-sm); max-height: var(--ra-size-control-sm);
  padding: 0; margin: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--ra-radius-sm);
  font-size: var(--ra-fs-12); line-height: 1;
}
/* T5/design-qa B7: .btn.xs (Sounds — 2 classes, e.g. .btn.ghost.xs) carries
   its own height:18px (components.css .btn.xs rule) at the SAME
   specificity as .phd-actions > *'s 22px, and cascade order let it win —
   found live: 22x18 instead of 22x22, sitting 4px low and mis-pitched
   against its neighbours. One more class here (.phd-actions > .btn.xs)
   forces it back onto the 22x22 grid regardless of source order.
   design-sync 2026-10-03: superseded by the min/max-height clamp above, which holds every child at 24. */
/* design-sync 2026-10-03 (Figma phd-actions 823:3): the icon is 16 whatever size class the tile carries (.btn.xs sets 12). */
.phd-actions > *, .phd-actions > .btn.xs { --ic-size: var(--ra-size-icon-lg); }
/* CD-648 (design/COMPONENT_DIFF.md §16.5, found by auditor B sweeping all
   104 icon shapes app-wide): `.btn.sm`'s own `padding: 0 8px` (0,2,0) is a
   tie the rule above never fought — this selector is (0,3,0), so it always
   wins regardless of source order, but the ORIGINAL fix only carried
   `height`, not `padding`. Left uncorrected, `.phd-actions > *`'s padding:0
   (0,1,0) loses outright to `.btn.sm`'s (0,2,0), eating 16 of the tile's
   22px and leaving a 6px-wide flex slot for the chart head's `.ceye` icon —
   an SVG with an explicit width attribute shrinks to fit, same as any
   other flex item, rendering 6x14 instead of 14x14 (then re-gridded to
   16x16 below). Same species as the height tie 8 lines up; same remedy. */
.phd-actions > .btn.xs, .phd-actions > .btn.sm { padding: 0; }
/* S3-02a (design/PLAN.md §S4.1): `.phd-actions > *`'s border-radius
   (var(--ra-radius-sm), 6px) and `.btn`'s (var(--r-sm), 4px, base.css:15)
   are both specificity (0,1,0) — a tie broken by source order, and `.btn`
   is 327 lines later, so every cluster child carrying `.btn` rendered r4
   while `.panel-close`/`.phd-overflow` (not `.btn`) rendered r6. Same shape
   as the .btn.xs height tie five lines above; same remedy, one more class
   to win it. Do NOT touch --r-sm or --ra-radius-sm — `.btn.ghost` alone
   appears 125x/21 files and a token edit would restyle the whole product. */
.phd-actions > .btn { border-radius: var(--ra-radius-sm); }
/* Glyph colour for the new inline `currentColor` icons (NEW-A1) needs no
   extra binding: `.btn` already sets color (text-2 rest / text-1 hover,
   below), and `.panel-close` already has its own rest/hover colour pair at
   panels/screener/screener.css:76,80 (text-4/text-1) — CORRECTED, this
   comment previously (wrongly) claimed panel-close had none and added a
   components.css override that silently out-specificity'd it, replacing
   text-4 with text-2 nobody asked for. Caught checking a hover-tie the
   team-lead flagged on a different fix; same failure shape, so reverted
   before it shipped rather than left for QA to find. */
/* T5/design-qa B6: icons rendered as bare glyphs with no resting tile —
   the frame shows all five slots (including close) as visible 22x22 tiles
   at rest, not just on hover; close in particular read as disconnected
   from its siblings without one. QA F5 (session 2): "all five" wasn't
   actually all five — .pip-pop (the PiP popout button, styled by its own
   class rather than .btn.ghost) was missing from this list, so it alone
   rendered as a bare glyph with no chip tile while its four siblings had
   one. Added below rather than assumed covered by "all five" in prose. */
/* CD-4 (design/COMPONENT_DIFF.md §T2.2, the seed rule): the frame's
   action-link-active tile is `strokes: []` — no border at all, on any of
   the panel-head action tiles. The 1px border this rule used to add was
   the same colour as the fill (surface.card on both), so it read as a
   pass visually while still eating 2px of the 22px box — content box
   measured 20x20 against the frame's 22x22. Fill only; no border (Figma phd-actions 823:3, 2026-10-18: every tile's
   stroke is hidden) -- the 1 px border stays transparent, since the 28 hit box above is placed from inside it. */
.phd-actions > .btn.ghost,
.phd-actions > .panel-close {
  background: var(--ra-surface-card);
  border: 1px solid transparent;
}
.phd-actions > .btn.ghost:hover,
.phd-actions > .panel-close:hover {
  background: var(--ra-surface-card-elevated);
}
/* CD-1/2/3 (design/COMPONENT_DIFF.md §14.4 item 1 — the CD-0 seed defect):
   the frame paints every glyph in this cluster text.axis (#606060); the app
   inherited `.btn`'s text.secondary (#9AA1A4) for the ghost buttons and
   `.panel-close`'s own text.faint (#4E555C) — neither ever bound to
   --ra-text-axis, which already exists (tokens.css:78) for exactly this
   value. Fitts & Miller parity (Figma phd-actions 823:3, sampled on the Screener template 17:84307): the window group,
   the link, the `more` tile and the close are text/secondary; the tools that show a state (the alerts bell, the chart's
   eye) keep text/axis. */
.phd-actions > .btn.ghost,
.phd-actions > .panel-close {
  color: var(--ra-text-secondary);
}
.phd-actions > .btn.phd-act-sounds, .phd-actions > .btn.ceye { color: var(--ra-text-axis); }
/* Hover lifts the glyph to --ra-text-hover (white in dark, --text-1 in light). Declared before `.on`
   so a linked tile keeps its teal on hover; a grouped link tile keeps its ink (panel.css). */
.phd-actions > .btn.ghost:hover,
.phd-actions > .panel-close:hover {
  color: var(--ra-text-hover);
}
/* CD-5: the link button's active/linked skin — the frame's own raw paint
   data on this vector (both 451:1273 here and 341:4434 in the topbar,
   design/COMPONENT_DIFF.md CD-73) carries #7EE0CD stacked under the resting
   #606060; screener.js toggles `.on` on click (both the static chart
   button and every `_attachPanelChrome()`-injected one), so the active skin
   only needs a colour swap, no markup change. Wins over the generic
   `.btn.on` (components.css above) on specificity (4 classes vs 2). */
.phd-actions > .btn.ghost.on {
  color: var(--ra-accent-primary-soft);
}
/* S13-A1's disabled-tile rule (LEAD_RULINGS S13-R4) removed here, in the
   SAME commit as S13-A6 (the landing) -- the link tile is no longer
   disabled; screener.js's click handler opens the real popover. */
/* S3-30 remainder (design/PLAN.md §S4.1, §S2 C5): the three #p2
   .phd-actions > .pip-pop rules that lived here (background/hover
   specificity-insurance + a margin-left override for pip.js's own
   runtime-injected <style>) are DELETED, not just unused — S3-38 removed
   pip.js's auto-injection entirely, so `.pip-pop` no longer exists
   anywhere in the DOM and these rules could never match again. Their
   comments also carried a stale "shell/pip.js, not ours to edit" claim
   (§S2 C5): pip.js matches platform/frontend/** and always was inside
   editScope — that phrasing traced back to a stale session-2 comment here
   and propagated into PANEL_MATRIX.md's own routing. Deleting the block
   removes the misinformation rather than patching it in place. */
/* Sounds keeps its numeric badge visible on the icon itself — a stateful
   toggle (alerts armed y/n) must never hide in the panel menu (a narrow head
   keeps it, opts.keep), PLAN.md's explicit reasoning ("esconder el estado de
   un toggle de alertas esconde estado relevante para la seguridad del
   usuario"). */
/* design-qa B1: the frame's badge (432:5576) is INLINE, margin-left 4,
   beside the icon — not a corner overlay on top of it. The Sounds button
   is one of .phd-actions' rigid 22x22 tiles (Amendment 3's fixed grid),
   so true DOM-flow inline would need the tile to grow.
   An earlier attempt faked "beside" with `left:100%`, anchoring the badge
   past the tile's own right edge. That REGRESSED: the badge takes no
   layout space, .phd-actions' gap is 4px, so the badge (x549..565, opaque
   #2A2F33) painted directly over the NEXT tile (x549..571) and hid a
   working control. Measured by design-qa and confirmed by the lead.
   Real fix, not the corner-overlay fallback: the badge is now a genuine
   flex SIBLING inside .phd-actions (pane.html moves it out of the
   button; screener.js's _layoutActionClusters() inserts it right after
   its own trigger when building the cluster) — the same "class the tile
   system does not count" solution as CD-139's Cols ▾ relocation. It is
   not in protectedItems/candidates, so it never consumes one of the n
   tile slots; .phd-actions' own `gap:4px` supplies the frame's
   margin-left 4 for free, no separate margin needed. `.phd-actions > *`'s
   22x22 tile-sizing (Amendment 3) would otherwise catch it too, since
   it's now a direct child — this rule's `width` wins on specificity
   (2 classes vs 1), but `min-width`/`max-width` are separate properties
   the plain `width` override doesn't touch, so the tile rule's own
   min-width:22px was still clamping this up to 22px until they were
   overridden explicitly too (caught live measuring the rendered box,
   not assumed from the specificity win alone). */
.phd-actions .snd-badge {
  width: 16px; min-width: 16px; max-width: 16px; height: 13px; flex-shrink: 0;
  min-height: 0; max-height: none; /* design-sync 2026-10-03: out of the tiles' 24 height clamp */
}

/* The panel menu (Fitts & Miller 03 / 05; Organisms Popover=panel-menu 675:5122), a PV2.ui.dropdown the `more` tile or
   a right-click on the head opens (panel-head.js): 300 wide on the tape ground, a surface/card ring, radius 4, 4 inside,
   shadow/popover; its headings «Panel · NBBO» and «Mover» in h6 11/500 upper, text/secondary, 8 / 12 / 4; the kit's
   30 px rows, spanning the menu (addendum 3), 12 in from both sides, a hint at the right; its three chunks 9 px apart (a
   hairline 4 from each side, frame 27). It shows whole (324 tall in Figma), past the kit's 320 cap. */
.ui-dd.phd-menu {
  width: 300px; max-height: none; background: var(--ra-surface-tape); border-color: var(--ra-surface-card);
  border-radius: var(--ra-radius-xs); box-shadow: var(--ra-shadow-popover);
}
.phd-menu-sec, .phd-menu > .ui-dd-head {
  display: block; padding: var(--ra-sp-sm) var(--ra-sp-lg) var(--ra-sp-2xs);
  font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); text-transform: uppercase;
  color: var(--ra-text-secondary);
}
.phd-menu > .ui-linkpop-hr { display: block; margin: var(--ra-sp-2xs) 0; }
.ui-dd.ui-dd-anim.phd-menu > .ui-dd-item { padding-left: var(--ra-sp-lg); padding-right: var(--ra-sp-lg); }

/* == ds/atoms/hscroll.css == */
/* ------------------------------------------------------------
   .hscroll -- ONE shared horizontal-scroll behaviour (S12-R2). The app
   shipped SIX ad-hoc scroll rows plus a seventh imperative handler
   (design/PLAN.md §S12.7.2); every one folds onto this instead of being
   copied per panel (the owner's standing modularity directive). Opt-in
   by markup class; the JS half (a wheel shim that only ever consumes an
   event the row actually moved on, plus the `data-hs` writer) lives in
   js/core/ui.js and needs no registration call — it delegates off
   `document`, so any row built after load picks it up for free.
   ------------------------------------------------------------ */
.hscroll {
  overflow-x: auto;
  /* `overflow-x:auto` alone computes `overflow-y` to `auto` too (a
     non-`visible` value on one axis forces the other off `visible`) —
     measured live turning `.scr-panel.trd-cp > .panel-head` into a
     vertical scroll container the moment this was tried. Every `.hscroll`
     row is a single line; lock the axis it isn't for. */
  overflow-y: hidden;
  /* S12-R7: a thin scrollbar was considered and declined — both idioms
     being folded here already hide theirs, and re-introducing one is a
     visible divergence from frames that draw none. The fade below (driven
     by `data-hs`) carries the same information. */
  scrollbar-width: none;
}
.hscroll::-webkit-scrollbar { display: none; }
/* Without this a flex row SQUASHES its children instead of overflowing —
   zero overflow, fully broken, and invisible to any overflow census by
   construction (it never trips `scrollWidth > clientWidth`). */
.hscroll > * { flex-shrink: 0; }
/* The fade is STATE-CONDITIONED on `data-hs` (written by ui.js), never a
   permanent gradient — a fade on a row that already fits is a lie in the
   other direction (S12-R7). `data-hs="none"`/absent paints nothing.
   `--mask-edge-opaque` is alpha-only (tokens.css) — the gradient's own
   `transparent` stop is what does the work, not this colour. */
.hscroll[data-hs="start"] {
  mask-image: linear-gradient(to right, transparent, var(--mask-edge-opaque) 24px);
  -webkit-mask-image: linear-gradient(to right, transparent, var(--mask-edge-opaque) 24px);
}
.hscroll[data-hs="end"] {
  mask-image: linear-gradient(to left, transparent, var(--mask-edge-opaque) 24px);
  -webkit-mask-image: linear-gradient(to left, transparent, var(--mask-edge-opaque) 24px);
}
.hscroll[data-hs="both"] {
  mask-image: linear-gradient(to right, transparent, var(--mask-edge-opaque) 24px, var(--mask-edge-opaque) calc(100% - 24px), transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, var(--mask-edge-opaque) 24px, var(--mask-edge-opaque) calc(100% - 24px), transparent);
}

/* .hscroll-pin -- S12-R2 clause 3's edge-pinning rule, generalised from
   the single surface that already proved it necessary: shell/pip.js's
   sticky-edge fix ("the pip button landed ~600px past the visible right
   edge and became unclickable" until pinned). A control class is pinned
   if losing it costs the user the panel — close / pop-out / fullscreen at
   minimum. Opt in with this class on any future pinned control; the one
   case that exists TODAY is `.phd-actions`, which is pinned by the
   STRUCTURAL rule below rather than a markup class — measured: it is
   every scrollable panel-head's own last element child, so no JS and no
   markup edit is needed, and no stacking context is created on the heads
   that never scroll. */
.hscroll-pin,
.hscroll > .phd-actions {
  position: sticky; right: 0; z-index: 3;
  background: var(--bg-2);
  /* Fade-to-surface, not a glow — same colour as the background, so the
     scrolling row appears to slide UNDER the pinned cluster instead of
     stopping beside it. Same mechanism shell/pip.js proved. */
  box-shadow: -8px 0 8px -4px var(--bg-2);
}

.panel-body {
  /* CD-37/G12: horizontal removed (now on .panel); vertical (--sp-3, 8px)
     stays here rather than moving to the card too — .panel-body scrolls
     (overflow:auto below), and a card-level bottom padding would sit
     OUTSIDE that scroll region, visible as fixed chrome after the
     scrollable box rather than travelling with the content's own end. */
  padding: var(--sp-3) 0;
  flex: 1;
  min-height: 0;
  overflow: auto;
}
/* .panel .panel-body.flush ties the card-a-tight/b/c overrides above at
   (0,3,0) — source order (this rule is later) then decides, so .flush wins
   on every card variant instead of silently losing to the family's own
   padding. Plain .panel-body.flush stays for family A (no variant class). */
.panel-body.flush, .panel .panel-body.flush { padding: 0; }
.panel-body.col   { display: flex; flex-direction: column; }

/* == ds/base/globals.css == */
/* ------------------------------------------------------------
   Global :focus-visible — DESIGN_SPEC §4/PLAN.md §4.2: 1px accent.primary
   outline, 2px offset. Applied once, globally, not per-component (the
   plan's explicit reasoning: "keeps 9 devs from inventing 9 focus styles").
   ------------------------------------------------------------ */
:focus-visible {
  outline: 1px solid var(--ra-accent-primary);
  outline-offset: 2px;
}

/* ------------------------------------------------------------
   2.0 scrollbar — DESIGN_SPEC §3: 3-4px, track surface.scrollbar-track @50%,
   thumb surface.scrollbar-thumb. Global default; panels/screener/screener.css
   overrides it for its own denser tables where a wider 11px thumb reads
   better (that file is dev-b's too, no cross-file edit needed).
   ------------------------------------------------------------ */
* { scrollbar-width: thin; scrollbar-color: var(--ra-surface-scrollbar-thumb) transparent; }
*::-webkit-scrollbar { width: 4px; height: 4px; }
/* ST2 (CD-619, design/PLAN.md §U1): color-mix(in oklch, X, transparent) drops
   the hue on low-chroma sRGB sources — Chromium's sRGB->oklch conversion
   emits a powerless (`none`) hue below ~0.02 chroma, and color-mix propagates
   it, rendering as hue 0. --ra-surface-scrollbar-track (#262B33) is exactly
   that. Relative colour syntax preserves the source's hue exactly instead of
   dropping it; measured painted delta at this site: 6 channels. */
*::-webkit-scrollbar-track { background: oklch(from var(--ra-surface-scrollbar-track) l c h / 0.5); }
*::-webkit-scrollbar-thumb { background: var(--ra-surface-scrollbar-thumb); border-radius: var(--ra-radius-xs); }

/* == ds/base/states.css == */
/* ds/base/states.css — Level 1 states (platform/docs/frontend/behavior/README.md). The attribute a screen reader announces
   is the one CSS styles: one look per state, tokens only. A controller writes it on the region whose content is in that
   state; the region's face (PV2.mol.loading, PV2.mol.emptyState) is a molecule inside it. */
/* Busy: the progress cursor. A control that sets its own cursor restates it: the .btn atom's busy and disabled faces
   (opacity --ra-opacity-disabled) are the last rules of ds/atoms/button.variants.css, after the variants they beat. */
[aria-busy="true"] { cursor: progress; }
/* Stale never hides a value: numbers stay readable, dimmed. A region is stale by its own data age (data-state), or because
   the link is not live (body[data-conn], written by app-state.js) when it shows streamed values (data-live). 'nocore' is a
   missing safety module, not a dead link: data still streams. */
[data-state="stale"] .num, [data-state="stale"].num,
body[data-conn]:not([data-conn="live"]):not([data-conn="nocore"]) [data-live] .num { opacity: var(--ra-stale-opacity); }
/* == ds/molecules/empty-state.css == */
/* ------------------------------------------------------------
   Empty state — Figma Platform `empty-state` (design-sync 2026-10-03): optional
   illustration over a dot grid + h2/section title + body/medium helper.
   New reusable primitive (PLAN.md §3.2 — genuinely new this session);
   not yet wired into a specific panel's markup, ready for the panels that
   need it (Trading's empty order/position states are S8's, out of scope).
   ------------------------------------------------------------ */
.empty-state {
  flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: var(--ra-sp-sm); padding: var(--ra-sp-2xl) var(--ra-sp-xl); text-align: center;
}
/* design-qa (card-c empty states): .empty-state's own flex:1/justify-
   content:center were always correct but inert — .panel-body (the
   parent) has no `display:flex` of its own, so a flex-item property on
   its child has no flex container to act inside; content rendered at
   its own natural height, top-aligned, instead of centred. Scoped via
   :has() to panel-bodies that actually contain one, not every
   .panel-body app-wide — .empty-state is a shared, reusable primitive
   (not yet wired anywhere but Trading's three activity cards), so this
   fix travels with it to any future consumer automatically. */
.panel-body:has(.empty-state) { display: flex; flex-direction: column; }
/* The 48x48 icon circle left the Figma component (2026-10-02); kept for the trading cards' hand-written markup. */
.empty-state .icon {
  width: 48px; height: 48px; border-radius: var(--ra-radius-full);
  background: var(--ra-surface-card);
  display: flex; align-items: center; justify-content: center;
  color: var(--ra-text-tertiary);
}
/* design-sync 2026-10-03 (Figma Platform empty-state): title h2/section, helper body/medium. */
.empty-state .title { font: var(--ts-h2-section); letter-spacing: var(--ts-h2-section-ls); color: var(--ra-text-primary); }
.empty-state .helper { font: var(--ts-body-medium); letter-spacing: var(--ts-body-medium-ls); color: var(--ra-text-secondary); }
/* design-sync 2026-10-03 («Icon title helper» / «With action», 520x419): the 240x184 Illustration over the 454x294
   `Background / Dot grid`, which paints behind the content; min-height keeps the grid whole, overflow clips it narrow. */
.empty-state.illustrated { position: relative; isolation: isolate; overflow: hidden; min-height: 419px; }
.empty-state .illus { position: relative; flex: none; width: 240px; height: 184px; }
.empty-state .illus-art { display: block; width: 100%; height: 100%; }
.empty-state .illus-dots { position: absolute; z-index: -1; left: 50%; top: 50%; width: 454px; height: 294px; transform: translate(-50%, -50%); pointer-events: none; }
/* Fitts & Miller 26 (Platform empty-state «With action», its `disenos` part; Screens «Todo oculto»): `foot`, a row 16 under
   the action (the column's 8 and its own 8), its parts 8 apart: a caption/11 lead and the caller's chips. */
.empty-state .es-foot { display: flex; align-items: center; gap: var(--ra-sp-sm); margin-top: var(--ra-sp-sm); }
.empty-state .es-foot > .label { font: var(--ts-caption-11); letter-spacing: var(--ts-caption-11-ls); color: var(--ra-text-secondary); }


/* S51 WP4 VARIANT `.note`: the NOTE form -- one line of text in normal flow (M.emptyState({label})),
   the panels' "No watchlists yet." / "No data" lines. Resets the whole-body flex centring the base
   carries and keeps the inset and the muted colour; a panel adds its own type/inset deltas as
   `.empty-state.note.<panel class>`. */
.empty-state.note { flex: 0 1 auto; display: block; flex-direction: row; align-items: normal; justify-content: normal; gap: normal;
  padding: var(--ra-sp-2xl); color: var(--text-4);
  font: var(--ts-body-medium); letter-spacing: var(--ts-body-medium-ls); }   /* design-sync 2026-10-03: the Note span is body/medium */

/* Loading (Feedback standard) — the face of an aria-busy region, PV2.mol.loading. It shows only after --mo-delay-busy, so a
   fast answer never flashes it. That delay is not motion: the reduced-motion switch (ds/organisms/app-motion.css) zeroes
   every animation-delay, so this face restores its own and only the fade collapses. */
.empty-state.loading { opacity: 0; animation: ra-busy-in var(--mo-dur-view) var(--mo-ease-settle) var(--mo-delay-busy) forwards; }
html.reduce-motion .empty-state.loading { animation-delay: var(--mo-delay-busy) !important; }
@media (prefers-reduced-motion: reduce) { .empty-state.loading { animation-delay: var(--mo-delay-busy) !important; } }
.empty-state.loading .loading-dots { color: var(--ra-text-tertiary); }
.empty-state.loading.note .loading-dots { margin-inline-end: var(--ra-sp-xs); }
[data-state="error"] > .empty-state.note > .btn { margin-inline-start: var(--ra-sp-xs); }   /* the Error face only: Retry beside its note */
@keyframes ra-busy-in { to { opacity: 1; } }
/* == ds/atoms/grip.css == */
/* ------------------------------------------------------------
   Panel-head grip glyph (design/DESIGN_SPEC.md §1.6 "panel-head anatomy":
   grip glyph in text.axis, before the title). Wired into Stats/Trading/
   Intelligence panel-heads (own `<span class=grip>⠿</span>` markup) and,
   as of CD-11 (session 4), Screener's own panel chrome too (screener.js
   injects `class="scr-grip grip"` reusing this paint). CD-16: font-size
   was 12px against the frame's Inter 400 11px — corrected.
   CD-16 (S10-T4): the font-size fix left `line-height: 1`, so the glyph's
   box measured 11px tall against the frame's 8x13 (font-size 11 x
   line-height 1 = 11). `.t-u-11` (tokens.css:520) is this exact glyph's
   own established typography rule — Inter 400 11px/13.31 line-height,
   named for `419:316` in its own comment — so the box height now uses
   that line-height instead of duplicating a bare `1`. Width set to the
   frame's 8px explicitly; the glyph's intrinsic advance width (8.188)
   is sub-pixel and not forced further.
   ------------------------------------------------------------ */
.panel-head .grip { font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls); color: var(--ra-text-axis); width: 8px; flex-shrink: 0; cursor: grab; user-select: none;
  display: inline-flex; align-items: center; justify-content: center; }

/* == ds/atoms/drag-handle.css == */
/* ------------------------------------------------------------
   seal/drag-handle (design/DESIGN_SPEC.md §3/§6.6): 8px, 5 grip-dots 3×3
   r1.5 surface.subtle, pitch 7 (itemSpacing 4 + 3px dot). Two
   orientations — the column gutter (vertical, width 8) and a row gutter
   inside a column (horizontal, height 8). Wired into every splitter
   across all five routes (trading/stats/intel/screener/base).
   CD-251 (design-qa A1, `432:5679`): gap was 7, not the frame's
   itemSpacing 4 — pitch rendered 10 (7+3) instead of 7 (4+3). Per
   adjudication A3 this single fix also closes CD-445 on intelligence:
   five dots at pitch 10 span 40px vs the frame's 28px at pitch 7;
   centred, that's exactly the 6px intel measured as an offset.
   The platform's one resize-handle face (the owner, 2026-10-11: "the hover effect on the resize handlers is not
   consistent"), his own edit of the screener's splitters: at rest the five dots alone, no bar, in --ra-text-axis (the
   grey he asked for); hovered or dragged, the bar one grey step up (--line-strong, a ramp step, never a hue). The screener's and Review's splitters
   (ds/organisms/screener-splitters.css) and the Stream tab's (ds/templates/stream-grid.css) wear the same. A grip
   resizes only once code wires it (PV2.layout marks it data-pl-wired): only then that face, the resize cursor and a
   hit area across its 24 gutter; a grip nothing wires is a separator in Figma's grip grey (surface.subtle).
   ------------------------------------------------------------ */
.drag-handle {
  flex-shrink: 0; display: flex; align-items: center; justify-content: center;
  background: transparent;
}
.drag-handle.v { width: 8px; flex-direction: column; gap: var(--ra-sp-2xs); }
.drag-handle.h { height: 8px; flex-direction: row; gap: var(--ra-sp-2xs); }
.drag-handle .dot {
  width: 3px; height: 3px; border-radius: 1.5px; flex-shrink: 0;
  background: var(--ra-surface-subtle);
}
.drag-handle[data-pl-wired] { position: relative; transition: background var(--mo-dur-control); }
.drag-handle[data-pl-wired].v { cursor: col-resize; }
.drag-handle[data-pl-wired].h { cursor: row-resize; }
.drag-handle[data-pl-wired].v::before { content: ""; position: absolute; inset: 0 calc(-1 * var(--ra-sp-sm)); }
.drag-handle[data-pl-wired].h::before { content: ""; position: absolute; inset: calc(-1 * var(--ra-sp-sm)) 0; }
.drag-handle[data-pl-wired] .dot { background: var(--ra-text-axis); }
.drag-handle[data-pl-wired]:hover, .drag-handle.dragging { background: var(--line-strong); }
/* == ds/atoms/status-pill.css == */
/* ------------------------------------------------------------
   status-pill (design/DESIGN_SPEC.md §6.4 "status-connected pattern"): 28
   tall, radius 14 (pill), tone @12% background + 0.5px solid border in the
   same tone. Recurs with different hues — header "Connected", Trading's
   kill-switch (Intelligence's avg pill is the kit .pill since the 2026-10-09
   Figma round) — so the tone is a custom property,
   not a class per colour. Named distinctly from the pre-existing
   `.status-chip` (css/app.css, the FOOTER's 23px r6 neutral pills — a
   different Figma component that happens to share the English name;
   design/DESIGN_SPEC.md §1.9 vs §6.4 are two different nodes) to avoid
   colliding with it.
   ------------------------------------------------------------ */
.status-pill { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); --chip-tone: var(--ra-accent-primary);
  display: inline-flex; align-items: center; gap: var(--ra-sp-xs);
  height: var(--ra-size-badge-lg); padding: 0 var(--ra-sp-md);
  border-radius: var(--ra-radius-full);
  /* ST2 (CD-619): --chip-tone is conditional (accent.primary, semantic error,
     text.tertiary...) — some bound values are low-chroma sRGB and drop hue
     under color-mix regardless of which one a given instance holds (a
     text.tertiary tone is exactly this case). Relative colour syntax fixes
     the class, not one value. */
  background: oklch(from var(--chip-tone) l c h / 0.12);
  border: 0.5px solid var(--chip-tone);
  color: var(--chip-tone);
  white-space: nowrap; }
.status-pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }

/* == ds/atoms/chip.css == */
/* ------------------------------------------------------------
   .chip / .chip-tiny (design/PLAN.md §4.1; design/DESIGN_SPEC.md §8.4 for
   chip-tiny's fill). Distinct from the pre-existing `.pill` (fully rounded,
   999px) and from `.status-pill` above (tone-parametrized idiom).
   .chip-tiny's contract is now VERIFIED (session 4, design/COMPONENT_DIFF.md
   §T2.1): every one of the seven declarations below this comment's previous
   version claimed was "not given by the spec" was in fact measured directly
   off 432:5594 / 421:504 and was wrong on all seven axes (h16/r7.14/
   surface.neutral-900/9px/600/no border). This is that verification —
   h22, r6 (--ra-radius-sm), pad 3/7, inactive fill surface.card + 1px
   surface.card-elevated border + text.secondary label, active fill
   surface.tape + 1px accent.dark border + text.primary label, Inter 500
   11/16 ls 0.2. Panels are NOT migrated onto this class (§S3 D-S3-5, held
   again at §T2.1's "no refactor" ruling) — `.tx-pill`/`.tx-chip`,
   `#intel-tabs button`, `#stSide button` and the `.segmented`-based chips
   below keep their own selectors and are each corrected to this table, per
   context, in their own files. This is the cited contract, not a shared
   rule that reaches into markup no one points at it from.
   ------------------------------------------------------------ */
/* design-sync 2026-10-03 (Figma chip 381:15, «Control heights»): every chip is 24, tiny included. */
.chip {
  display: inline-flex; align-items: center; gap: var(--ra-sp-2xs);
  height: var(--ra-size-control-sm); padding: 0 var(--ra-sp-sm);
  border-radius: var(--ra-radius-sm);
  background: var(--ra-surface-card);
  border: 0;
  color: var(--ra-text-secondary);
  font-family: var(--font-sans); font-size: var(--ra-fs-10); font-weight: 500;
  white-space: nowrap;
}
/* Platform chip State=on (owner, 2026-10-18: every switched-on chip): the selected-row tint and a Semi Bold label, its
   ink unchanged (text/secondary). */
.chip.on { background: var(--ra-state-row-hover); font-weight: 600; }
.chip-tiny {
  display: inline-flex; align-items: center; justify-content: center;
  height: var(--ra-size-control-sm); padding: var(--ra-sp-3) var(--ra-sp-7); --ic-size: var(--ra-size-icon-sm);
  border-radius: var(--ra-radius-sm);
  background: var(--ra-surface-card);
  border: 1px solid var(--ra-surface-card-elevated);
  color: var(--ra-text-secondary);
  font-family: var(--font-sans); font-size: var(--ra-fs-11); font-weight: 500;
  line-height: var(--ra-lh-16); letter-spacing: var(--ra-ls-px-0-2);
  white-space: nowrap;
}
/* Fitts & Miller (page 7's note; Platform chip Size=tiny State=on is 11/600): a tiny chip that is on turns 600. */
.chip-tiny.on {
  background: var(--ra-surface-tape);
  border-color: var(--ra-accent-dark);
  color: var(--ra-text-primary);
  font-weight: 600;
}
/* Figma chip Size=tiny, Tag=small (2971:1525), the root tag `small` (A.chip({label, tiny: true, tag: 'small'})): the
   15-tall tag of a folder's symbols, on the default face (surface/card, a surface/card-elevated ring; its `on` face was
   deleted on 2026-10-15) with the label in text/primary, 9 / 600 tracked 0.06em, r3; Figma's 1.5 / 3.5 inside a 0.5 ring
   is 1 / 3 inside the 1 px hairline. */
small.chip-tiny {
  height: auto;
  padding: calc(var(--ra-sp-3xs) - var(--ra-border-hairline-w)) calc(var(--ra-sp-2xs) - var(--ra-border-hairline-w));
  border-width: var(--ra-border-hairline-w);
  border-radius: var(--ra-radius-2xs);
  color: var(--ra-text-primary);
  font-size: var(--ra-fs-9); font-weight: 600; line-height: normal; letter-spacing: var(--ra-ls-em-06);
}
/* design-sync 2026-10-03: Figma chip `Content=icon+divider+label` (2721:2932) -- stroked, 4 inset, a 10 icon,
   a full-height 1 px divider, then the label (A.chip({icon, label, divider: true})). */
.chip.divided { padding: 0 var(--ra-sp-2xs); border: var(--ra-border-hairline-w) solid var(--ra-surface-card-elevated); --ic-size: var(--ra-size-icon-xs); }
.chip .chip-sep { align-self: stretch; width: var(--ra-border-hairline-w); background: var(--ra-surface-card-elevated); }
/* design-sync 2026-10-03 («Dropdown heights», Figma chip `caret`): a chip that opens a menu ends in the 10 px chevron. */
.chip > .ic[data-icon=chevron-down]:last-child, .chip-tiny > .ic[data-icon=chevron-down]:last-child { --ic-size: var(--ra-size-icon-xs); }

/* == ds/molecules/filters-row.css == */
/* ------------------------------------------------------------
   .filters-row (design/PLAN.md §4.1): 22 tall, r6 chip row — selected vs
   resting. Trading's own filter rows and Intelligence consume this.
   ------------------------------------------------------------ */
.filters-row { display: flex; align-items: center; gap: var(--ra-sp-2xs); height: 22px; }
.filters-row button { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); height: 22px; padding: 0 var(--ra-sp-sm);
  border-radius: var(--ra-radius-sm);
  background: var(--ra-surface-card);
  border: 1px solid transparent;
  color: var(--ra-text-tertiary);
  cursor: pointer; white-space: nowrap; }
.filters-row button.on {
  background: color-mix(in oklch, var(--ra-accent-primary) 12%, transparent);
  border-color: var(--ra-accent-primary);
  color: var(--ra-accent-primary);
}

/* == ds/atoms/switch.css == */
/* ------------------------------------------------------------
   .switch (design/PLAN.md §4.1): two drawn sizes, 36x20 r10 (default) and
   34x18 r9 (`.sm`). Trading's existing local `.tx-sw` is its own instance
   of this same component (panels/trading/trading.css) and is left in
   place — dev-b adopts this shared primitive only where it edits that file
   anyway, never a drive-by rename.
   Markup: <label class=switch><input type=checkbox><span class=track></span></label>
   ------------------------------------------------------------ */
.switch { position: relative; display: inline-block; width: 36px; height: 20px; flex-shrink: 0; }
.switch input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.switch .track {
  position: absolute; inset: 0; border-radius: 10px;
  background: var(--ra-surface-card); border: 1px solid var(--ra-border-default);
  transition: background-color var(--mo-dur-control), border-color var(--mo-dur-control);
  pointer-events: none;
}
.switch .track::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--ra-text-secondary);
  transition: transform var(--mo-dur-control), background-color var(--mo-dur-control);
}
.switch input:checked + .track { background: color-mix(in oklch, var(--ra-accent-primary) 20%, transparent); border-color: var(--ra-accent-primary); }
.switch input:checked + .track::after { transform: translateX(16px); background: var(--ra-accent-primary); }
.switch.sm { width: 34px; height: 18px; }
.switch.sm .track { border-radius: 9px; }
.switch.sm .track::after { width: 12px; height: 12px; }


/* S51 WP4 VARIANTS (design/PLAN_S51.md §4): the panels' switches drawn by the kit anatomy at the exact
   geometry each panel shipped -- the census graded each migration at zero pixels.
   .flat  34x18 r9, no stroke, track --ra-border-switch, thumb 14 --ra-text-switch-thumb;
          on: track --ra-accent-blue-deep, thumb --ra-text-primary (trading ticket G14/CD-223, screener SND-38)
   .navbar 36x20 r10, track --bg-3 + 1px --ra-surface-subtle, thumb 14 --text-1;
          on: track --ra-accent-blue-deep + border --ra-accent-primary-soft (trading nav bar CD-247)
   .lg    40x24 r999, track --ra-surface-card, no stroke, thumb 20 --ra-text-secondary;
          on: track mix(accent-primary 20%, card), thumb --ra-accent-primary (stats EMA, CD-312)
   State is `input:checked` OR the `.on` class on the root: a <button class=switch> (no checkbox) toggles
   `.on` and the JS that read that class keeps working; button.switch resets the UA button box.
   .flat / .navbar also accept the LABEL FORM -- the checkbox placed just before the root inside the label that wraps
   both (<label><input type=checkbox><span class="switch flat"><span class=track></span></span> text</label>): the
   label toggles it, and the checkbox keeps whatever the panel gave it (Trading's ticket and nav bar). */
.switch.flat { width: 34px; height: 18px; }
/* .flat and .navbar keep their track a STATIC block filling the root (the root stays the thumb's containing block):
   the recipes they reproduce painted the track as the positioned flex item itself, and a positioned track nested
   inside the positioned root adds a paint-layer boundary that snaps the thumb and the corners a fifth of a pixel
   away from that at a fractional y (Trading, WP4 -- graded to zero only with this layer structure). */
.switch.flat .track, .switch.navbar .track { position: static; display: block; width: 100%; height: 100%; box-sizing: border-box; }
.switch.flat .track { border-radius: 9px; background: var(--ra-border-switch); border: 0; transition: background var(--mo-dur-switch); }
.switch.flat .track::after { background: var(--ra-text-switch-thumb); transition: transform var(--mo-dur-switch), background-color var(--mo-dur-switch); }
input:checked + .switch.flat .track, .switch.flat input:checked + .track, .switch.flat.on .track { background: var(--ra-accent-blue-deep); border-color: transparent; }
input:checked + .switch.flat .track::after, .switch.flat input:checked + .track::after, .switch.flat.on .track::after { transform: translateX(16px); background: var(--ra-text-primary); }

.switch.navbar .track { background: var(--bg-3); border: 1px solid var(--ra-surface-subtle); transition: background var(--mo-dur-switch); }
.switch.navbar .track::after { top: 3px; left: 3px; background: var(--text-1); transition: transform var(--mo-dur-switch), background-color var(--mo-dur-switch); }   /* 2px inside the 1px stroke, measured from the root */
input:checked + .switch.navbar .track, .switch.navbar input:checked + .track, .switch.navbar.on .track { background: var(--ra-accent-blue-deep); border-color: var(--ra-accent-primary-soft); }
input:checked + .switch.navbar .track::after, .switch.navbar input:checked + .track::after, .switch.navbar.on .track::after { transform: translateX(18px); background: var(--text-1); }

.switch.lg { width: 40px; height: 24px; }
.switch.lg .track { border-radius: var(--ra-radius-full); background: var(--ra-surface-card); border: 0; transition: background-color var(--mo-dur-switch); }
.switch.lg .track::after { width: 20px; height: 20px; background: var(--ra-text-secondary); transition: transform var(--mo-dur-switch), background-color var(--mo-dur-switch); }
.switch.lg input:checked + .track, .switch.lg.on .track { background: color-mix(in oklch, var(--ra-accent-primary) 20%, var(--ra-surface-card)); border-color: transparent; }
.switch.lg input:checked + .track::after, .switch.lg.on .track::after { transform: translateX(16px); background: var(--ra-accent-primary); }

.switch.on .track { background: color-mix(in oklch, var(--ra-accent-primary) 20%, transparent); border-color: var(--ra-accent-primary); }
.switch.on .track::after { transform: translateX(16px); background: var(--ra-accent-primary); }
button.switch { padding: 0; border: 0; background: transparent; cursor: pointer; font: inherit; color: inherit; }
/* == ds/atoms/checkbox.css == */
/* ------------------------------------------------------------
   seal/checkbox -- Figma acDx3hoowFphwB8tGtqIPi 3057:26338 (Unchecked /
   Checked / Indeterminate x Default / Hover / Pressed / Focus / Disabled).
   The app's default checkbox: every native <input type=checkbox> except the
   switch's, whose hidden input stays the switch's inset:0 hit area
   (atoms/switch.css). Zero specificity (:where), so any component rule still
   wins -- including those that hide an input -- and display / position are
   left alone for the same reason; disabled dims with filter:opacity so an
   input already hidden at opacity:0 stays hidden. Strokes are inset
   box-shadows, so the 2px focus ring moves nothing. The marks are Inter
   4.001's own U+2713 (dngb_check) and U+2212 outlines at the frame's 10px /
   12px and placement, used as masks painted in --ra-control-checkmark: the
   Inter the app loads has no U+2713, so a text check would come from a
   different fallback font on every OS.
   ------------------------------------------------------------ */
:where(input[type=checkbox]:not(.switch input)) {
  -webkit-appearance: none; appearance: none;
  flex: none; width: 16px; height: 16px; margin: 0;
  border-radius: var(--ra-radius-2xs);
  background: var(--bg-2);
  box-shadow: inset 0 0 0 1px var(--ra-control-checkbox-border);
  cursor: pointer;
}
:where(input[type=checkbox]:not(.switch input))::after { content: ""; display: block; width: 16px; height: 16px; }
:where(input[type=checkbox]:not(.switch input):not(:disabled):hover) {
  background: var(--ra-control-checkbox-hover-bg); box-shadow: inset 0 0 0 1px var(--ra-control-checkbox-hover-border); }
:where(input[type=checkbox]:not(.switch input):not(:disabled):active) {
  background: var(--ra-control-checkbox-pressed-bg); box-shadow: inset 0 0 0 1px var(--ra-control-checked); }
:where(input[type=checkbox]:not(.switch input):focus-visible) { outline: none; box-shadow: inset 0 0 0 2px var(--ra-accent-dark); }
:where(input[type=checkbox]:not(.switch input):is(:checked, :indeterminate)) { background: var(--ra-control-checked); box-shadow: none; }
:where(input[type=checkbox]:not(.switch input):is(:checked, :indeterminate):not(:disabled):hover) { background: var(--ra-control-checked-hover); box-shadow: none; }
:where(input[type=checkbox]:not(.switch input):is(:checked, :indeterminate):not(:disabled):active) { background: var(--ra-control-checked-pressed); box-shadow: none; }
:where(input[type=checkbox]:not(.switch input):is(:checked, :indeterminate):focus-visible) { box-shadow: inset 0 0 0 2px var(--ra-control-checked-focus); }
:where(input[type=checkbox]:not(.switch input):checked)::after {
  background: var(--ra-control-checkmark);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4.55 8.416 5.111 7.84 6.977 9.671 10.839 5.828 11.42 6.399 6.977 10.833Z'/></svg>") 0 0 / 16px 16px no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4.55 8.416 5.111 7.84 6.977 9.671 10.839 5.828 11.42 6.399 6.977 10.833Z'/></svg>") 0 0 / 16px 16px no-repeat;
}
:where(input[type=checkbox]:not(.switch input):indeterminate)::after {
  background: var(--ra-control-checkmark);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M10.668 8.211V9.207H5.189V8.211Z'/></svg>") 0 0 / 16px 16px no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M10.668 8.211V9.207H5.189V8.211Z'/></svg>") 0 0 / 16px 16px no-repeat;
}
:where(input[type=checkbox]:not(.switch input):disabled) { filter: opacity(0.4); cursor: not-allowed; }
:where(input[type=checkbox]:not(.switch input):not(:checked):not(:indeterminate):disabled) {
  box-shadow: inset 0 0 0 1px var(--ra-control-checkbox-disabled-border); }
/* == ds/molecules/data-table.css == */
/* ------------------------------------------------------------
   .data-table (design/PLAN.md §4.1): 40px header on surface.inset-deep,
   38px rows, zebra @20%. Distinct from the legacy `table.data` below (its
   own header/row rhythm predates the 2.0 measurement) — Intelligence and
   Stats opt into this one where they redraw a table this session.
   ------------------------------------------------------------ */
.data-table { font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls); width: 100%; border-collapse: collapse; }
.data-table thead th { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); height: 40px; padding: 0 var(--ra-sp-sm); text-align: left;
  background: var(--ra-surface-inset-deep);
  border-bottom: 1px solid var(--ra-border-default);
  color: var(--ra-text-tertiary);
  text-transform: uppercase; white-space: nowrap; }
.data-table tbody td {
  height: 38px; padding: 0 var(--ra-sp-sm);
  color: var(--ra-text-secondary);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.data-table tbody tr:nth-child(even) td {
  /* ST2 (CD-619): --ra-surface-card (#23282C) is a low-chroma sRGB source —
     see the scrollbar-track rule above for the mechanism. Painted delta: 2
     channels. */
  background: oklch(from var(--ra-surface-card) l c h / 0.2);
}

/* == ds/molecules/kv.css == */
/* ------------------------------------------------------------
   .kv (design/PLAN.md §4.1): label-over-value stack, gap 2. Trading's
   nav-bar (×6) and Stats consume this.
   ------------------------------------------------------------ */
.kv { display: flex; flex-direction: column; gap: var(--ra-sp-3xs); }
.kv .k {
  font-family: var(--font-sans); font-size: var(--ra-fs-9); font-weight: 400;
  color: var(--ra-text-tertiary); text-transform: uppercase; letter-spacing: var(--ra-ls-em-05);
}
.kv .v {
  font-family: var(--font-mono); font-size: var(--ra-fs-12);
  color: var(--ra-text-primary); font-variant-numeric: tabular-nums;
}
/* S51 WP4 VARIANT `.row`: the horizontal pair -- label left, value right, one line (Review's trade-context and
   risk rows). Type and colour stay the base's; a panel adds its own deltas as `.kv.row.<panel class>`. */
.kv.row { flex-direction: row; align-items: center; justify-content: space-between; gap: var(--ra-sp-sm); }
/* Figma round 2026-10-16 (Platform kv 320:9). Layout=strip, the KPI strip's cell: an h6 label (muted), an h2 value, a
   caption sub line (secondary). Tone=positive / negative tints the value at 95 %; Tone=split is win / loss, each in its
   tone around a primary slash, 4 apart. */
.kv.strip .k { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); color: var(--ra-text-muted); }
.kv.strip .v { font: var(--ts-h2-section); letter-spacing: var(--ts-h2-section-ls); }
.kv.strip .d { font: var(--ts-caption-11); letter-spacing: var(--ts-caption-11-ls); color: var(--ra-text-secondary); }
.kv.pos .v, .kv.split .win { color: color-mix(in srgb, var(--ra-accent-primary) 95%, transparent); }
.kv.neg .v, .kv.split .loss { color: color-mix(in srgb, var(--ra-semantic-error-strong) 95%, transparent); }
.kv.split .v { display: flex; gap: var(--ra-sp-2xs); }

/* == ds/atoms/badge.css == */
/* ------------------------------------------------------------
   .badge (design/PLAN.md §4.1): 22x22 r4 count (--ra-size-badge-sm since design-sync 2026-10-03). Intelligence's right
   column consumes this.
   ------------------------------------------------------------ */
/* Figma round 2026-10-09 (badge 380:801): caption/helper type (12/18, 0.1px), was body/small. */
.badge { font: var(--ts-caption-helper); letter-spacing: var(--ts-caption-helper-ls); display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--ra-size-badge-sm); height: var(--ra-size-badge-sm); padding: 0 var(--ra-sp-2xs);   /* design-sync 2026-10-03: 20 -> 22, the badge scale */
  border-radius: var(--ra-radius-xs);
  background: var(--ra-surface-card);
  color: var(--ra-text-secondary); }


/* S51 WP4 VARIANT `.count`: the counter chip (CD-245's hotkeys badge, Trading's CD-32/252, the
   screener's activity counts): caption-helper type in accent.primary-soft on surf-hi, r4. Hidden at zero
   by the consumer's `hidden` attribute (an author display rule would otherwise beat the UA's).
   Figma round 2026-10-09 (badge Count=yes 380:802): it hugs its text with 5 on every side, the text trimmed to
   its cap height (Figma leading trim), so the height is 1cap + 2 x 5 (19 at 12px) and the width the digits + 10. */
.badge.count { min-width: 0; height: calc(1cap + 2 * var(--ra-sp-5)); padding: 0 var(--ra-sp-5); box-sizing: border-box;
  background: var(--surf-hi); color: var(--ra-accent-primary-soft);
  font: var(--ts-caption-helper); letter-spacing: var(--ts-caption-helper-ls); font-variant-numeric: tabular-nums; }
.badge.count[hidden] { display: none; }
/* == ds/atoms/tone-tag.css == */
/* ------------------------------------------------------------
   .tone-tag (S51 WP4): the small tone tag the panels drew ~15 times over
   (trade side LONG/SHORT, news heat tiers, catalyst urgency, constraint
   flags): inline-block, mono 600 9px uppercase (--ts-tag), 1/5 inset, r2,
   elevated fill, tertiary text. The atom fixes geometry and type; each
   consumer's tones are its own modifiers (semantic colours that have no
   1:1 token), declared beside the consumer: `.tone-tag.rj-heat.hot`.
   Not `.tag`: css/account.css already owns a `.tag` preview label.
   Markup: A.toneTag({label, tone, cls}) -> <span class="tone-tag [tone]">
   ------------------------------------------------------------ */
.tone-tag { display: inline-block; font: var(--ts-tag); letter-spacing: var(--ts-tag-ls); text-transform: uppercase;
  padding: var(--ra-xsp-chip-y) var(--ra-sp-5); border-radius: var(--ra-radius-3xs);
  background: var(--ra-surface-card-elevated); color: var(--ra-text-tertiary); white-space: nowrap; }
/* == ds/atoms/button.css == */
/* ------------------------------------------------------------
   Buttons, Inputs, Pills, Segmented, Kbd, Tables below: token_lint still
   flags ~19 oklch() literals in this section (Amendment 2, PLAN.md §6,
   commit df9f04b8) — NOT migrated, deliberately. Every one of them reads
   its hue/chroma channel from var(--hue-pos|neg|info) or var(--bg-hue,
   --bg-chroma): that IS the design system, parametrized for Customization's
   accent/background personalisation, not off-system colour. They already
   carry the 2.0 palette live, for free, through the S1 alias layer
   (e.g. --hue-neg now resolves to 25.5) — hand-migrating them to fixed
   --ra-* tokens would undo that lever, the same mistake naively fixing
   candle colours would have been (§4.5). The linter's OKLCH_RE can't tell
   "parametrized system" from "off-system hardcode" (the L channel is
   always a literal digit even when H/C are var()s), so this file stays in
   migrationDebt with this comment as the declared reason — not silently,
   not because it's actually dirty.
   ------------------------------------------------------------ */
/* S19 §9.2/S19-R6 (OWNER: "Follow the design", app-wide) -- step F, its own
   commit, sequenced last, revertible alone. design/TOKENS.json
   radii.conventions counts r6 379 times against r4 60 -- the design
   system's OWN button radius is r6, reachable before this only by adding
   a modifier to every button that wants it (the literal-in-one-more-
   selector shape the owner's modularity rule forbids, spelled as a class
   instead of a hex). `--r-sm` itself is UNTOUCHED -- it still serves
   un-migrated files as a separate, smaller scale by design; only this
   rule's OWN reference moves to `--ra-radius-sm` (6px). */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ra-sp-xs);
  /* design-sync 2026-10-03 (Figma button, «Control heights» + «Icons inside buttons follow the button»):
     4 heights (xs 18 · sm 24 · md 32 · lg 44); the button sizes its icon through --ic-size (.ic-svg reads it). */
  height: var(--ra-size-control-sm);
  --ic-size: var(--ra-size-icon-lg);
  padding: 0 var(--ra-sp-md);
  border: 1px solid var(--line);
  border-radius: var(--ra-radius-sm);
  color: var(--text-2);
  background: var(--ra-k-bg-20-a60);
  font-size: var(--ra-fs-11);
  line-height: 1;
  /* The properties a button's states change, by name (it was `all`, PLAN_S31 §3.6): hover / on / press inks,
     disabled opacity, the chart-head chips' `.on` type (.cgrad), the focus ring, and `filter` -- the link
     tile's brightness hover and press (ds/organisms/panel.css), kept until that cue moves off filter. */
  transition: filter var(--mo-dur-control) ease, color var(--mo-dur-control) ease, /* motion-lint-ignore: filter: kept for the link tile's brightness hover, which lifts fill and ink together */
    background var(--mo-dur-control) ease, border-color var(--mo-dur-control) ease,
    opacity var(--mo-dur-control) ease, font-size var(--mo-dur-control) ease,
    font-weight var(--mo-dur-control) ease, letter-spacing var(--mo-dur-control) ease,
    outline var(--mo-dur-control) ease, outline-offset var(--mo-dur-control) ease;
  white-space: nowrap;
  cursor: pointer;
  font-family: inherit;
}
.btn:hover  { color: var(--text-1); border-color: var(--line-strong); background: var(--bg-2); }
.btn.ghost  { background: transparent; }
.btn.sm     { height: var(--ra-size-control-sm); font-size: var(--ra-fs-10); padding: 0 var(--ra-sp-sm); }
.btn.xs     { font: var(--ts-caption-helper); letter-spacing: var(--ts-caption-helper-ls); height: var(--ra-size-control-xs); padding: 0 var(--ra-sp-xs); --ic-size: var(--ra-size-icon-sm); }
/* Fitts & Miller 09.9 (Figma button 3103:1589, Size=xs): the 18 tall button takes clicks over a 24 tall band, an
   invisible box centred on it (the button stays its own size). */
.btn.xs { position: relative; }
.btn.xs::after { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: var(--ra-size-control-sm); transform: translateY(-50%); }
/* An icon-only xs button (18 wide) takes clicks over 24 x 24, the band centred both ways. */
.btn.xs.icon::after { left: 50%; right: auto; width: var(--ra-size-control-sm); transform: translate(-50%, -50%); }
/* design-sync 2026-10-03: Figma `default lg icon` (2629:53) -- the 44 size, 20 icon (chat send). */
.btn.lg     { height: var(--ra-size-control-lg); --ic-size: var(--ra-size-icon-xl); }
/* design-sync 2026-10-03 («Every icon has the same empty space»): an icon-only button is square at its own
   height, no side padding, icon centred -- xs 18 / sm and default 24 / lg 44. */
.btn.icon    { padding: 0; width: var(--ra-size-control-sm); }
.btn.xs.icon { width: var(--ra-size-control-xs); }
.btn.lg.icon { width: var(--ra-size-control-lg); }
/* design-sync 2026-10-03 (Figma button `caret`): a trailing chevron is the 10 px caret at every size. */
.btn > .ic[data-icon=chevron-down]:last-child:not(:first-child) { --ic-size: var(--ra-size-icon-xs); }
/* Fitts & Miller parity (Figma button 3103:1589, 2026-10-18): primary and solid labels are Semi Bold (sm 10, md 11);
   ghost, quiet and default stay Regular. */
.btn.primary {
  background: linear-gradient(180deg, var(--pos-bg), transparent);
  border-color: var(--ra-k-pos-55-12-a60);
  color: var(--pos);
  font-weight: 600;
}
/* Fitts & Miller 09.9 (button Kind=solid): the one filled primary of a view -- the accent fill, the on-accent ink, no
   border of its own. */
.btn.solid { background: var(--ra-accent-primary); border-color: transparent; color: var(--ra-text-on-accent); font-weight: 600; }
.btn.solid:hover { background: var(--ra-accent-primary-soft); border-color: transparent; color: var(--ra-text-on-accent); }
/* :hover glow rules removed outright (design/PLAN.md §3 D11), not left as
   empty blocks — .btn.primary/.danger already had no other hover
   treatment, so the whole rule was dead once its only declaration
   (a --glow-pos/--glow-neg reference, both dead tokens since P1's
   un-aliasing) was gone. */
.btn.danger {
  background: linear-gradient(180deg, var(--neg-bg), transparent);
  border-color: var(--ra-k-neg-55-14-a60);
  color: var(--neg);
  font-weight: 500;
}
/* Figma round 2026-10-09 (button Kind=primary State=disabled, 379:54): a disabled primary keeps its fill and draws its
   border and label in the axis grey (Figma `ax`); the kit's disabled opacity (button.variants.css) still applies. */
.btn.primary:disabled { border-color: var(--ch-ax); color: var(--ch-ax); }
/* Figma round 2026-10-09 (button Kind=delete, 2902:1848 / hover 2902:1862): a destructive action that rests as a
   default button -- the default fill over the danger gradient, the line border, text-2 ink -- and turns danger on hover. */
.btn.delete {
  background: linear-gradient(var(--ra-k-bg-20-a60), var(--ra-k-bg-20-a60)), linear-gradient(180deg, var(--neg-bg), transparent);
  font-weight: 500;
}
.btn.delete:hover {
  background: linear-gradient(180deg, var(--neg-bg), transparent);
  border-color: var(--ra-k-neg-55-14-a60);
  color: var(--neg);
}
.btn.info {
  background: linear-gradient(180deg, var(--info-bg), transparent);
  border-color: var(--ra-k-info-55-12-a60);
  color: var(--info);
}
/* T5/design-qa B4: "on" is a plain active-toggle state (chart's G/link
   button, etc.), not an info/notice state — reads as accent.primary
   (teal) in the frame, not blue. --hue-info was the wrong FAMILY of
   token, not a parametrized-formula question (Amendment 2 doesn't apply
   here: this isn't personalisation, it's picking the wrong semantic). Same
   12%-tint+1px-border ratio as the header's status-connected chip and the
   AI-agent button — the one system precedent for a small accent control. */
.btn.on {
  color: var(--ra-accent-primary);
  border-color: var(--ra-accent-primary);
  background: color-mix(in oklch, var(--ra-accent-primary) 12%, transparent);
}

/* S19 §3.5 -- three new modifiers, each completing a family that already
   exists rather than starting a new one. */
/* .btn.chip: the frame's seal/chip-tiny -- one component with six skins in
   this frame alone, 135 across the file. Type Heading/H6, radius r6.
   Declared AFTER .sm (above) so it wins the size on source order in this
   same file -- same specificity (0,2,0) as .btn.sm, later rule wins. */
.btn.chip { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); border-radius: var(--ra-radius-sm); }
/* .btn.warn: completes .btn.primary / .danger / .info, which already exist
   and already read --pos / --neg / --info. Semantic/Warning #F59E0B is the
   frame's OWN style name for 451:1093 -- the least-invention option. */
.btn.warn {
  background: var(--ra-surface-tape);
  border-color: var(--ra-semantic-warning);
  color: var(--ra-text-primary);
}
/* .btn.ghost.block: the frame's btn-add-condition (451:1181, 451:1228 --
   two nodes agreeing: no fill, #2A2F33, r6, h32, pad 8/12). */
.btn.ghost.block {
  align-self: stretch;
  justify-content: center;
  height: var(--ra-size-control-md);
  border-radius: var(--ra-radius-sm);
  padding: 0 var(--ra-sp-lg);
  border-style: solid;
  border-color: var(--ra-surface-card-elevated);
  font: var(--ts-body-small);
  letter-spacing: var(--ts-body-small-ls);
}

/* S19 §3.2 -- NEW, PROMOTED (not invented): the frame component the Done
   button must adopt is seal/button/ai-agent (451:1247 Sound Rules ·
   443:769 Replay · 321:2107 Base -- three nodes agreeing on r14, no fill,
   gradient stroke, pad 12/16, gap 8). That recipe already exists, correct,
   bound to one consumer's name, in panels/screener/screener.css's
   `.ai-agent-btn` (a masked ::before gradient ring, after two other
   techniques were tried and rejected live -- see that file's own comment).
   Geometry copied verbatim; `.ai-agent-btn` keeps its OWN label rule
   (Body/Medium, correct against 321:2108) because the three frame nodes
   DISAGREE on the label -- Sound Rules' node is Button/Medium, the other
   two are Body/Medium. `.btn.cta` takes Sound Rules' reading. Do not
   "unify" the two into a contradiction; it is the audit's divergence, not
   an oversight here.
   ZERO CONSUMERS as of this commit -- nothing renders differently yet.
   dev-b (step C) adds `cta` to the Done button and to `.ai-agent-btn`'s
   own element, THEN deletes the now-duplicated declarations from
   screener.css, in that order, in the same commit (PLAN_S19.md §3.2.1). */
.btn.cta {
  position: relative;
  height: var(--ra-size-control-lg);   /* design-sync 2026-10-03: Figma cta 44 (was 48) */
  --ic-size: var(--ra-size-icon-xl);
  padding: 0 var(--ra-sp-xl);
  gap: var(--ra-sp-sm);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--ra-radius-card);
  font: var(--ts-button-medium);
  letter-spacing: var(--ts-button-medium-ls);
  color: var(--ra-text-primary);
}
.btn.cta::before {
  content: "";
  position: absolute; inset: 0; border-radius: inherit; padding: var(--ra-xsp-border-inset);
  background: var(--ra-gradient-ai-button);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none;
}
.btn.cta:hover  { background: color-mix(in oklch, var(--ra-accent-primary) 12%, transparent); }
.btn.cta:active { background: color-mix(in oklch, var(--ra-accent-primary) 20%, transparent); }


/* S51 WP4 VARIANT `.quiet`: a borderless, fill-less button -- the panels' text and glyph triggers
   (a range label, a reset link, a drawer close). The consumer's variant carries its own type and inset. */
.btn.quiet { border: 0; background: transparent; }
.btn.quiet:hover { background: transparent; border-color: transparent; }
/* == ds/atoms/input.css == */
/* ------------------------------------------------------------
   Inputs
   ------------------------------------------------------------ */
/* S19 §3.4/H2: `background` here is the LONGHAND `background-color`, never
   the shorthand -- the app-wide half of H2 is exactly a shorthand at higher
   specificity ([data-theme="light"] .input, .select, widgets.css) silently
   resetting `background-image` to `none` on every `.select` in the
   product. Kept longhand here too, in the KIT's own base rule, so a future
   specificity tie can never repeat that on this line. */
/* S19 §9.2/S19-R6 -- step F, same repoint as `.btn` above, same reason. */
.input, .select {
  height: var(--ra-size-control-sm);   /* design-sync 2026-10-03 («Control heights»): every input and select 24 */
  padding: 0 var(--ra-sp-md);
  border: 1px solid var(--line);
  border-radius: var(--ra-radius-sm);
  color: var(--text-1);
  background-color: var(--ra-k-bg-12);
  font-size: var(--ra-fs-11);
  outline: none;
  transition: border-color var(--mo-dur-control);
  font-family: inherit;
}
/* The global :focus-visible outline (globals.css) never reaches a field: `outline: none` above wins it (same
   specificity, later in the manifest), so a field's focus is its border. */
.input::placeholder, .select::placeholder {
   color: var(--text-2);
}
.select:focus {
  border-color: var(--ra-k-info-55-12-a70);
}
/* Platform input State=focus (owner, 2026-10-18: every text field): a 2 px info border, the 1 px border and a 1 px
   inset ring, on the field or on the `.input` box that draws a bare one (text+icon, the sound rules' values) while it
   holds the focus. A bare input inside any other container keeps that container's face. */
.input:not(.bare):focus-within { border-color: var(--info); box-shadow: inset 0 0 0 1px var(--info); }
.input.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
/* Fill: Platform select 246:96 (every state, and every select instance in the Organisms and Screens files) is
   surface/card, #23282C (2026-10-18; S19's --ra-surface-field came from the older Session Dropdown frames, #1A1A1A).
   S19 §3.4/§2.1: border reads --ra-border-strong, not
   --ra-surface-chip -- both hold the identical dark hex (#1A2332, see
   tokens.css), but only --ra-border-strong already carries a verified
   light-theme repoint (widgets.css:1191, --line-strong); --ra-surface-chip
   has none. Binding a BORDER role to the surface-named token would repeat
   the exact role/name mismatch §2.1 named and fixed for --ra-surface-field
   itself (a light-invisible border is not a shape this repair should add
   back) -- same dark pixel, one fewer unmapped token consumed. Caret via
   --ra-caret-select (tokens.css §2.2): the `%23` literal that used to live
   here is gone, and the caret now has a light value for the first time. */
.select {
  appearance: none; -webkit-appearance: none;
  /* Figma round 2026-10-16 (select 246:96, every state): the value is h6/table-header, 11 Medium / 16 / 0.2. */
  font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls);
  padding-right: var(--ra-sp-2xl);
  background-color: var(--ra-surface-card);
  border-color: var(--ra-border-strong);
  background-image: var(--ra-caret-select);
  background-repeat: no-repeat;
  background-position: right var(--ra-xsp-9) center;   /* Figma round 2026-10-09 (select 246:96): the 10 px caret box sits 9 from the edge */
}


/* S51 WP4 VARIANT `.bare`: an input drawn by its container (a search pill, an editable section title):
   no box of its own -- the container is the visible field. */
.input.bare { height: auto; padding: 0; border: 0; border-radius: 0; background-color: transparent; transition: initial; }
/* == ds/atoms/pill.css == */
/* ------------------------------------------------------------
   Pills
   ------------------------------------------------------------ */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--ra-sp-xs);
  height: var(--ra-size-badge-sm);   /* a label, not a control: the badge scale (design-sync 2026-10-03) */
  padding: 0 var(--ra-sp-md);
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: var(--ra-fs-10);
  color: var(--text-2);
  background: var(--ra-k-bg-18-a80);
  white-space: nowrap;
}
.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-2); }
/* T5/design-qa B9: the header's message-rate pill ("442 /s") frame-samples
   accent.primary-soft (#7EE0CD) for its dot; the app showed --info blue.
   The colour lived as an INLINE style on the dot span in index.html
   (dev-a's file), which beat any class rule regardless of specificity —
   dev-a fixed the inline value itself (72786dc8), so the plain class rule
   below is enough now; the !important that used to be here is gone.
   Also measured against 321:120 directly (screenshot, not just the §3
   prose): the dot's edge is crisp, no halo — the box-shadow glow this rule
   used to add doesn't exist in the frame. That measurement covered ONE
   instance and deliberately did not generalise to .pill.pos/.neg/.info's
   own glows (58 box-shadow occurrences deferred as a class in eafc4484).
   design/PLAN.md §3 D11 now resolves the deferral system-wide (0 effects
   across 5,639 paints on all five frames) — the .pos glow below is
   deleted on that stronger, whole-system evidence, not re-measured
   per-instance. */
.pill.num .dot { background: var(--ra-accent-primary-soft); }
.pill.pos { color: var(--pos); border-color: var(--ra-k-pos-55-12-a50); }
.pill.pos .dot { background: var(--pos); }
.pill.neg { color: var(--neg); border-color: var(--ra-k-neg-55-14-a50); }
.pill.neg .dot { background: var(--neg); }
.pill.info { color: var(--info); border-color: var(--ra-k-info-55-12-a55); background: var(--info-bg); }
.pill.info .dot { background: var(--info); }
/* Amendment 2 (PLAN.md §6, df9f04b8): hue 85 here is a literal, not a
   var() channel like its siblings above — real debt, migrated. */
.pill.warn { color: var(--warn); border-color: color-mix(in oklch, var(--ra-semantic-warning) 50%, transparent); }
/* CD-749/CD-758 (S11-R2a cl.4, S11.4.2, S11-R17): the "live" pill modifier.
   The D3-Trades pill (screener.css:284, deleted in the same programme) was
   already frame-exact on shape — seal/chip 45x24 r6, no fill — and is the
   one the NEWS pill unifies toward (S11-R2a: "unify toward the one that
   already matches the frame; do not design a third"), not a third
   implementation and not a change to this primitive's own defaults.
   S11-R17 node-read 321:796/797/798 itself, after H3's arithmetic (built
   on the APP's padding/gap) was refuted: padL 8 - dot 6 - gap 4 - label 22
   - padR 5 = 45 exactly — asymmetric padding and a 4px gap, not the base
   .pill's symmetric "0 8px"/gap:6. The dot is pinned (`flex: 0 0 6px`) so
   it can never be the flex-shrink victim CD-758 found it as — that shrink
   happened under the base .pill's own gap:6 + padding:0 8px, which left
   only 27px of content for 6+6+22=34. Declared here, beside the primitive,
   consumed today by the D3 live row alone (.d3-live-row, screener-d3.html):
   the NEWS head moved to the plain Tone=pos pill with its dot in the
   2026-10-09 Figma round (screener-news.html, fig-scr-small).
   design-sync 2026-10-03 (Figma pill Live=yes 380:9, «live pill 24»): 24 (control-sm), no vertical padding,
   content centred and hugged with 8 each side -- the fixed 45 clipped «EN VIVO». */
.pill.live {
  gap: var(--ra-sp-2xs);
  justify-content: center;
  height: var(--ra-size-control-sm);
  padding: 0 var(--ra-sp-sm);
  border-radius: var(--ra-radius-sm);
  background: transparent;
  border-color: var(--ra-surface-card-elevated);
  color: var(--ra-accent-primary-soft);
}
.pill.live .dot { flex: 0 0 6px; background: var(--ra-accent-primary-soft); }
.pill.live span:not(.dot) { font: var(--ts-label-badge); letter-spacing: var(--ts-label-badge-ls); }

/* S19 §3.5/§3.7 -- NEW: SND-11/SND-34's shape half (the sound tag). Shape
   only, h24, r999, icon 10, 1px stroke, type Heading/H6. It takes NO
   colour by design (PLAN_S19.md §2.4/§9.1c): the frame contradicts itself
   on the tag's green (#4CAF50 tag vs #00E676 picker, two greens for one
   concept), and the app colour is not a literal either -- it comes from an
   8-colour per-sound palette (tokens.css --ra-rule-*) that a fixed frame
   colour would collapse. Painting from currentColor lets the per-rule
   value supply the colour without this class ever adjudicating which
   green is right; the colour half stays escalated, not fixed here. */
.tag-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--ra-sp-2xs);
  height: var(--ra-size-badge-md);
  padding: 0 var(--ra-sp-sm);
  border: 1px solid currentColor;
  border-radius: var(--ra-radius-full);
  background: color-mix(in oklch, currentColor 12%, transparent);
  font: var(--ts-h6-table-header);
  letter-spacing: var(--ts-h6-table-header-ls);
}
.tag-pill svg, .tag-pill img { width: 10px; height: 10px; flex: none; }

/* == ds/organisms/modal.css == */
/* ------------------------------------------------------------
   Modal shell — S11-R18 (OWNER)
   ------------------------------------------------------------ */
/* S11-R18 (OWNER, escalated per S11-R13): ONE shared shell for all six
   MODAL_SPEC.md §2 modal nodes, at radius 14 + 1px border — not the two
   the frame draws (four at radius 14 + border, two at radius 6 + no
   stroke). The owner took the frame's majority reading together with the
   modularity directive ("the team must develop the frontend modular so we
   can reuse elements everywhere") and ruled ONE shell: the same argument
   that makes seven header-action signatures a defect (CD-757) makes two
   modal shells one. `--ra-radius-card` (14px, TOKENS.json
   radii.conventions.card) and `--line-strong` (`--ra-border-strong`
   #1A2332, MODAL_SPEC §2's stroke on every bordered shell) already exist
   as tokens — this declares their COMBINATION as the modal shell contract
   once, rather than each consumer re-picking the same two values.
   KNOWN, ACCEPTED DEVIATION from the frame on two nodes: `405:193` (Modes)
   and `443:979` (TFs) draw radius 6 + no stroke. Recorded HERE so a future
   audit reads a decision, not a miss — re-measuring those two nodes will
   find a real difference from what the app now draws, on purpose.
   Consumers (the pills contract: declare once, never open a consumer):
   this file's own `.ui-dd:has(> .col-popover)` and `.cind-dd`
   (css/widgets.css, CD-748/CD-755 — geometry acceptance for both is
   UNCHANGED, radius/border move neither a width nor a height) and dev-A's
   `.cmode-pop` (screener.css:1213, CD-752/753, Modes + TFs — not touched
   here). The modal HEADER (`Frame 19`) is still not built (CD-760); unlike
   this shell it WOULD move the widths/heights those rows close on.
   ADDED S17 (`screener.css`'s `.rpl-pop`, Replay Control Panel, 443:707):
   this fourth pop-up had silently declared its own shell (own literal
   fill, `--line`, 6px radius) and never joined the S11-R18 contract —
   found by `POPUPS_S17B.md` §3.1, ruled by the owner (`S17-R21b`, "all
   pop ups should be consistent") to join like the rest.
   ADDED S17, SECOND PASS (`screener.css`'s `.snd-pop`, Sound Rules,
   448:1631): the fifth and last pop-up. Task #7 first built this one to
   448:1631's OWN frame values (`#060A10`/`#1E252D`) on a standing task
   instruction that read `S17-R21b` as excluding it — flagged as a live
   conflict, not silently resolved either way, twice, before the lead's
   `S17-R30` settled it: `S17-R21b` STANDS for all five, 448:1631's own
   fill/stroke are the REJECTED deviation this recipe already speaks for,
   not a sixth exception. All five S17 pop-ups (`.cmode-pop`/`.ctf-pop`,
   `.cind-dd`, `.rpl-pop`, `.snd-pop`) now consume this token pair — the
   set is complete, "declare once" now covers the whole population, not
   four of five.

   *** A5.7 SUPERSESSION RECORD — READ THIS BEFORE "RESTORING" RADIUS 6 ***
   `DESIGN_BRIEF` Addendum S17-B (`design/s17/POPUPS_S17B.md` §2.1/§2.2)
   reads the two accepted deviations above (`405:193`/`443:979`, radius 6
   + no stroke) as superseded BACK toward the frame — i.e. as if this app
   should now move TO radius 6. `S17-R21c` (`design/s17/LEAD_RULINGS.md`)
   is the owner's later, explicit ruling and it goes the OTHER way: the
   frame is not self-consistent across all five S17 pop-up nodes (radius
   6 ×2, 14 ×3; stroke none ×2, `#1A2332` ×2, `#1E252D` ×1) and "adjust to
   the design" + "all pop ups consistent" resolves to the PLURALITY on
   every axis — radius 14 / `1px #1A2332` / `#0D1319` — which is EXACTLY
   this declaration, unchanged. **The app's 14 IS the unified recipe and
   STAYS; the frame's 6 at those two nodes is the drift, not the target.**
   `.cmode-pop` needed NO radius change for S17. A future session
   re-reading Addendum S17-B's prose in isolation will conclude the
   opposite of what was ruled — the ruling is the current one, the
   addendum's prose is the stale one. */
:root {
  --s11-modal-shell-radius: var(--ra-radius-card);
  --s11-modal-shell-border: 1px solid var(--line-strong);
}

/* == ds/molecules/pop-head.css == */
/* ------------------------------------------------------------
   Pop-up header ("Frame 19") -- S17 #6/#16
   ------------------------------------------------------------ */
/* ELEMENT_DIFF_S17C.md §6.3: the frame draws the SAME header on every one
   of the five S17 pop-ups -- an uppercase Space Grotesk title on the left
   and a filled 22x22 r=6 close chip on the right, at the shell's own
   inset -- and four of the five apps drew none at all (MOD-01, TF-01,
   IND-01, RPL-01; only Sound Rules already had a title, in the wrong
   typeface, SND-01/task #7's territory). This is `S11-R18`'s own
   long-open note that "the modal HEADER (Frame 19) is still not built" --
   built here, once, declared at the same site as the shell it sits
   inside. Consumers: dev-a's screener.js (Modes/TFs/Replay markup, task
   #16) and dev-b's features/chart/indicators-dd.js (Indicators) -- one rule,
   four pop-ups, the "declare once, never open a consumer" convention
   this file's own modal-shell block already established. `.snd-pop`
   (Sound Rules) is NOT a consumer: its header is its own, ruled
   separately (task #7). */
/* Fitts & Miller 09.9 and the parity pass (Figma pop-head 339:9, 2026-10-17/18): the title fills the row and what
   follows it -- the optional status pill (`.pop-head-status`, a ticket's mode), the optional 24 gear
   (`.pop-head-settings`), the 24 close -- packs 8 apart at the right. */
.pop-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ra-sp-sm);
  height: var(--ra-size-control-sm); flex: none;
}
.pop-head > .pop-head-title { flex: 1; min-width: 0; }
/* S19 §3.1 -- REPAIR: was a hand-written 15/24/500/Space-Grotesk block that
   happened to already match the frame's own numbers; now it READS them off
   the shared type scale (tokens.css --ts-h3-card-title) so a future size
   change has one place to land instead of three (the census measured the
   same close chip's title at three different inherited sizes because
   nothing here was absolute). text-transform stays HERE, not in the token
   -- only this one style carries Figma's textCase:UPPER. */
.pop-head-title { font: var(--ts-h3-card-title); letter-spacing: var(--ts-h3-card-title-ls); text-transform: uppercase;
  color: var(--ra-text-primary); }
/* design-sync 2026-10-03 (Figma pop-head 339:9 `pop-head-close`): an icon-only 24 square like the panel-head
   tiles -- fill --ra-surface-card, r6, a 16 `close` icon in --ra-text-axis. */
.pop-head-close, .pop-head-settings {
  width: var(--ra-size-control-sm); height: var(--ra-size-control-sm); padding: 0; flex: none;
  background: var(--ra-surface-card); border: 0; border-radius: var(--ra-radius-sm); color: var(--ra-text-axis); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.pop-head-close:hover, .pop-head-settings:hover { color: var(--text-1); }
.pop-head-status { flex: none; }

/* == ds/atoms/segmented.css == */
/* ------------------------------------------------------------
   Segmented control
   ------------------------------------------------------------ */
/* Platform segmented 255:7 `Kind=default` (2026-10-18; it counts its stroke in the layout): tray 24, the 1 px border and
   then 2 inside, so the pieces are 18 (the chart's timeframe controls; 255:2 had drawn them 20). */
.segmented {
  display: inline-flex;
  padding: var(--ra-sp-3xs);
  background: var(--bg-1);
  border: 1px solid var(--line);
  /* design/PLAN.md §4.1 "restyle of the existing .segmented": container r6
     (was --r-sm, the legacy 4px scale). Cell radius below flips the other
     way, r4 — the two legacy values were swapped relative to the 2.0 kit. */
  border-radius: var(--ra-radius-sm);
  gap: var(--ra-sp-3xs);
  height: var(--ra-size-control-sm);
}
.segmented button {
  max-height: 100%;
  padding: 0 var(--ra-sp-md);
  font-size: var(--ra-fs-10);
  color: var(--text-2);
  border-radius: var(--ra-radius-xs);   /* r4 — see the container comment above */
  /* By name (it was `all`, PLAN_S31 §3.6). `box-shadow` stays for the light theme's `.on` ring
     (ds/themes/light.css), until that ring moves to an opacity-faded layer. */
  transition: box-shadow var(--mo-dur-control), color var(--mo-dur-control), background var(--mo-dur-control), /* motion-lint-ignore: box-shadow: kept for the light theme's on ring (ds/themes/light.css) */
    border-color var(--mo-dur-control), font-weight var(--mo-dur-control),
    outline var(--mo-dur-control), outline-offset var(--mo-dur-control);
  cursor: pointer;
  background: var(--ra-surface-card);
  border: 1px solid transparent;
  font-family: inherit;
}
.segmented button:hover { color: var(--text-1); }
/* Fitts & Miller 04 / 08 (Figma segmented 255:7, every default cell's «hit area · 24»): a cell takes clicks over the
   track's whole height and up to the middle of the 2 px gap on each side, an invisible box past its own edges (the
   track's inset and border, the cell's border), so no click between two options misses both. */
.segmented:not(.dx):not(.soft):not(.chips) button { position: relative; }
.segmented:not(.dx):not(.soft):not(.chips) button::after { content: ""; position: absolute;
  inset: calc(-1 * var(--ra-sp-3xs) - 2 * var(--ra-border-hairline-w)) calc(-1 * var(--ra-sp-3xs) / 2 - var(--ra-border-hairline-w)); }
/* T5/design-qa B3 → QA F3 (session 2, superseding the finding below): a
   fresh, per-context pass confirmed .segmented button.on serves (at
   least) FOUR frame contexts, and the base rule's border/text was right
   for none of them as measured live before — it was the chart's, and
   PLAN.md §4.1's "accent.secondary" claim (parked pending verification
   in P1) turned out to be correct for that context after all. Fill
   (surface-tape) is unchanged everywhere; only border+text colour vary
   per context below. */
.segmented button.on {
  color: var(--ra-accent-secondary);
  background: var(--ra-surface-tape);
  border: 1px solid var(--ra-accent-secondary);
  font-weight: 600;
}
/* Per-context overrides — QA F3, each verified against its own node, not
   generalised from another context (AGENTS.md's ".segmented button.on"
   lesson: same class, several frame components, never assume one covers
   the rest):
   The scanners' price filters and the Ganadoras exchange / session pickers are dx controls since Fitts & Miller 04
   (the dx variant below; their ID-scoped chip skins went with it). The footer's RTH / ETH is the chips kind since
   Fitts & Miller 14 (its 2 px inset and current cell: ds/organisms/app-footer.css). */
/* CD-133 (A2): `.nb.h`/`.nb.w` (widgets.css) set their own semantic
   border-color unconditionally, winning over `.snd-news-cat`'s neutral
   one at equal specificity by source order — so the OFF (inactive) chip
   showed the HOT/WARM hue as its border instead of the frame's neutral
   ghost-outline. `.nb.c` never set border-color, which is why COLD was
   already correct. Fixed at the `.off` state specifically (css/widgets.css,
   `.snd-news-cat.off`) rather than here, to stay in the file that already
   owns `.nb`/`.snd-news-cat`. */


/* S51 WP4 VARIANTS. `.dx` is the Review dashboard recipe (review.css A10, "ONE recipe, built once"): an
   elevated 24-tall track with no stroke, 20-tall cells, label-badge type, the active cell filled
   surface-tape with an accent-secondary stroke; `.h28` (now the same 24 box); `.wide` its 16px cell inset (GEX,
   News). `.soft` is the Stats recipe (stats.css G47): a surface-tape track, button-small type, the active
   cell filled surface-card-elevated. Each resets what the base rule paints that it does not. */
/* Fitts & Miller parity (Figma segmented 255:7, 2026-10-18): a dx cell is as tall as its 24 track -- the 2 px inset sits
   at the sides only -- so each option is clickable over the control's full height and touches its neighbours. */
.segmented.dx { height: var(--ra-size-control-sm); padding: 0 var(--ra-sp-3xs); align-items: center; gap: normal; background: var(--ra-surface-card-elevated); border: 0; }
.segmented.dx button { display: inline-flex; align-items: center; justify-content: center; height: 100%; max-height: none; padding: 0 var(--ra-sp-sm);
  background: none; color: var(--ra-text-secondary); font: var(--ts-label-badge); letter-spacing: var(--ts-label-badge-ls);
  transition: background var(--mo-dur-hover-in), color var(--mo-dur-hover-in), border-color var(--mo-dur-hover-in); }
.segmented.dx button:hover { color: var(--ra-text-secondary); }
.segmented.dx button.on { background: var(--ra-surface-tape); border-color: var(--ra-accent-secondary); color: var(--ra-text-primary); font-weight: 500; }
/* design-sync 2026-10-03 («Control heights»): Figma's `dx H28=yes` is 24 now -- `.h28` draws the plain dx box. */
.segmented.dx.wide button { padding: 0 var(--ra-sp-xl); }

/* design-sync 2026-10-03: Figma `soft` (255:6) is a 32 tray, 2 inset, its pieces filling it. */
.segmented.soft { height: var(--ra-size-control-md); padding: var(--ra-sp-3xs); background: var(--ra-surface-tape); border: 0; }
.segmented.soft button { padding: var(--ra-sp-2xs) var(--ra-sp-md); font: var(--ts-button-small); letter-spacing: var(--ts-button-small-ls);
  color: var(--ra-text-secondary); background: transparent; white-space: nowrap; transition: color var(--mo-dur-hover-out); }
.segmented.soft button.on { background: var(--ra-surface-card-elevated); color: var(--ra-text-primary); border-color: var(--ra-accent-secondary); font-weight: 500; }
.segmented.soft button:hover:not(.on), .segmented.soft button:focus-visible:not(.on) { color: var(--ra-text-primary); transition-duration: var(--mo-dur-hover-in); }

/* Figma round 2026-10-16 (Platform segmented 818:6 `Kind=chips`, the Review journal filter bar): tiny chips on an
   elevated track, r6, no inset -- the track shows only in the 4 px gaps and at the rounded ends. A chip is 24 tall with
   a card fill, an elevated stroke and h6 type; the on chip is tape-filled with an accent-secondary stroke and primary ink. */
.segmented.chips { padding: 0; gap: var(--ra-sp-2xs); background: var(--ra-surface-card-elevated); border: 0; }
.segmented.chips button { max-height: none; padding: 0 var(--ra-sp-7); font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls);
  color: var(--ra-text-secondary); background: var(--ra-surface-card); border-color: var(--ra-surface-card-elevated); border-radius: var(--ra-radius-sm); }
.segmented.chips button.on { color: var(--ra-text-primary); background: var(--ra-surface-tape); border-color: var(--ra-accent-secondary); font-weight: 500; }
.segmented.chips button:not(.on):hover { color: var(--ra-text-primary); }
/* == ds/atoms/kbd.css == */
/* ------------------------------------------------------------
   Keyboard hint
   ------------------------------------------------------------ */
.kbd {
  font-family: var(--font-mono);
  font-size: var(--ra-fs-10);
  padding: var(--ra-xsp-chip-y) var(--ra-sp-5);
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--text-2);
  background: var(--ra-k-bg-14);
}

/* == ds/molecules/table.css == */
/* ------------------------------------------------------------
   Tables
   ------------------------------------------------------------ */
table.data { font: var(--ts-label-form); letter-spacing: var(--ts-label-form-ls); width: 100%;
  border-collapse: collapse;
  table-layout: auto; }
/* Fitts & Miller 09.9 (Figma table/th 833:35): a header at rest is text-secondary. */
table.data th { font: var(--ts-h6-table-header); text-align: left;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: var(--ra-ls-em-08);
  /* T5/design-qa B5: long labels ("SINCE NEWS", "TPS MAX") wrapped to two
     lines and grew the thead. DESIGN_SPEC §1.6 keeps thead one row (563x28)
     and truncates instead. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  padding: var(--ra-sp-xs) var(--ra-sp-sm);
  border-bottom: 1px solid var(--line);
  background: var(--ra-k-bg-16-a90);
  position: sticky; top: 0; z-index: 1;
  user-select: none; }
table.data th[data-k] { cursor: pointer; }
table.data td {
  padding: 0 var(--ra-sp-sm);
  height: var(--row);
  /* CD-122 (S10-T4): frame node 321:303 "seal/dec/hairline" is a solid
     opaque colour (verified at the node, Figma MCP) equal to
     --ra-surface-card / --ra-border-card -- the app was painting a
     translucent oklch composite at 35 percent alpha instead, computing
     to a visibly darker, different colour (design/COMPONENT_DIFF.md:608).
     --ra-border-card carries the frame's exact value and is the
     border-family token, over --ra-surface-card (same value,
     surface-family, wrong semantic for a border-bottom). Shared rule,
     not a scoped override: `table.data` is itself the base component
     (Stats/Trading/Intelligence/Screener all consume it), so this is
     the correct altitude to fix. */
  border-bottom: 1px solid var(--ra-border-card);
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Tabular data is mono by default — every panel showing numbers in a
     table (prices, qty, P&L, durations, dates) reads cleaner with
     fixed-width digits. Stats, Trading, Intelligence already opted in
     locally; this lifts that decision to the global primitive. Screener
     cells that need a different look use cell-level classes (.num is
     already mono, .tk-name overrides etc.). */
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
/* Right-align numeric cells consistently across panels — opt-in via
   class="num" or the legacy class="r" on the <th> / <td>. Both names
   are accepted so existing markup (Stats / Trading use .num,
   Intelligence uses .r in ~96 places) doesn't need bulk renames. */
table.data td.title { font: var(--ts-h5-widget-title); letter-spacing: var(--ts-h5-widget-title-ls); display:flex;align-items:center;gap:var(--ra-sp-md); }
table.data th.num, table.data td.num,
table.data th.r,   table.data td.r { text-align: right; }
table.data tr:not(:first-child) { cursor: pointer; }
table.data tr:not(:first-child):hover td { background: var(--ra-k-bg-23-a50); }
table.data tr.sel td { background: var(--ra-k-info-30-06-a18); }

/* `table.data td` sets color:var(--text-2) at specificity (0,1,2), which beats a
   bare `.pos-c`/`.neg-c` (0,1,0) — so P&L / change cells inside data tables
   (Closed P&L side+RPNL, positions uPnL, gainers chg/bid/ask) rendered grey
   instead of green/red. The table-scoped selector restores the intended colour;
   `.warn-c` (ds/atoms/ink.css) gets the same for the gainers' Borrow column. */
.pos-c, table.data td.pos-c { color: var(--pos); }
.neg-c, table.data td.neg-c { color: var(--neg); }
table.data td.warn-c { color: var(--warn); }
.neu-c { color: var(--text-2); }

/* flashpos/flashneg + .flash-pos/.flash-neg DELETED (S31 UX-R12): grep for
   flash-pos|flash-neg across the whole frontend [probed, PLAN_S31 §1.5]
   returned ONLY these four lines — no JS anywhere ever added either class.
   Dead since before this session; not a motion regression. */

/* == ds/organisms/ui-kit.css == */
/* ------------------------------------------------------------
   PV2.ui kit (core/ui.js) — modal / toast / dropdown.
   Tokens only, so the [data-theme="light"] token flip in widgets.css
   restyles these with zero overrides. Visuals consolidated from the
   previous per-panel implementations (.footer-dd-menu / .cind-dd /
   #userPop were near-identical already).
   ------------------------------------------------------------ */
/* S31 A4 (N5, S31-R4): scrim ships the reference's blur+dim, tokenized
   (tokens/tokens.css §1f). Amendment 2's declared-decision precedent
   (achromatic literal, no TOKENS.json group for a modal scrim) is now
   `--ra-scrim-modal` instead of the bare oklch() this rule carried before
   — same reasoning, promoted to a token so the S31-R4 fallback flip is a
   two-token edit in tokens.css, not a hunt through components.css.
   Entrance/exit: default state is fully transparent + unblurred; `.on`
   (added one frame after mount by js/core/ui.js so the browser has a
   "from" value to transition away from) carries the real values. Backdrop
   click is interactive from frame 1 regardless (no pointer-events gate) —
   only the DISMISS is guarded (ui.js), per UX-REVIEW-motion R4(a). The
   `motion_lint:allow-filter` marker below is the ONE authorised forbidden-
   property exception this whole session (§3.6) — backdrop-filter on this
   element only. */
.ui-modal-back {
  position: fixed; inset: 0; z-index: 450;
  background-color: transparent;
  backdrop-filter: blur(0); -webkit-backdrop-filter: blur(0); /* motion_lint:allow-filter */
  transition: background-color var(--mo-dur-scrim) var(--mo-ease-scrim),
              backdrop-filter var(--mo-dur-blur) var(--mo-ease-blur); /* motion_lint:allow-filter */
  display: flex; align-items: center; justify-content: center;
}
.ui-modal-back.exiting { transition-duration: var(--mo-dur-exit); }
.ui-modal-back.on {
  background-color: var(--mo-scrim-bg);
  backdrop-filter: blur(var(--mo-scrim-blur)); -webkit-backdrop-filter: blur(var(--mo-scrim-blur)); /* motion_lint:allow-filter */
}
.ui-modal {
  background: var(--bg-1); border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  max-width: min(92vw, 640px); max-height: 84vh;
  display: flex; flex-direction: column; padding: var(--sp-4);
  outline: none;
  /* UX-REVIEW-motion R4(b): opacity + scale only, NEVER a translation — no
     target may move under the cursor on a screen that places orders.
     MOTION_REF §2.2: scale is not detectable in the reference (could not
     be confirmed OR excluded); interactive from frame 1 regardless, since
     this is decoration on an already-live surface, not a gate on it. */
  opacity: 0; transform: scale(0.98);
  transition: opacity var(--mo-dur-view) var(--mo-ease-settle),
              transform var(--mo-dur-view) var(--mo-ease-settle);
}
.ui-modal-back.on .ui-modal { opacity: 1; transform: none; }
.ui-modal-hd { font-weight: 600; color: var(--text-1); margin-bottom: var(--ra-sp-3xs); }
/* A titled modal() sits on the pop-head (Fitts & Miller 09.9); its title keeps the plain title row's type. */
.ui-modal > .ui-modalhead2 > .ui-modalhead2-title { font-size: var(--ra-fs-12); line-height: normal; letter-spacing: var(--ra-ls-em-01); }
.ui-modal > .ui-modalhead2 { flex: none; margin-bottom: var(--ra-sp-sm); }   /* modal({head}): Figma pop-head, 8 above the body */
.ui-modal-sub { font-size: var(--ra-fs-11); color: var(--text-2); margin-bottom: var(--sp-2); }
.ui-modal-body { overflow: auto; min-height: 0; color: var(--text-2); padding: var(--sp-2) 0; }
.ui-modal-ft { display: flex; gap: var(--sp-2); justify-content: flex-end; padding-top: var(--sp-3); }

#ui-toasts {
  position: fixed; right: 14px; bottom: 40px; z-index: 500;
  display: flex; flex-direction: column; gap: var(--ra-sp-xs); pointer-events: none;
}
/* S19 §9.2/S19-R6, task #20: task #18's exclusion reasoning ("never adjacent
   to anything R6 touched") was MEASURED FALSE by design-qa -- a real toast
   fired through PV2.ui.toast sits in the footer 5px from two 6px
   .status-chip elements, with a 6px .footer-btn at 119px and four 6px
   .btn.ghost.sm at 135px, all within 200px. Repointed to match its actual
   neighbours, not the reasoning that assumed them away. */
/* ── Jump pill (owner 2026-09-09) ─────────────────────────────────────────
   The control a scrolled-away list shows to jump back: the news panel's
   "N new headlines · feed paused while you read" (NEWS_SPEC_S46 §2) and the
   GEX ladder's "Back to price". The owner asked for one look in both, so
   the SKIN lives here once — a quiet card-coloured pill with a leading
   chevron that points the way the click travels — and each panel keeps only
   its own geometry and sticky edge (news: 312x34 at the list's top;
   gex: content-width, centred at the body's bottom). */
.ui-jump-pill {
  display: flex; align-items: center; justify-content: center; gap: var(--ra-sp-xs);
  background: var(--ra-surface-card); color: var(--text-2);
  border: 0; border-bottom: 1px solid var(--ra-border-hairline);
  border-radius: var(--ra-radius-xs);
  font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls);
  cursor: pointer;
}
.ui-jump-pill .ic { flex-shrink: 0; color: var(--text-2); }

/* design-sync 2026-10-03 (Figma Platform «Kit · paneles» › toast): 386 wide, hugs its height; state icon + (title,
   text, action) + close; a 1px border all round (the 3px left accent is gone), surface/panel, shadow/popover. Every
   toast now carries the close button, so each opts back in to clicks (`#ui-toasts` itself stays pointer-events:none). */
.ui-toast {
  display: flex; align-items: flex-start; gap: var(--ra-sp-sm); width: 386px; padding: var(--ra-sp-lg);
  background: var(--ra-surface-panel); border: var(--ra-border-hairline-w) solid var(--line-strong);
  border-radius: var(--ra-radius-sm); box-shadow: var(--ra-shadow-popover); pointer-events: auto;
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--mo-dur-toast), transform var(--mo-dur-toast);
}
.ui-toast.on { opacity: 1; transform: none; }
.ui-toast.info { --toast-tone: var(--ra-semantic-info); }
.ui-toast.success { --toast-tone: var(--ra-semantic-success); }
.ui-toast.warning { --toast-tone: var(--ra-semantic-warning); }
.ui-toast.error { --toast-tone: var(--ra-semantic-error); }
.ui-toast > .ic { flex: none; display: flex; color: var(--toast-tone); }
.ui-toast > .ic svg { width: 17px; height: 17px; }   /* Figma scales the 24 icon instance to 17.3 */
.ui-toast-body { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: var(--ra-sp-2xs); }
.ui-toast-title { font: var(--ts-label-status-upper); letter-spacing: var(--ts-label-status-upper-ls);   /* text style label/status-upper */
  text-transform: uppercase; color: var(--toast-tone, var(--ra-text-primary)); }
.ui-toast-text { font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls); color: var(--ra-text-secondary); overflow-wrap: anywhere; }
.ui-toast-body > .btn { margin-top: var(--ra-sp-2xs); }
.ui-toast:not(.default) .ui-toast-body > .btn { height: var(--ra-size-control-sm); }   /* Kind=default, Size=md at control/sm */
/* Fitts & Miller 05 (Figma toast 304:83, every state): the close is a 24 square with a 16 icon. */
.ui-toast-x.btn { flex: none; width: var(--ra-size-control-sm); background: var(--ra-surface-card); border-color: var(--ra-surface-card-elevated); }


/* S19 §9.2/S19-R6, task #18: `.ui-dd`/`.ui-dd-item` DO repoint -- these are
   the pop-open menus several 6px controls open (js/core/ui.js dropdown(),
   #tzMenu, Stats' range/more menus), and R6's own change left them at the
   OLD 4px scale, so a 6px trigger opened a 4px menu -- the exact
   inconsistency the owner's ruling exists to remove. `.ui-dd-item`'s
   `calc(--r-sm - 2px)` inset relationship is preserved with the new token. */
.ui-dd {
  position: fixed; z-index: 400;
  background: var(--bg-1); border: 1px solid var(--line-strong);
  border-radius: var(--ra-radius-sm);
  padding: var(--ra-sp-2xs); max-height: 320px; overflow: auto; min-width: 120px;
}
/* S31 #23 (fixes a same-session regression from A4, above this comment
   until this fix): the entrance transition used to live on BARE .ui-dd,
   whose FROM-state (opacity:0) is the resting state of every element
   carrying the class — including `#tzMenu` (index.html), the census two
   comments up already names as the app's ONE static-markup .ui-dd
   consumer. `dropdown()` (js/core/ui.js) adds `.on` a frame after mount to
   release the transition; #tzMenu opens via a bare `hidden` toggle
   (footer.js) and never gets `.on`, so it sat at permanent opacity:0 —
   present, hit-testable, click-swallowing, invisible (UX walk verdict,
   design/UX-REVIEW-motion.md).
   Scoped to `.ui-dd-anim`, a marker ONLY `dropdown()` stamps, rather than
   patching #tzMenu's open path to add `.on` by hand: a future static-
   markup .ui-dd consumer is immune BY CONSTRUCTION instead of depending on
   every author remembering the toggle — the six-consumer census stays
   structural instead of a comment the next property change can walk past. */
.ui-dd.ui-dd-anim {
  opacity: 0; transform: translateY(-4px) scale(0.98);
  transition: opacity var(--mo-dur-menu) var(--mo-ease-settle),
              transform var(--mo-dur-menu) var(--mo-ease-settle);
}
.ui-dd.ui-dd-anim.on { opacity: 1; transform: none; }
/* S31 #24 (MOTION_VERDICT.md §7, ruled spend-not-drop): --mo-stagger shipped
   at A1 consumed by zero rules — the reference's signature per-item cascade
   on menu open was declared but never spent. Scoped the same way #23 scoped
   the container fix: `.ui-dd.ui-dd-anim .ui-dd-item` only ever matches
   items `dropdown()`'s array-content path builds (js/core/ui.js — the ONLY
   call site that stamps both classes together), so `.ui-dd-item` reused as
   bare button styling elsewhere (Stats' #stMoreMenu, panels/stats/pane.html
   — a DIFFERENT, non-.ui-dd, static-markup consumer of this same item
   class) is untouched, and #tzMenu's own items never carry `.ui-dd-item` at
   all. Capped at 6 (MOTION_REF §4.2's own reference dropdown shows ~5
   items; a 53-item menu like #tzMenu's would never reach here anyway, but
   a factory-built menu with many rows must not take 3s to finish opening)
   — item 1 and every item past 6 render together with the container
   (delay 0), only items 2-6 stagger. Reduced motion needs no extra rule:
   the existing html.reduce-motion / prefers-reduced-motion block (app.css)
   already forces every transition-duration to 0.01ms !important, which
   collapses transition-delay's effect to nothing perceptible. */
.ui-dd.ui-dd-anim .ui-dd-item {
  opacity: 0;
  transition: opacity var(--mo-dur-menu) var(--mo-ease-settle);
}
.ui-dd.ui-dd-anim.on .ui-dd-item { opacity: 1; }
/* :nth-of-type, not :nth-child (measured live, matters): a dropdown() call
   can pass a whole Element as content (Stats' #stRange/#stMoreMenu,
   stats.js:685/688), which mixes non-.ui-dd-item chrome (a segmented
   control, date inputs, a meta span) in as EARLIER siblings ahead of the
   item buttons -- :nth-child counted those too and put the real first item
   at delay 3 instead of delay 0. Every .ui-dd-item here is a <button>, and
   it is the only tag sharing that container level with plain non-button
   chrome, so :nth-of-type(button) correctly numbers 1..N among buttons
   only, ignoring the divs/span/input around them. */
.ui-dd.ui-dd-anim.on .ui-dd-item:nth-of-type(2) { transition-delay: var(--mo-stagger); }
.ui-dd.ui-dd-anim.on .ui-dd-item:nth-of-type(3) { transition-delay: calc(var(--mo-stagger) * 2); }
.ui-dd.ui-dd-anim.on .ui-dd-item:nth-of-type(4) { transition-delay: calc(var(--mo-stagger) * 3); }
.ui-dd.ui-dd-anim.on .ui-dd-item:nth-of-type(5) { transition-delay: calc(var(--mo-stagger) * 4); }
.ui-dd.ui-dd-anim.on .ui-dd-item:nth-of-type(6) { transition-delay: calc(var(--mo-stagger) * 5); }
/* design-sync 2026-10-03 (Platform `dropdown/item`): min-height, so a row with a second line hugs it. A row shows an
   icon only when its item names one (below). Fitts & Miller 09.9 and the parity pass (dropdown/item 837:168,
   2026-10-17/18): menu and option rows are both 30; the label is the app's Label/Form 12 in text/primary (its
   20 line sits centred in the 30); the current row (`.on`, like `.sel` below) is the row tint, a trailing check
   and a Semi Bold label; a disabled row dims its label and its icon. */
.ui-dd-item { font: var(--ts-label-form); letter-spacing: var(--ts-label-form-ls);
  display: block; width: 100%; text-align: left;
  min-height: var(--ra-size-menu-item-sm);
  background: none; border: 0; color: var(--ra-text-primary);
  padding: var(--ra-sp-5) var(--ra-sp-md);
  border-radius: calc(var(--ra-radius-sm) - 2px); cursor: pointer; }
.ui-dd-item:hover { background: var(--surf-hi); color: var(--text-1); }
.ui-dd-item.danger { color: var(--neg); }
.ui-dd-item:disabled { color: var(--text-4); cursor: default; background: none; }
/* A row's leading icon is text/secondary at rest, on and selected; a disabled row's icons are text-4 (Figma dropdown/item
   837:168). The trailing check and the submenu caret keep their own inks. */
.ui-dd-item:not(.danger, :disabled) > .ic:not(.ui-dd-check, .ui-dd-caret) { color: var(--ra-text-secondary); }
.ui-dd-item:disabled > .ic { color: var(--text-4); }
/* Figma round 2026-10-13 (Platform dropdown/item 837:168; Organisms popover / thread-action 557:8806, folder-picker
   557:11851). A row with an icon, a hint or a submenu (it holds a .ui-dd-label) is Figma's row: 5 / 8 / 5 / 4, its parts
   12 apart, the icon 12 in the row's colour, the label cut with an ellipsis, the hint caption/10 in text-4, the submenu's
   caret 12 in a 14 box. A text-only row keeps 5 / 10: Figma's 4 is drawn before an icon or a checkbox, and on a bare
   label it would pull every menu's text 6 to the left (net 9 from the menu's edge against 15). `.sel` (Density=option,
   State=selected 3027:1994) and the row whose submenu is open: state/row-hover, text/primary. `.ui-dd-pop`: the
   popover's shell (Platform popover 351:187): r14, the menu's 1 px border (Figma counts its stroke in the layout: rows
   at 15 / 17, 182 wide in 216), 14 / 16 inside it, rows 6 apart, shadow/popover. */
.ui-dd-item:has(> .ui-dd-label) { display: flex; align-items: center; gap: var(--ra-sp-lg); padding: var(--ra-sp-5) var(--ra-sp-sm) var(--ra-sp-5) var(--ra-sp-2xs); --ic-size: var(--ra-size-icon-sm); }
/* a label-span row with no leading icon (the current row of a text menu, which carries the check) keeps the text rows' 10 */
.ui-dd-item:has(> .ui-dd-label:first-child) { padding-left: var(--ra-sp-md); }
.ui-dd-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-dd-hint { flex: none; font: var(--ts-caption-10); letter-spacing: var(--ts-caption-10-ls); color: var(--ra-text-secondary); }
.ui-dd-caret { flex: none; width: var(--ra-size-icon-md); color: var(--text-4); }
.ui-dd-item.on, .ui-dd-item.sel, .ui-dd-item[aria-expanded=true] { background: var(--ra-state-row-hover); color: var(--ra-text-primary); }
/* The current or selected row's label in 600 and its trailing check (14, text/primary): Figma dropdown/item State=on and
   State=selected (837:168; 3027:1994; the folder picker 557:11851). */
.ui-dd-item.on, .ui-dd-item.sel > .ui-dd-label, .ui-dd-item.on > .ui-dd-label { font-weight: 600; }
.ui-dd-check { flex: none; color: var(--ra-text-primary); --ic-size: var(--ra-size-icon-md); }
.ui-dd.ui-dd-pop { display: flex; flex-direction: column; gap: var(--ra-sp-xs); padding: var(--ra-sp-14) var(--ra-sp-xl); max-height: calc(100vh - var(--ra-sp-sm));
  border-radius: var(--s11-modal-shell-radius); background: var(--ra-surface-panel); box-shadow: var(--ra-shadow-popover); }

/* == ds/molecules/popover.css == */
/* ------------------------------------------------------------
   PV2.ui.popover() / .modalHead() / .linkPopover() (core/ui.js) — S13-A5,
   design/PLAN.md §S13.3.2/§S13.4 items 1-3. The shell of node `362:1608`,
   built for its FIRST CONSUMER (the link-group popover) rather than a
   bespoke block (LEAD_RULINGS S13-R11: the retrofit of .cmode-pop/.cind-dd/
   .col-popover and modal()'s own bare `.ui-modal-hd` is DEFERRED and NOT
   touched here). NOTHING OPENS THIS POPOVER YET — bootable and INERT.
   Geometry is 100% from the node (both reads agree, S13-R32 re-confirmed
   the read did not move): BOTH axes HUG in Figma, but the content is fully
   deterministic (exactly 6 swatches, forever — absence #3), so 160×122 is
   authored here as fixed dimensions rather than re-implementing HUG sizing.
   ------------------------------------------------------------ */
.ui-popover {
  position: fixed;
  z-index: 405;   /* above .ui-dd (400), below .ui-modal-back (450) — this
                      is a dismissible anchored surface like a dropdown, not
                      a blocking modal */
  background: var(--ra-surface-panel);
  border-radius: var(--s11-modal-shell-radius);
  /* S11-R18's modal-shell contract (declared above, ~line 1027) is
     `border: var(--s11-modal-shell-border)` on its other consumers — a
     REAL border, which in border-box sizing CONSUMES 1px of box space per
     side. Figma's `strokeAlign: INSIDE` does not: it paints inside the
     frame's boundary without shrinking the auto-layout content area, so a
     plain `border` here would leave content 2px narrower than the node's
     own 136 (measured live: 134, not 136, cost a probe round). An `outline`
     with a negative offset reproduces the SAME 1px-inside look while never
     participating in box sizing, in any mode — this element has NO width/
     height of its own and shrink-wraps `.ui-linkpop-shell` (below) to
     exactly its rendered 160x122, and the ring must not disturb that. */
  outline: var(--s11-modal-shell-border);
  outline-offset: -1px;
}
.ui-linkpop-shell { width: 160px; box-sizing: border-box; padding: var(--ra-sp-lg); display: flex; flex-direction: column; gap: var(--ra-sp-md); }
.ui-modalhead2 { display: flex; align-items: center; justify-content: space-between; }
.ui-modalhead2-title {
  font-family: var(--font-display);
  font-size: var(--ra-fs-15);
  line-height: var(--ra-lh-24);
  font-weight: 500;
  color: var(--text-1);
  text-transform: uppercase;
  letter-spacing: 0;
}
/* design-sync 2026-10-03 (Figma pop-head 339:9, the same anatomy): the close is the 24 icon-only tile, 16 icon. */
.ui-modalhead2-close {
  width: var(--ra-size-control-sm); height: var(--ra-size-control-sm); padding: 0; margin: 0; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--ra-radius-sm);
  background: var(--ra-surface-card);
  color: var(--ra-text-axis);
  cursor: pointer;
  font-family: inherit;
}
/* absence #6 (§S13.3.3): "close chip hover = the kit's existing
   .btn.ghost:hover" — the VALUES that rule applies (line ~854 above), not
   its class: .btn's own rest state carries a 1px border this chip's spec
   explicitly has none of. DECLARED DIVERGENCE — no frame hover exists. */
.ui-modalhead2-close:hover {
  color: var(--text-1);
  border: 1px solid var(--line-strong);
  background: var(--bg-2);
}
.ui-linkpop-hr { height: 1px; flex: none; background: var(--ra-surface-card); }
.ui-linkpop-swatches { display: flex; gap: var(--ra-sp-sm); }
.ui-linkpop-swatch {
  width: 16px; height: 16px; flex: none; padding: 0; margin: 0;
  border: 0; border-radius: 50%;
  cursor: pointer;
}
.ui-linkpop-swatch[data-color=magenta] { background: var(--ra-link-magenta); }
.ui-linkpop-swatch[data-color=blue]    { background: var(--ra-link-blue); }
.ui-linkpop-swatch[data-color=orange]  { background: var(--ra-link-orange); }
.ui-linkpop-swatch[data-color=yellow]  { background: var(--ra-link-yellow); }
.ui-linkpop-swatch[data-color=purple]  { background: var(--ra-link-purple); }
.ui-linkpop-swatch[data-color=cyan]    { background: var(--ra-link-cyan); }
/* The selected treatment (`402:209`, S13-R32 re-confirmed): a 1.5px ring
   OUTSIDE the 16×16 layout box, rendering 19×19 — box-shadow, never a
   border (a border would shrink the swatch's content instead of
   overflowing past it, and cannot be told apart from the ring by a probe
   that only measures the rendered box). */
.ui-linkpop-swatch.sel { box-shadow: 0 0 0 1.5px var(--ra-link-ring); }
/* absence #5: "swatch hover = the selection ring at 40% alpha" — scoped to
   :not(.sel) so an already-ringed swatch does not draw two rings at once
   (the frame draws only one selected treatment, absence #8: zero or one,
   never two). DECLARED DIVERGENCE — no frame hover exists. */
.ui-linkpop-swatch:not(.sel):hover {
  box-shadow: 0 0 0 1.5px color-mix(in oklch, var(--ra-link-ring) 40%, transparent);
}
/* F4 (design/s13/qa/S13_QA_CLOSING_REVIEW.md): --ra-text-tertiary measured
   3.82:1 against this popover's own background (--ra-surface-panel) at
   its rendered 11px -- under the 4.5:1 text floor, and the only escape
   from a colour group, so the control most likely to be reached for while
   already confused. Re-measured live (canvas pixel-readback, not a string
   parse of getComputedStyle -- see design/s14/dev_b/f4_readings.json for
   why that distinction matters on this Chromium build): 3.82 confirmed
   exactly, --ra-text-tertiary resolves to R107/G114/B118 (avoiding the
   `rgb(` spelling here on purpose -- that shape alone is what token_lint's
   RGB_RE flags, unconditionally, even inside a comment describing an
   already-allowed value: caught by re-running the linter after writing
   this paragraph, the same trap K2's own record names for lang_lint).
   --ra-text-subtle is the DIMMEST token in this file's text scale that
   clears 4.5 -- 6.13:1. The next step down, --ra-text-dim (4.24:1), still
   falls short, so there is no token landing near the floor: the fix is a
   ~60% contrast jump, not a one-step nudge, and it is visibly brighter,
   not merely technically compliant. Flagged to the lead alongside both
   measurements rather than silently picked -- if that reads as heavier
   de-emphasis than the popover was meant to carry, that is a call for the
   lead/owner, not this file. */
.ui-linkpop-clear {
  display: flex; align-items: center; gap: var(--ra-sp-xs);
  height: 16px; padding: 0; margin: 0; border: 0; background: none;
  cursor: pointer;
  color: var(--ra-text-subtle);
  font-family: var(--font-sans); font-size: var(--ra-fs-11); line-height: var(--ra-lh-16); letter-spacing: 0;
}
/* design-sync 2026-10-03: the icon fills its 12 box (24-grid icons keep their own 2-unit air), size from the token. */
.ui-linkpop-clear .ic { width: var(--ra-size-icon-sm); height: var(--ra-size-icon-sm); --ic-size: var(--ra-size-icon-sm); flex: none; display: inline-flex; align-items: center; justify-content: center; }
/* absence #5: "Clear link row hover = color: var(--text-2)". DECLARED
   DIVERGENCE — no frame hover exists. */
.ui-linkpop-clear:hover { color: var(--text-2); }

/* == ds/organisms/tooltip.css == */
/* ------------------------------------------------------------
   The kit tooltip (ds/organisms/tooltip.js, PV2.ui.tooltip): one element per document, `.ui-tip`, positioned fixed so
   it escapes any ancestor's overflow and never takes a pointer. The base is the compact face (the SEC form tag:
   a head line in the info colour over one short text); `.rich` is the card face (the Intelligence info icon: a darker
   card with a shadow, a larger body and a source / refresh footer). Tokens only; `[hidden]` takes it out of the layout
   once its fade is over (tooltipHide).
   ------------------------------------------------------------ */
.ui-tip {
  position: fixed;
  z-index: 2147483600;
  max-width: 300px;
  padding: var(--ra-sp-sm) var(--ra-sp-md);
  border-radius: var(--ra-radius-md);
  background: var(--bg-2);
  border: 1px solid var(--line);
  font: var(--ts-caption-11);
  line-height: 1.45;
  color: var(--text-2);
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--mo-dur-tip) ease-out, transform var(--mo-dur-tip) ease-out;
}
.ui-tip.show { opacity: 1; transform: none; }
.ui-tip-h {
  margin-bottom: var(--ra-sp-3);
  font: var(--ts-caption-10);
  line-height: normal;
  letter-spacing: var(--ts-tag-ls);
  color: var(--info);
  text-transform: uppercase;
}

.ui-tip.rich {
  max-width: 360px;
  min-width: 240px;
  padding: var(--ra-xsp-13) var(--ra-sp-xl) var(--ra-xsp-11);
  background: var(--bg-1);
  box-shadow: 0 10px 28px var(--ra-intel-rgb-0-0-0-a50), inset 0 0 0 1px var(--ra-intel-rgb-255-255-255-a03);
}
.ui-tip.rich .ui-tip-h {
  margin-bottom: var(--ra-xsp-9);
  font-family: var(--font-mono);
  font-size: var(--ra-fs-10);
  font-weight: 600;
  letter-spacing: var(--ra-ls-em-12);
  color: var(--text-2);
}
.ui-tip.rich .ui-tip-b {
  font-size: var(--ra-fs-13);
  line-height: 1.5;
  color: var(--text-1);
  white-space: pre-wrap;
}
.ui-tip-foot {
  margin-top: var(--ra-sp-md);
  padding-top: var(--ra-sp-sm);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--ra-sp-3);
}
.ui-tip-meta {
  display: grid;
  grid-template-columns: 70px 1fr;
  gap: var(--ra-sp-lg);
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: var(--ra-fs-11);
  line-height: 1.4;
}
.ui-tip-l {
  letter-spacing: var(--ra-ls-em-10);
  text-transform: uppercase;
  color: var(--text-4);
  font-weight: 600;
}
.ui-tip-v { color: var(--text-1); }
/* == ds/templates/shell.css == */
/* shell.css: shell, topbar, ticker, footer, pane+grid — split out of rally.css. */
/* ------------------------------------------------------------
   Shell
   ------------------------------------------------------------ */
.shell {
  height: 100vh;
  display: grid;
  grid-template-rows: auto auto 1fr auto;   /* topbar, tape, content, footer */
  overflow: hidden;
}
.shell > .topbar { grid-row: 1; }
.shell > .ticker-bar { grid-row: 2; }
.shell > .footer { grid-row: 4; }

/* == ds/organisms/topbar.css == */
/* ------------------------------------------------------------
   Top bar — 60px (design/DESIGN_SPEC.md §1.4, node 321:95). The nav tabs
   that used to live here moved into the Rail (§S3, index.html) — the
   header is search/status/avatar only now.
   ------------------------------------------------------------ */
.topbar {
  height: var(--ra-size-bar-top); /* design-sync 2026-10-03: Figma size/bar/top */
  display: flex;
  align-items: center;
  /* CD-54 (design/COMPONENT_DIFF.md §T3.2 G16): itemSpacing 17, not the
     --sp-3 (8px) scale step — measured directly off 321:95, not a round
     number on the generic spacing scale. */
  gap: var(--ra-xsp-17);
  /* 27px both sides, not --sp-4 (12px) — design/DESIGN_SPEC.md §1.4: logo
     @ x=27 on the left, avatar (node 321:142, 102x38 @ x=1791) ends at
     1893 = 1920-27 on the right. T5/A5 caught the right side, which the
     first pass left at --sp-4 as out of scope; symmetric 27 is correct.
     Vertical 0->8 (S3-08, design/PLAN.md §S4.1): matches the frame's own
     auto-layout padding. Verified this is symmetric on a fixed 60px height
     with align-items:center below, so it is provably a no-op on today's
     all-centered content (checked the arithmetic, not assumed) — kept for
     correctness against the frame and because it stops being a no-op the
     moment any non-centered child is added. */
  padding: var(--ra-sp-sm) var(--ra-xsp-27);
  /* design/PLAN.md §8.1 #1/#2/#3, design/DESIGN_SPEC.md §6.2: the 2.0 header
     is a FLAT surface.header at every y (the 1.0 gradient measured two
     different, visibly darkening hex at y=1 vs y=50 against a flat frame)
     with no border between bands — the 1.0 gradient + underline glow +
     hairline are deleted, not overridden. */
  background: var(--ra-surface-header);
  position: relative; z-index: 20;
  /* overflow-x/-y + the ::-webkit-scrollbar suppression below DELETED
     here (design/PLAN.md S12.8.1 W-A6, S12-R2 cl.4, own commit LAST):
     folded onto the shared `.hscroll` behaviour (index.html), which
     supplies the identical pair plus a wheel shim and a state-conditioned
     fade this row never had — the app's highest-traffic control row was
     ALREADY a hidden-scrollbar horizontal scroller with no way for a
     mouse-only user to reach it (S12-R2's own grounds for folding this
     one, stronger than the others: we already ship the defect the
     behaviour exists to end). `white-space:nowrap` is KEPT, not folded —
     it is not part of the scroll idiom (`.hscroll` does not set it) and
     `.user-wrap .name`/`.addr` (below) have no `white-space` of their own,
     so they rely on it being inherited from here; removing it would let
     a long username/handle wrap instead of truncate, a regression this
     item does not need to take on. */
  white-space: nowrap;
}
.topbar-spacer { flex: 1; }
/* app-header cluster 341:4431 (S3-01) — the 6 icon buttons' own pitch,
   split out of .topbar's gap by CD-54/G16 (see the comment on .topbar
   above). Unchanged from what the cluster measured before that split. */
.topbar-icons { display: flex; align-items: center; gap: var(--sp-3); flex-shrink: 0; }

.logo {
  /* 127x32 @ (27,14) — design/DESIGN_SPEC.md §1.4, node 321:96. Width was
     96 (a stale pre-2.0 figure); at 127 the SVG's own aspect ratio gives
     ~31.8px height, matching the spec's 32 and still centring inside the
     60px header. margin-right closes the gap to the next header element
     (#feedBanner, which falls at x=219 by flow once this is 219-27-127=65:
     the .topbar `gap` (17px, CD-54) contributes part of that, this margin
     the rest — 48+17=65, re-derived when the gap moved off its old 8px).
     Fitts & Miller 06: that element is the feed chunk #feed now (below). */
  width: 127px;
  /* S22-B item 12 (design/s22b/LOGO_FACTS.md §4.1): 127/32, matching the
     exported SVG's OWN viewBox header — was 1307/327 (3.9914:1, the OLD
     single-colour asset's stale box, ~0.9% wider than the canonical
     export's 3.9551:1 ink ratio); a drop-in of the new asset at the old
     ratio squashes it. */
  aspect-ratio: 127 / 32;
  margin-right: var(--ra-sp-4xl);
  /* S22-B item 12: `.logo` is now the inline <svg> itself (index.html/
     login.html), not a masked div — a CSS mask can only paint ONE colour
     and the lockup is two-colour (LOGO_FACTS §4.2). The mark's 2 paths
     carry their own --ra-accent-primary fill; `color` here drives the
     10 wordmark paths' `fill="currentColor"` — --text-1, not
     --ra-text-inverse (a fixed #FFFFFF that would reproduce the exact
     light-theme hazard LOGO_FACTS §4.3 measured: the export's real white
     wordmark vanishes on light theme's near-white topbar). */
  color: var(--text-1);
  flex-shrink: 0;
}

/* The feed chunk #feed (Fitts & Miller 06 / 10, Organisms topbar 83:627): the market data's one status, right after the
   logo, in the same place in every state (shell/layout/topbar.js writes data-state). wait / live: the status chip and
   the rate; late: a filled amber chunk, the delay in words, the attempt and the rate; down: a filled red chunk, the
   attempts and Reconectar ahora. Both fills carry dark ink (frame 10: text/on-accent on --warn, 11.7:1; 7.5:1 on danger),
   r6 and Regular text; the ⚠ and the words stay, so the state never rides on colour alone. 34 tall in every state, so a
   change never moves the bar. */
.feed { display: flex; align-items: center; gap: var(--ra-sp-md); height: 34px; flex-shrink: 0; border-radius: var(--ra-radius-sm); }
.feed[data-state=late], .feed[data-state=down] { padding: 0 var(--ra-sp-xs) 0 var(--ra-sp-lg); color: var(--ra-text-on-accent); }
.feed[data-state=late] { background: var(--warn); }
.feed[data-state=down] { background: var(--ra-semantic-danger); }
.feed[data-state=late] .st, .feed[data-state=down] .st, .feed[data-state=down] .pill { display: none; }
.feed-msg { display: none; font: var(--ts-h5-widget-title); letter-spacing: var(--ts-h5-widget-title-ls); font-weight: 400; white-space: nowrap; }
.feed[data-state=late] .feed-msg, .feed[data-state=down] .feed-msg { display: block; }
/* The rate pill hugs its text (06): 28 tall, the number 11 Medium (h6) in text/primary, the unit in the pill's own 10, secondary. */
.feed .pill.num { height: var(--ra-size-badge-lg); background: var(--ra-surface-panel); border-color: var(--ra-border-card);
  font-weight: 500; color: var(--ra-text-secondary); }
#rate { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); color: var(--ra-text-primary); }
/* Reconectar ahora: the kit's ghost sm, in the chunk's ink (frame 10's offline variant). */
.feed-reconnect { font: var(--ts-button-small); letter-spacing: var(--ts-button-small-ls); height: var(--ra-size-control-sm); padding: 0 var(--ra-sp-lg);
  background: transparent; border: var(--ra-border-hairline-w) solid currentColor; color: inherit; border-radius: var(--ra-radius-md); cursor: pointer; }
.feed-reconnect:hover { background: color-mix(in srgb, currentColor 12%, transparent); }
.feed-reconnect:disabled { opacity: .5; cursor: default; }
.feed-reconnect[hidden] { display: none; }
/* What a screen reader hears once per change (role=status), never the ticking delay or rate: kept off screen. */
.feed-say { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* User wrap */
.avatar {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
    var(--avatar-ring-info),
    var(--avatar-ring-pos),
    var(--avatar-ring-info));
  position: relative;
  flex-shrink: 0;
}
.avatar::after {
  content: ""; position: absolute; inset: 2px;
  border-radius: 50%;
  background: var(--bg-1);
}
/* CD-76 (G19): 38 tall (was 42), fill surface.header (was surface.panel),
   border surface.card-elevated (was border.default). CD-77 (the avatar
   letter's own type — Space Grotesk 500/15 near-black vs the app's
   Inter 600/11 near-white) is css/account.css's `.avatar.am-mark`, outside
   this file's scope this session — not fixed here. */
.user-wrap {
  display: flex;
  align-items: center;
  gap: var(--ra-sp-sm);
  height: 38px;
  padding: var(--ra-sp-2xs) var(--ra-sp-md) var(--ra-sp-2xs) var(--ra-sp-2xs);
  border: 1px solid var(--ra-surface-card-elevated);
  border-radius: 999px;
  background: var(--ra-surface-header);
  flex-shrink: 0;
  box-sizing: border-box;
}
.user-wrap .user-meta { display: flex; flex-direction: column; line-height: 1.15; }
/* CD-79/CD-80 (G19): the name run is Inter 400/12/lh20/ls0.3 in
   text.inverse (near-white, one step brighter than text.primary) — was
   500/11/lh12.65. The handle run is Inter 400/9/lh16 in text.secondary —
   was 10px/text.quaternary; its CONTENT (`@handle` vs a bare `▾` caret) is
   P1 (design/PLAN.md §T5), the owner's call, not touched here.
   S17 #21 (S17-R25, the --ra-text-inverse per-site pass): `.user-wrap`'s
   own background is `--ra-surface-header`, which FLIPS (checked live) --
   pure white text on that background goes invisible in light theme
   (measured RED before this fix). `--ra-text-inverse` -> `--ra-text-primary`:
   CD-79/80's own intent ("one step brighter than primary") has no light-
   theme equivalent -- primary is already the brightest/darkest extreme in
   each theme -- so this keeps the MOST prominent flip-safe token rather
   than chasing a delta that doesn't exist on the light side. */
.user-wrap .name { font: var(--ts-label-form); letter-spacing: var(--ts-label-form-ls); color: var(--ra-text-primary); }
.user-wrap .addr { font: var(--ts-caption-helper); letter-spacing: var(--ts-caption-helper-ls); color: var(--ra-text-secondary); }

/* == ds/organisms/ticker-bar.css == */
/* ------------------------------------------------------------
   Ticker bar — 38px (design/DESIGN_SPEC.md §1.5, node 321:150). A restyle
   of the existing element, not new (design/PLAN.md §0.2) — same markup,
   same footer.js data source, taller + retextured.
   ------------------------------------------------------------ */
.ticker-bar { font: var(--ts-label-stat); letter-spacing: var(--ts-label-stat-ls); height: var(--ra-size-bar-ticker);
  display: flex;
  align-items: center;
  /* CD-84 (G20): itemSpacing 32, not the --sp-6 (24px) scale step. */
  gap: var(--ra-sp-3xl);
  /* S3-09 (design/PLAN.md §S4.1): 0/16 -> 8/22. Horizontal was --sp-5
     (16px, confirmed); 22 matches no spacing token so it's a literal, same
     treatment as the many other measured one-off px values in this file.
     Vertical is symmetric on this fixed 38px/align-items:center bar too —
     same no-op-today reasoning as .topbar above. */
  padding: var(--ra-sp-sm) var(--ra-xsp-22);
  /* design/PLAN.md §8.1 #4, design/DESIGN_SPEC.md §6.2: no border between
     bands — separation is by fill alone. */
  /* T5/A2: design/DESIGN_SPEC.md §1.5 states the background literally
     ("surface.tape") — was the old --bar-dark-bg formula, sampled ~18 RGB
     darker than the frame's surface.tape. */
  background: var(--ra-surface-tape);
  overflow: hidden;
  /* QA F2 (design/s22/QA_VERDICT_S22.md §1.4): this fade predates S22
     (171ec79a), from when the left edge was moving content like the right.
     S22-A1 put STATIC content (the S&P 500 pin) there instead, so a LEFT
     fade now just dims real, always-readable text (~55% opacity under the
     "S", full only past x≈43) for no reason — the frame draws no fade at
     all. Right-edge-only now; .marquee-viewport (F3 below) carries its own
     independent left fade for the one thing that still needs one: movers
     entering the animated lane. */
  mask-image: linear-gradient(90deg, var(--mask-edge-opaque) 0, var(--mask-edge-opaque) calc(100% - 40px), transparent 100%);
  flex-shrink: 0; }
.ticker-bar .tk { display: inline-flex; align-items: center; gap: var(--ra-sp-sm); white-space: nowrap; }
.ticker-bar .sym { color: var(--text-2); letter-spacing: var(--ra-ls-em-05); }
@keyframes ramarquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* Gap is --ra-sp-3xl (32px), not --sp-6 (24px): FRAME_FACTS_S22.md §1.1
   measures the tape's auto-layout itemSpacing at 32 uniformly (no
   dividers) — found stale here while re-deriving K1 for S22-A1, fixed to
   match .ticker-bar's own 32px gap below it. */
.ticker-bar .marquee-track {
  display: inline-flex;
  gap: var(--ra-sp-3xl);
  animation: ramarquee var(--mo-dur-marquee) linear infinite;
}
/* K1 (design/s13/qa/S13_QA_CLOSING_REVIEW.md) SUPERSEDED AGAIN — owner
   defect report on the S22-A1 build (2026-08-22): the first re-derivation
   (an opaque background + z-index on #tapeStatic, painted over whatever of
   .marquee-track slid underneath it) had a real gap. .marquee-track's own
   width is its full unclipped content (~7000px of duplicated movers); only
   .ticker-bar's OUTER overflow:hidden was clipping it, and .tape-static's
   opaque patch only covers ITS OWN box (x from the bar's left padding to
   its own right edge) — .ticker-bar's left PADDING itself is inside that
   outer overflow's visible area, so once translateX pushed the track's box
   left of x=22 (tape-static's own left edge), the sliver of track content
   sitting in that padding gutter had nothing painted over it and leaked
   through, exactly as the owner's screenshot showed (letters before "S&P
   500"). An opaque patch only ever covers where you remembered to put it;
   a clipping viewport makes the leak structurally impossible. Fix:
   .marquee-viewport wraps .marquee-track with ITS OWN overflow:hidden,
   flush against #tapeStatic's right edge (the tape's own 32px item gap) —
   the animated track can now never paint a pixel left of its own viewport,
   full stop, at any point in the 60s cycle. #tapeStatic goes back to a
   plain flex item; it no longer needs to occlude anything.
   Fitts & Miller 14 (Organisms ticker-bar 13:46094): 48 between the index and TENDENCIA, so the label leads its list. */
.ticker-bar .tape-static {
  display: inline-flex;
  align-items: center;
  gap: var(--ra-sp-4xl);
  flex-shrink: 0;
}
/* QA F3 (design/s22/QA_VERDICT_S22.md §1.4): the viewport's left edge was a
   hard overflow:hidden clip flush against TRENDING — one end of the strip
   (the bar's own right edge, above) fades, the other guillotined movers
   mid-glyph, permanently parking a fragment beside the label. Own left-edge
   fade, same formula/token .ticker-bar's already uses, so entering movers
   fade in instead of chopping. */
.ticker-bar .marquee-viewport {
  overflow: hidden;
  flex: 1 1 0;
  min-width: 0;
  mask-image: linear-gradient(90deg, transparent 0, var(--mask-edge-opaque) 24px, var(--mask-edge-opaque) 100%);
  -webkit-mask-image: linear-gradient(90deg, transparent 0, var(--mask-edge-opaque) 24px, var(--mask-edge-opaque) 100%);
}
.ticker-bar .trending-label { font: var(--ts-button-medium); letter-spacing: var(--ts-button-medium-ls); color: var(--ra-text-secondary); flex-shrink: 0; }
/* S&P 500 pin (design/s22/FRAME_FACTS_S22.md §1.3/1.5): symbol is always
   text.primary; price and delta are direction-coloured via the shared
   pos-l/neg-l classes (--pos-light/--neg-light, tokens/panels/intelligence/
   intelligence.css), or .tap-neutral for the em-dash placeholder state —
   never combined with pos-l/neg-l on the same element, so no default color
   here competes with them on specificity. */
.ticker-bar .pin-sym, .ticker-bar .pin-px { font: var(--ts-h4-card-subtitle); letter-spacing: var(--ts-h4-card-subtitle-ls); }
.ticker-bar .pin-sym { color: var(--text-1); }
.ticker-bar .pin-chg { font: var(--ts-body-large); letter-spacing: var(--ts-body-large-ls); }
/* Movers price (A-2, design/s22/PLAN_S22.md §6 area A): folds the row
   builder's inline style="color:var(--text-1)" into a class. */
.ticker-bar .px { color: var(--text-1); }
.ticker-bar .tap-neutral { color: var(--text-2); }

/* == ds/organisms/status-bar.css == */
/* ------------------------------------------------------------
   Status bar (.footer) — 48px (design/DESIGN_SPEC.md §1.9, node 321:2113).
   CHANGE_MAP §1.1: "near-total parity already" — same contents, taller.
   ------------------------------------------------------------ */
.footer { font: var(--ts-label-stat); letter-spacing: var(--ts-label-stat-ls); height: var(--ra-size-bar-status); /* design-sync 2026-10-03: Figma size/bar/status */
  display: flex;
  align-items: center;
  /* CD-93 (G22): itemSpacing 16 (--sp-5), not 12 (--sp-4). The frame
     separates the status-text run by spacing alone, no glyph between
     items — CD-96's six app-only `/` separators (index.html) are removed
     in the same commit rather than left orphaned by a wider gap. */
  gap: var(--sp-5);
  padding: 0 var(--sp-5);
  /* design/PLAN.md §8.1 #5 (T1's S6), design/DESIGN_SPEC.md §6.2: no border
     between bands — separation is by fill alone. */
  color: var(--text-4);
  /* T5/A3: no background was declared at all, so it fell through to the
     shell's #060A10 base. design/DESIGN_SPEC.md §1.1's Status bar row is
     surface.panel. */
  background: var(--ra-surface-panel);
  flex-shrink: 0; }
/* CD-96 (G22): .footer .sep dead-coded — the six `/` separators it styled
   were removed from index.html (app-only, the frame spaces by gap alone). */

/* == ds/templates/workspace.css == */
/* ------------------------------------------------------------
   Workspace — Rail (81px) + pane area, 8px padding all sides, 8px gutter
   between them (design/DESIGN_SPEC.md §1.2, node 321:216). Replaces the
   old inline-styled grid-row:4 div; the panes it wraps are unchanged.
   ------------------------------------------------------------ */
.workspace {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  position: relative;
  display: flex;
  flex-direction: row;
  padding: var(--ra-sp-sm);
  gap: var(--ra-sp-sm);
  box-sizing: border-box;
}
.workspace-panes {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* == ds/organisms/rail.css == */
/* ------------------------------------------------------------
   Rail — 81px vertical nav (design/DESIGN_SPEC.md §1.3, node 321:217).
   No prior side-nav exists anywhere in the app (design/PLAN.md §3.2); the
   only structural precedent is the chart's vertical drawing toolbar
   (css/widgets.css:72-99). [data-panel-tabs] travels inside `.nav.tabs`
   unchanged (design/PLAN.md S3) — shell/panels.js is not edited.
   ------------------------------------------------------------ */
/* Owner 2026-09-11: "increase the sidebar width slightly as Intelligence and
   Automations are breaking word". Measured live on the owner's machine the
   single-line labels need 77.34px (Automations) and 71.55px (Intelligence)
   against the 81px rail's 70px label box — K2's 66.31px figure below was
   taken under a different font rendering and no longer holds, so both wrap
   mid-word again. 92px gives an 81px box, 3.7px clear of the widest label.
   DECLARED divergence from the frame's 81px (§1.3), on the owner's call.
   Fitts & Miller 09 (Organisms rail 13:45904): one 92 px rail on every screen, its items filling it (84 wide, 4 from its
   outer edge), the same items everywhere. The rail also spans the workspace's 8 px gutter: its frame (::before) still
   starts 8 in, while its scroll box (.rail-in) starts at the screen's left edge, so the items' hit areas (below) reach it
   without being clipped. */
.rail {
  position: relative;
  width: calc(92px + var(--ra-sp-sm));
  margin-left: calc(-1 * var(--ra-sp-sm));
  flex-shrink: 0;
  height: 100%;
}
.rail::before {
  content: ""; position: absolute; inset: 0 0 0 var(--ra-sp-sm); pointer-events: none;
  background: var(--ra-surface-panel);
  border: 1px solid var(--ra-border-default);
  border-radius: var(--ra-radius-sm);
}
.rail-in {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: var(--ra-border-hairline-w) var(--ra-border-hairline-w) var(--ra-border-hairline-w) calc(var(--ra-sp-sm) + var(--ra-border-hairline-w));
  overflow-y: auto;
  overflow-x: hidden;
}
.rail .nav.tabs {
  display: flex;
  flex-direction: column;
  gap: var(--ra-sp-2xs);
  padding: var(--ra-sp-3);
}
/* CD-91 (S13-W2): the frame's Group 3 (321:233, 73x215.162 for its three
   seal/nav-item children) is exactly 3x69.054 + 2x4 gap -- ZERO padding.
   `.nav.tabs` above (same rail, same 4px gap) matches that same shape for
   its own frame group (321:220, 73x288.216 = 4x69.054 + 3x4). The
   vertical 4px here was extra inset the frame's group box doesn't carry;
   dropped. Fitts & Miller 09: 4 from the rail's outer edge at the sides and the foot (3 inside its 1 px border). */
.rail-bottom {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--ra-sp-2xs);
  padding: 0 var(--ra-sp-3) var(--ra-sp-3);
}
/* Nav item: icon over label, both centred (design/DESIGN_SPEC.md §1.3).
   Applies to both the primary tabs (.nav.tabs button, JS-generated by
   shell/panels.js — data-tab is the only hook it gives us) and the
   bottom utility group (.rail-util — same seal/nav-item component in
   Figma, node 321:234/238/241). */
.rail .nav.tabs button,
.rail-util { font: var(--ts-label-nav-tab); letter-spacing: var(--ts-label-nav-tab-ls); width: 100%;
  /* design/DESIGN_SPEC.md §6.3 (node 321:220): seal/nav-item is 73x69.054,
     step 73.054 (69.054 + 4 gap). Was 60px — a pre-2.0 figure. */
  min-height: 69.054px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--ra-sp-xs);
  /* CD-89 (G21): 12 top/bottom, 6 left/right — was 8/2. */
  padding: var(--ra-sp-lg) var(--ra-sp-xs);
  border-radius: var(--ra-radius-xs);
  background: transparent;
  border: 0;
  color: var(--ra-text-secondary);   /* Fitts & Miller 09: inactive labels in text/secondary (7.1:1; text/tertiary was 3.8:1) */
  text-align: center;
  cursor: pointer;
  transition: background-color var(--mo-dur-control), color var(--mo-dur-control);
  /* CD-767, design/PLAN.md S12.8.1 W-A4: `nav.rail` clips its own label
     in ES on every route (+7px, `overflow-x:hidden` above), because a
     SINGLE-WORD Spanish label ("Automatizaciones", 16 chars, no natural
     break point) can't wrap inside the 71px content box and forces the
     rail wider instead. `.rail` is a VERTICAL nav -- it does not inherit
     `.hscroll` (S12.12); the remedy is the label box, measured in ES.
     `anywhere`, not `break-word`: the label text is a bare text node
     inside this flex COLUMN container (no wrapping span in the markup),
     so it lays out as an anonymous flex item — `break-word` only affects
     overflow rendering, not the min-content size a flex item computes its
     own minimum width from (CSS Text spec), so it measured ZERO effect
     live (`railScroll` stayed 86). `anywhere` participates in min-content
     sizing too, which is what actually lets the item shrink below the
     whole word's width. */
  overflow-wrap: anywhere; }
/* CD-91 (S13-W2, re-opened S13-R21): frame Group 3 (321:233) fixes EVERY
   seal/nav-item at 69.054 regardless of icon size (32/27/32, non-uniform,
   already correct) or line count (its own `Keyboard Shortcuts` label is
   TWO lines at 51x24 and the item still holds 69.054) -- so the frame's
   item height is content-independent, not content-driven.
   Scoped to `.rail-util` only (Group 3's three children) -- NOT
   `.rail .nav.tabs button` (nav-primary, frame group 321:220), which is
   untouched here and carries its own pre-existing vertical residue
   (HANDOFF_S13 §C, nav.rail `fix-later`) that this row does not own.
   Mechanism: drop the vertical padding: every item's own content stack
   (icon + 6px gap + label) measures under 69.054 even at 2 lines --
   kbd 32+6+24=62, settings 27+6+12=45, logout 32+6+12=50 -- so
   `justify-content:center` alone holds each at the min-height floor with
   no clipping, where the removed 12px top/bottom padding was pushing kbd
   to 86 and logout to 74. Horizontal 6px padding is untouched (governs
   width, not this row's property). */
.rail-util { padding: 0 var(--ra-sp-xs); }
/* K2 (S13 QA closing review, fix-now): two of the nine primary-tab labels
   -- Automations, Intelligence -- break MID-WORD in EN, the default
   locale (`Automatio/ns`, `Intelligenc/e`), because CD-767's
   `overflow-wrap:anywhere` (above) was measured against the ES label it
   was built for and this EN cost was never re-measured. NOT removing
   `overflow-wrap:anywhere` -- it stays load-bearing for CD-767's ES
   remedy, and its own comment already records `break-word` at zero
   effect; this changes a DIFFERENT property.
   Measured in place (temporarily forced single-line on the real button,
   not a disconnected clone, cross-checked against a fitting label --
   Watchlists' single-line width read 56.00px here, exactly QA's own
   figure): content box 59px (71 - 2x6 padding); Automations needs
   66.31px, Intelligence needs 62.61px. QA's own estimate ("roughly
   3-5px more") undershoots Automations specifically by ~2px -- the
   measured figure is the one this fix is built to, not the estimate.
   Scoped to `.rail .nav.tabs button` only -- NOT `.rail-util` (whose
   labels are all short and already single-line; widening its box too
   would be an unrequested, unmeasured side effect on a sibling group
   this row does not own, same discipline as CD-91 above kept the two
   groups apart in the other direction).
   Fix: horizontal padding 6px -> 1px per side, content box 71-2=69px --
   a 2.69px margin over Automations' measured need, 6.39px over
   Intelligence's, comfortably clear of both without cutting it to the
   exact boundary K3 (this same session) just measured the cost of. */
.rail .nav.tabs button { padding: var(--ra-sp-lg) var(--ra-xsp-1); }
.rail .nav.tabs button::before,
.rail-util::before {
  content: "";
  display: block;
  width: var(--ra-size-icon-2xl);
  height: var(--ra-size-icon-2xl);
  flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}
.rail .nav.tabs button:hover,
.rail-util:hover { color: var(--ra-text-primary); background: var(--ra-surface-card); }
.rail .nav.tabs button.on {
  /* design/DESIGN_SPEC.md §6.3: active label is text.inverse (pure white),
     not text.primary (off-white) — the icon's own accent-primary override
     is scoped separately below via ::before. */
  color: var(--ra-text-inverse);
  background: var(--ra-surface-chip);
}
/* T5/A8: the frame's active-tab ICON is accent.primary (#0FD4BB, sampled
   62px in the active-tab region), not text.primary — the label stays
   text.primary (§1.3 only specifies the chip background, not an icon
   colour, so this one detail comes from the frame's pixels, not its
   prose). Scoped to ::before specifically so the label text is unaffected
   by overriding `color` (which currentColor on the icon would otherwise
   just inherit from the button's own `color` above). */
.rail .nav.tabs button.on::before {
  background-color: var(--ra-accent-primary);
}
/* Per-tab icons — pure CSS, keyed off the data-tab shell/panels.js
   already sets on every button (static or dynamically mounted). No panels.js
   edit needed or made. */
.rail .nav.tabs button[data-tab=stream]::before       { -webkit-mask-image: var(--icon-nav-stream);       mask-image: var(--icon-nav-stream); }
.rail .nav.tabs button[data-tab=screener]::before     { -webkit-mask-image: var(--icon-nav-screener);     mask-image: var(--icon-nav-screener); }
.rail .nav.tabs button[data-tab=news]::before         { -webkit-mask-image: var(--icon-nav-news);         mask-image: var(--icon-nav-news); }
.rail .nav.tabs button[data-tab=composer]::before     { -webkit-mask-image: var(--icon-nav-composer);     mask-image: var(--icon-nav-composer); }
.rail .nav.tabs button[data-tab=automations]::before  { -webkit-mask-image: var(--icon-nav-automations);  mask-image: var(--icon-nav-automations); }
.rail .nav.tabs button[data-tab=watchlists]::before   { -webkit-mask-image: var(--icon-nav-watchlists);   mask-image: var(--icon-nav-watchlists); }
.rail .nav.tabs button[data-tab=intelligence]::before { -webkit-mask-image: var(--icon-nav-intelligence); mask-image: var(--icon-nav-intelligence); }
/* S36: Review occupies the Stats rail slot (design/PLAN_S36.md §6.3), with its icon. */
.rail .nav.tabs button[data-tab=review]::before       { -webkit-mask-image: var(--icon-nav-stats);        mask-image: var(--icon-nav-stats); }
.rail .nav.tabs button[data-tab=trading]::before      { -webkit-mask-image: var(--icon-nav-trading);      mask-image: var(--icon-nav-trading); }
.rail-util[data-rail=kbd]::before      { -webkit-mask-image: var(--icon-rail-kbd);      mask-image: var(--icon-rail-kbd); }
.rail-util[data-rail=settings]::before { -webkit-mask-image: var(--icon-rail-settings); mask-image: var(--icon-rail-settings); }
/* design-sync 2026-10-03 (FIGMA_TO_CODE «live area 20 of 24», Organisms rail `ic-svg`): every rail icon
   renders at 24 (`--ic-size` on the svg rule below) -- the CD-610 per-icon 32 / 27 / 23 boxes are gone.
   CD-90/91's HEIGHT question is a separate finding, NOT fixed by this
   alone, and deliberately not chased further: every seal/nav-item in the
   raw dump reports `layoutSizingVertical:"FIXED"` at 69.054,
   `primaryAxisAlignItems:"CENTER"`, and — critically —
   `clipsContent:true`. With the CORRECT per-icon size, several items'
   true content sum (padding 12+12, icon, gap 6, label) EXCEEDS 69.054:
   e.g. kbd's 2-line label alone makes 24(pad)+32(icon)+6(gap)+24(2-line
   text)=86. Figma is not silently rendering that at 86 — clipsContent
   centers it and CUTS OFF ~8.5px off the top and bottom of the icon/
   label. Icon top positions in the raw dump match this centred-and-
   clipped model to sub-pixel precision (verified: predicted 937.365 vs
   measured 937.3648 for kbd). Matching the frame's literal 69.054 would
   mean deliberately clipping part of the Settings icon and the
   "Keyboard Shortcuts" label in a LIVE product — worse UX than the
   current min-height (grows to fit, nothing clipped, siblings pushed
   down instead of overlapped). Reads as an authoring artifact (icons
   swapped to bigger assets, or a 2-line label added, without anyone
   updating the fixed-height component and Figma's own canvas never
   visually flags a clip) rather than an intentional 69.054 target — same
   species as CD-320/326/359, just on a size property instead of a
   string. Not unilaterally closing CD-90/91 on this — flagged for a
   product call rather than replicating a clip. */
/* S3-11 (design/PLAN.md §S3 D-S3-3 REVISED): foundation/icons.js's
   hydrateRail() inserts the real vector as the button's first child,
   inline; its `currentColor` stroke resolves off `color` below (--ra-text-axis,
   the frame's #606060). design-sync 2026-10-03: the icons are single-tone
   Lucide now, so the old 2- / 3-tone `.ic-fill` / `.ic-accent` rules are gone.
   Safety, per button, not rail-wide:
   hydrateRail() adds `.ic-hydrated` only to the button it succeeded on, so
   only THAT button's `::before` mask hides — a tab whose icon the registry
   doesn't know (or JS blocked) keeps its mask, and the rail cannot blank. */
.rail .nav.tabs button svg.ic-svg, .rail-util svg.ic-svg {
  --ic-size: var(--ra-size-icon-2xl);
  color: var(--ra-text-axis);
  flex-shrink: 0;
}
/* Fitts & Miller 09 (Organisms rail: "the hit areas touch, the 8 px gap belongs to each item, and they reach the screen's
   left edge"): once hydrated, an item's ::before is no icon but its hit area: half of each 4 px gap above and below, the
   rail's 3 px inset on the right, and on the left the inset, the frame's border and the workspace gutter. */
.rail .nav.tabs button.ic-hydrated, .rail-util.ic-hydrated { position: relative; }
.rail .nav.tabs button.ic-hydrated::before, .rail-util.ic-hydrated::before {
  position: absolute; width: auto; height: auto; background: none; -webkit-mask: none; mask: none;
  inset: calc(-1 * var(--ra-sp-3xs)) calc(-1 * var(--ra-sp-3)) calc(-1 * var(--ra-sp-3xs)) calc(-1 * (var(--ra-sp-sm) + var(--ra-border-hairline-w) + var(--ra-sp-3)));
}
/* The "×" remove glyph panels.js appends to removable (Composer) tabs
   inherits nav-item text sizing fine as inline content; no rule needed. */

/* The rail's Keyboard Shortcuts dialog (shell/layout/topbar.js __railShortcuts, D17): one row per key, the label left and
   its chords right; was an inline style in footer.js. */
.kbd-list { display: flex; flex-direction: column; gap: var(--ra-sp-sm); font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls); }
.kbd-row { display: flex; justify-content: space-between; align-items: center; gap: var(--ra-sp-xl); }
/* == ds/templates/pane.css == */
/* ------------------------------------------------------------
   Pane + grid layout
   ------------------------------------------------------------ */
.pane { flex: 1; min-height: 0; height: 100%; overflow: hidden; }
/* `[hidden]` stays the source of truth (PLAN_S31 §5 step A3's own
   constraint) — `.pane-exiting` below is a class ADDED ALONGSIDE it by
   shell/app.js's tab(), never a replacement. If the class mechanism ever
   fails to apply (script error, old cached JS), the pane simply keeps
   today's synchronous hide/show with no exit animation — it can never get
   stuck half-hidden, because `[hidden]` alone already fully controls
   visibility. */
.pane[hidden] { display: none; }
/* Outgoing pane, click path only (S31 N4). Opacity only — MOTION_REF §1
   class D: view transitions in the reference are plain opacity fades, no
   scale/translate. Duration/easing tokenized (§3.3 class B); reduce-motion
   collapses this to near-zero via the existing global mechanism. */
.pane.pane-exiting { opacity: 0; transition: opacity var(--mo-dur-exit) var(--mo-ease-exit); }
/* S33-R3.4 (task #60) MEASURED AND NOT SHIPPED -- see design/s33/dev_a/
   T60_pane_blur_not_shipped.md. Adding `filter: blur()` to THIS rule
   blurs every `.pane > *` mount, which is the WHOLE incoming tab's
   subtree, not one panel's region -- a live chart sitting anywhere inside
   costs 5-8fps and 2-5 frames >32ms on EVERY tab switch (paired
   counterfactual against this exact baseline, reproducible across 4
   trials), the same cost SHAPE UX-REVIEW-motion §3 already measured and
   rejected for whole-subtree filter (53.2fps/22 long frames). The
   architect's zero-cost Option B reading (CHART_REVEAL_OPTIONS.md §2) is
   for the CHART'S OWN WRAPPER (entrance.js, shipped below in
   components.css) -- a much smaller filtered area than this selector.
   "Measure ... report, do not assume" (#60's own brief) is why this half
   is filed rather than shipped; the ruling's chart-wrapper half is not
   affected. */
@keyframes pane-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.pane > * { animation: pane-in var(--mo-dur-view) var(--mo-ease-settle); }

/* == ds/atoms/signal-dot.css == */
/* widgets.css: charts, popovers, panel-specific helpers — split out of rally.css. */
/* ------------------------------------------------------------
   Traffic-light signal dots (screener)
   ------------------------------------------------------------ */
.sig-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--text-4);
  /* design/PLAN.md §3 D11: 0 effects across 5,639 paints — surfaces are
     separated by a border, not a shadow. This 0-blur/1px-spread ring WAS
     a border simulated as a box-shadow (no visual difference at 0 blur);
     drawn as a real border instead so it drops out of the shadow census
     with zero visual change. */
  border: 1px solid var(--line);
}
/* The .up/.down/.flat glows (6px/4px blur) deleted — design/DESIGN_SPEC.md
   §6.6/§3: "do not invent box-shadow", and these clearly are one (blur>0
   around a small dot is exactly the glow the frame measured zero of).
   The dot's own colour still carries the state. */
.sig-dot.up   { background: var(--pos); }
.sig-dot.down { background: var(--neg); }
.sig-dot.flat { background: var(--ra-k-h70-55-04); }

/* !important removed (design/DESIGN_BRIEF.md forbids it) by fixing the
   actual specificity conflict instead of overpowering it: both classes
   only ever land on a `<td class=num ...>` (panels/screener/gainers.js),
   where `table.data td` (0,1,2 — css/components.css) outranks a bare
   class (0,1,0). Same fix css/components.css already applies to
   .pos-c/.neg-c for the identical reason — this just extends the pattern
   instead of reaching for !important again. */
.tps-hot, table.data td.tps-hot   { color: var(--pos); font-weight: 600; }
.rvol-hot, table.data td.rvol-hot { color: var(--ra-k-h70-80-15); font-weight: 600; }

/* == ds/molecules/depth-ladder.css == */
/* ------------------------------------------------------------
   Depth ladder (horizontal — bids left / asks right)
   ------------------------------------------------------------ */
.depth-h-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--ra-sp-3xs);
  height: 18px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--ra-fs-10);
}
.depth-h-cell {
  position: relative;
  display: grid;
  align-items: center;
  height: 100%;
  padding: 0 var(--ra-sp-xs);
  overflow: hidden;
}
.depth-h-cell.bid { grid-template-columns: 1fr auto; text-align: left; }
.depth-h-cell.ask { grid-template-columns: auto 1fr; text-align: right; }
/* S31 PLAN §1.6/§2 (dev-b) — CORRECTED at task #28 (dev-a's motion_lint
   cross-check, EVIDENCE.md): B1 only retimed 0.2s -> --mo-dur-hover-in,
   which left `width` itself animating -- forbidden outright by §3.6
   ("Never width... on anything that shares a frame with a chart"), no
   named exception. The §3.6-correct fix is transform:scaleX() (bar
   growing from its fixed bid/ask edge instead of resizing), but that
   requires the JS that writes this bar to set a transform instead of
   `style.width` -- confirmed at panels/screener/depth.js:97,101
   (`bc.children[0].style.width=...`, `ac.children[0].style.width=...`),
   which is FROZEN this session (§4.2) and not editable from here.
   With a CSS-only fix required, the honest reading of §1.6's own
   finding wins anyway: the ladder writes at 2Hz, so ANY transition,
   however short, shows the bar lagging the book for part of every
   update -- exactly UX-REVIEW-motion §4's "do-not-animate" family
   (quote rows / NBBO / time-and-sales / P&L: a value rewritten faster
   than its own animation is wrong on screen most of the time). Removed
   outright rather than shortened further: the bar now reflects the live
   book with zero lag, which is more correct than any eased version,
   and the forbidden-property finding is gone because there is no
   longer a transition to flag. */
.depth-h-cell .bar { position: absolute; top: 1px; bottom: 1px; border-radius: 2px; z-index: 0; }
.depth-h-cell.bid .bar { right: 0; background: var(--ra-k-pos-40-10-a28); }
.depth-h-cell.ask .bar { left:  0; background: var(--ra-k-neg-40-12-a24); }
.depth-h-cell .px, .depth-h-cell .sz { position: relative; z-index: 1; }
.depth-h-cell.bid .px { color: var(--pos); font-weight: 400; text-align: right; padding-right: var(--ra-sp-3xs); }
.depth-h-cell.bid .sz { color: var(--text-2); text-align: left; }
.depth-h-cell.ask .px { color: var(--neg); font-weight: 400; text-align: left; padding-left: var(--ra-sp-3xs); }
.depth-h-cell.ask .sz { color: var(--text-2); text-align: right; }
/* Large-size highlight (>= 5000 shares at this level, threshold in
 * panels/screener/depth.js renderD4 BIG constant). Brighter bar + size text in the
 * side accent color (green for bids, red for asks) + bold weight so
 * institutional-sized orders stand out at a glance across 15 levels. */
.depth-h-cell.bid.big .bar { background: var(--ra-k-pos-55-18-a55); }
.depth-h-cell.ask.big .bar { background: var(--ra-k-neg-55-20-a50); }
.depth-h-cell.bid.big .sz { color: var(--pos); font-weight: 700; }
.depth-h-cell.ask.big .sz { color: var(--neg); font-weight: 700; }
.depth-h-cell.big .px { font-weight: 700; }

/* == ds/molecules/chart-toolbar.css == */
/* ------------------------------------------------------------
   Chart drawing toolbar (vertical, left side of chart)
   ------------------------------------------------------------ */
.ch-sb {
  position: absolute;
  left: 6px; top: 6px;
  display: flex;
  flex-direction: column;
  gap: var(--ra-sp-2xs);
  z-index: 2;
}
.ch-sb button { font: var(--ts-dropdown-menu-item); letter-spacing: var(--ts-dropdown-menu-item-ls); width: 24px; height: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--ra-k-bg-14-a70);
  border: 1px solid var(--line);
  color: var(--text-2);
  border-radius: 4px;
  cursor: pointer; }
.ch-sb button:hover { color: var(--text-1); }
.ch-sb button.on   { color: var(--info); border-color: var(--ra-k-info-55-14-a55); background: var(--info-bg); }

/* == ds/atoms/section-label.css == */
/* ------------------------------------------------------------
   Section helpers
   ------------------------------------------------------------ */
.section-label {
  display: flex;
  align-items: center;
  gap: var(--ra-sp-sm);
  font-size: var(--ra-fs-9);
  color: var(--text-4);
  text-transform: uppercase;
  letter-spacing: var(--ra-ls-em-08);
}
.section-label::before, .section-label::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}

.field { display: flex; flex-direction: column; gap: var(--ra-sp-2xs); min-width: 0; }
.field .flbl { font: var(--ts-caption-helper); letter-spacing: var(--ts-caption-helper-ls); color: var(--text-4);
  text-transform: uppercase; }

/* == ds/organisms/loading.css == */
/* ------------------------------------------------------------
   Loading overlay: the boot curtain (#loading, index.html, removed by shell/app.js when the boot chain ends) and the
   sign-out curtain (.logout-curtain, shell/session.js logout). Both are role=status + aria-busy (level 1 Loading);
   tokens only (D6 shell-b).
   ------------------------------------------------------------ */
#loading {
  position: fixed;
  inset: 0;
  background: var(--bg-0);
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: var(--ra-fs-12);
  letter-spacing: var(--ra-ls-em-30);
}
#loading > span { animation: lp var(--mo-dur-loading-pulse) ease-in-out infinite; }
@keyframes lp { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }

.logout-curtain {
  position: fixed;
  inset: 0;
  background: color-mix(in oklch, var(--bg-0) 88%, transparent);
  backdrop-filter: blur(var(--mo-scrim-blur));
  z-index: 99999;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-1);
  font-family: var(--font-mono);
  font-size: var(--ra-fs-13);
  letter-spacing: var(--ra-ls-em-12);
  text-transform: uppercase;
}
.logout-curtain > span { opacity: .6; }
/* == ds/organisms/hidden-strip.css == */
/* ------------------------------------------------------------
   The hidden strip (panels/screener/hidden-strip.js; Fitts & Miller 25 / 26, Organisms `screener-hidden-strip` 13:5815
   State=folded / expanded / all-hidden / Default): the pane's first row, 40 tall folded with its frame (1 + 7 + 24 + 7 +
   1), 8 above the grid; surface/panel, a border/card ring, radius md; its parts 18 apart: the tools (Column, Chart, 8
   apart), a rule, the hidden panels, then at the right a rule, the layout chip and its save state (4 apart), «Restore the
   N» and fold. Folded the hidden panels are the one «Hidden · N» menu; unfolded the row keeps its place and a drawer
   under it shows them by group (a label, the restore chips; 12 between groups, 8 inside), on as many lines as they need.
   The chips are the kit's (A.chip), the menu a kit button.
   ------------------------------------------------------------ */
.hidden-strip {
  display: flex; align-items: center; flex-wrap: wrap; flex: none;
  gap: var(--ra-sp-sm) calc(var(--ra-sp-sm) + var(--ra-sp-md));   /* 18 between its parts; 8 when a narrow pane wraps it */
  padding: var(--ra-sp-7) var(--ra-sp-sm) var(--ra-sp-7) var(--ra-sp-xs);
  margin-bottom: var(--ra-sp-sm);
  background: var(--ra-surface-panel);
  border: var(--ra-border-hairline-w) solid var(--ra-border-card);
  border-radius: var(--ra-radius-md);
  font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls);
}
.hidden-strip .hs-tools, .hidden-strip .hs-g, .hidden-strip .hs-right { display: flex; align-items: center; gap: var(--ra-sp-sm); flex: none; }
.hidden-strip .hs-hidden { display: flex; align-items: center; gap: var(--ra-sp-lg); min-width: 0; }
.hidden-strip .hs-right { align-self: stretch; margin-left: auto; }
.hidden-strip .hs-lay { display: flex; align-items: center; gap: var(--ra-sp-2xs); }
/* Figma `div`: the 1 px rule, the row's height. */
.hidden-strip .hs-div { align-self: stretch; width: 1px; flex: none; background: var(--ra-border-card); }
/* «Hidden · N» and the group labels: h6/table-header in text/secondary, the groups upper case. */
.hidden-strip .label { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); color: var(--ra-text-secondary); white-space: nowrap; }
.hidden-strip .hs-gl { text-transform: uppercase; }
/* lf-2: the restore chips' «+» is --info. The strip's tiny chips read text/primary, their icons and carets text/secondary. */
.hidden-strip .chip [data-icon="chart-plus"], .hs-pop .chip [data-icon="chart-plus"] { color: var(--info); }
.hidden-strip .chip-tiny { color: var(--ra-text-primary); }
.hidden-strip .chip-tiny > .ic { color: var(--ra-text-secondary); }
/* What each state shows (the owner, 2026-10-18): folded, the one row and its «Hidden · N» menu; unfolded, the same row
   with «Hidden · N» as a label and the drawer, a line of its own under it (8 apart) where every hidden panel shows by
   group, wrapping onto more lines 8 apart (a group wider than the strip wraps inside). The row never moves, so neither
   does the fold chip; the pane is a flex column (screener-pane.css), so the grid below gives up the drawer's height. */
.hidden-strip .hs-dd, .hidden-strip.folded :is(.hs-n, .hs-drawer) { display: none; }
.hidden-strip.folded .hs-dd { display: inline-flex; }
.hidden-strip .hs-drawer { flex: 1 0 100%; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ra-sp-sm) var(--ra-sp-lg); }
.hidden-strip .hs-drawer > .hs-g { flex-wrap: wrap; max-width: 100%; }
/* The save state (frame 25 «Estado del guardado»): words in caption/11, text/secondary; a failure in red with Retry
   (quiet, 11 / 500 as drawn). */
.hidden-strip .hs-save { display: inline-flex; align-items: center; gap: var(--ra-sp-sm); white-space: nowrap;
  font: var(--ts-caption-11); letter-spacing: var(--ts-h6-table-header-ls); color: var(--ra-text-secondary); }
.hidden-strip .hs-err { color: var(--ra-semantic-error); }
.hidden-strip .hs-save:empty { display: none; }   /* no state between saves: «Guardado» shows for saveFlash, then goes */
.hidden-strip .hs-save > .btn { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); }
/* Figma `screener-hidden-strip-popovers` Popover=hidden-list (287:32609): the Columns popover's shell (molecules/
   col-popover.css) at 204 wide with the popover shadow, scrolling past 480 (frame 26); 20 between its parts: the pop-head,
   the hairline (12 more above it), the groups (8 more above them; 12 apart, each a label over full-width restore chips 8
   apart), the hairline and «Restore the N». */
.ui-dd:has(> .col-popover.hs-pop) { width: 204px; max-height: 480px; box-shadow: var(--ra-shadow-popover); }
.col-popover.hs-pop { gap: calc(var(--ra-sp-xl) + var(--ra-sp-2xs)); }
.hs-pop-list { display: flex; flex-direction: column; gap: var(--ra-sp-lg); padding-top: var(--ra-sp-sm); }
.hs-pop-list + .cind-hairline { margin-top: 0; }
.hs-pop-g { display: flex; flex-direction: column; gap: var(--ra-sp-sm); }
.hs-pop-g > .label { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); text-transform: uppercase; color: var(--ra-text-secondary); }
.hs-pop-g .chip { width: 100%; }
.hs-pop > .btn { align-self: flex-start; }
/* Popover=layouts (755:5442): the kit's dropdown as it is, 280 wide; its heading «Layouts» (h6, upper case) over where
   they are kept (caption/11), 8 / 12 inside, both text/secondary. «Save as a new layout…» swaps in its form: the label,
   the input, the note, Save (solid) and Cancel, 8 apart. */
.hs-lay-head { padding: var(--ra-sp-sm) var(--ra-sp-lg); }
.ui-dd:has(> .hs-lay-head) > .ui-linkpop-hr { display: block; }   /* the kit's '-' rule is a span: a row of its own here */
.hs-lay-t { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); text-transform: uppercase; color: var(--ra-text-secondary); }
.hs-lay-s, .hs-saveas-l, .hs-saveas-n { font: var(--ts-caption-11); letter-spacing: var(--ts-caption-11-ls); color: var(--ra-text-secondary); }
.hs-saveas { display: flex; flex-direction: column; align-items: flex-start; gap: var(--ra-sp-sm); padding: var(--ra-sp-sm) var(--ra-sp-lg); }
.hs-saveas .input { width: 100%; }
.hs-saveas-a { display: flex; align-items: center; gap: var(--ra-sp-sm); }
/* == ds/molecules/col-popover.css == */
/* ------------------------------------------------------------
   Ganadoras' popovers: Columnas (the head's "Cols") and Filtros
   ------------------------------------------------------------ */
/* Figma sidebar-sections (acDx3hoowFphwB8tGtqIPi 3057:26785), built from the other pop-ups' own pieces:
   the Indicators dd's shell (surface.panel, the S11 modal-shell border / radius, 14/16 padding, blur),
   the shared .pop-head header and .cind-hairline (molecules/pop-head.css, indicators-dd.css). The shell is configured
   from this consumer (CD-748): the .ui-dd primitive's other consumers keep theirs; the hidden strip's list borrows it
   (organisms/hidden-strip.css). Rows are drag-reorderable (panels/screener/gainers-controls.js).
   Fitts & Miller 09.9 and the parity pass (Organisms screener-popovers Popover=cols 13:5523): 234 wide, the parts 12
   apart, the head with «N de M visibles» (the Sound Rules head's sub); a row is 30 tall (dropdown/item, 5 / 12 / 5 / 8):
   the checkbox first (atoms/checkbox.css), the label in text/primary, 12 apart; a fixed column is locked, its box dimmed
   and its label in text/muted (09.9: disabled rows really dim), with the hint «fija». Reset is the kit's quiet button. */
.ui-dd:has(> .col-popover) {
  width: 234px;
  background: var(--ra-surface-panel);
  border: var(--s11-modal-shell-border);
  border-radius: var(--s11-modal-shell-radius);
  padding: var(--ra-sp-14) var(--ra-sp-xl);
  max-height: 80vh;
  backdrop-filter: blur(12px);
}
.col-popover { display: flex; flex-direction: column; }
#scr-cols-pop { gap: var(--ra-sp-lg); }
#scr-cols-pop > .btn { align-self: flex-start; font-size: var(--ra-fs-11); line-height: var(--ra-lh-16); }
#scr-cols-list { padding-top: var(--ra-sp-sm); }
#scr-cols-list + .cind-hairline { margin-top: 0; }
.col-row {
  position: relative;
  display: flex; align-items: center;
  height: var(--ra-size-menu-item-sm); padding: 0 var(--ra-sp-lg) 0 var(--ra-sp-sm); border-radius: var(--ra-radius-xs);
  cursor: grab;
}
.col-row:hover { background: oklch(from var(--ra-text-primary) l c h / 0.04); }
.col-row.dragging { opacity: 0.45; }
/* The drop line sits ON the gap between two rows (centred on the shared edge), drawn by the one row
   that owns that gap (panels/screener/gainers-controls.js), so it holds still for as long as the slot stays the same. */
.col-row.drop-before::before, .col-row.drop-after::after {
  content: ""; position: absolute; left: 0; right: 0; height: 2px; background: var(--info); pointer-events: none;
}
.col-row.drop-before::before { top: -1px; }
.col-row.drop-after::after { bottom: -1px; }
.col-row label {
  display: flex; align-items: center; gap: var(--ra-sp-lg); flex: 1; min-width: 0;
  font: var(--ts-dropdown-menu-item); letter-spacing: var(--ts-dropdown-menu-item-ls);
  color: var(--ra-text-primary); cursor: pointer;
}
.col-row label > span:first-of-type { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.col-row label:has(input:disabled) { color: var(--ra-text-muted); cursor: default; }
.col-row .col-fixed { font: var(--ts-caption-10); letter-spacing: var(--ts-caption-10-ls); color: var(--ra-text-secondary); }
/* Reset in the Intelligence "Sidebar sections" footer (#td-cfg-reset, design-sync 2026-10-03): the frame's reset-action
   row, coloured like the other pop-ups' text actions (.snd-add). */
.col-reset {
  display: flex; align-items: center; gap: var(--ra-sp-xs);
  padding: var(--ra-sp-sm) 0 0; background: none; border: 0;
  font: var(--ts-caption-11); letter-spacing: var(--ts-caption-11-ls);
  color: var(--text-2); cursor: pointer; text-align: left;
}
.col-reset svg { flex: none; color: var(--ra-text-axis); }
.col-reset:hover, .col-reset:hover svg { color: var(--text-1); }
/* The Filtros popover (Fitts & Miller 04, Organisms screener-popovers Popover=filters 681:5200): the kit's popover shell
   300 wide (PV2.ui.dropdown `pop`, gainers-controls.js), its parts 24 apart -- the head, a rule, five named groups 16
   apart (a label 6 above its control; the price filter's two chunks stacked, molecules/price-filter.css), a rule, Reset. */
.gn-filters { display: flex; flex-direction: column; gap: var(--ra-sp-2xl); }
.gn-filters > .cind-hairline { margin-top: 0; }
.gn-filters > .btn { align-self: flex-start; font-size: var(--ra-fs-11); line-height: var(--ra-lh-16); }
.gn-filter-groups { display: flex; flex-direction: column; gap: var(--ra-sp-xl); }
.gn-fgroup { display: flex; flex-direction: column; align-items: flex-start; gap: var(--ra-sp-xs); }
/* == ds/molecules/indicators-dd.css == */
/* ------------------------------------------------------------
   Indicators dropdown (chart)
   ------------------------------------------------------------ */
/* CD-755 (S11.4.4): the frame (443:785 Indicators Panel, MODAL_SPEC §5.2)
   is 235 wide and 561 tall -- unlike CD-752/753, this height is a
   DECLARED CAP (max-height:360px), not content, so the app was clipping a
   561px design by declaration (measured: scrollHeight 412 > clientHeight
   358 at the pre-fix 360px cap, 13 rows). min-width raised to the frame's
   235; the fixed 360px cap replaced with the viewport-derived cap
   .cmode-pop already uses in this same app (screener.css:1221) rather
   than inventing a second answer -- MODAL_SPEC §9.7 records the frame
   itself says nothing about overflow for a 561px panel. overflow:auto
   kept so a short viewport still scrolls instead of clipping. Acceptance
   is NOT-CLIPPED (scrollHeight == clientHeight at the 1080 reference
   viewport), not "height == 561" -- the app's indicator list is not
   asserted to have the frame's 13 rows.
   S11-R18 (OWNER): radius/border now take the shared modal shell
   (css/components.css, beside .pill.live) instead of this rule's own
   local 6px/--line -- geometry acceptance above is unaffected.
   AMENDED S13-W12: the "not asserted to have the frame's 13 rows" clause
   above was true when written; re-measured at S13 (13 `_INDS` entries,
   10 overlay + 3 sub-pane, live click) the inventory NOW pairs 13-for-13,
   so the residual -145px is content rhythm, not a missing section --
   closed below via `.itm` padding, not a height literal. NOT-CLIPPED
   stays the acceptance bar; it still holds after this change.
   AMENDED AGAIN, S13-R28/R30: the frame moved under this ledger (as it did
   under CD-752/753's, S13-R28) -- `443:785` re-read today is 235x529, 9
   OVERLAYS + 3 SUB-PANES (12 rows, not the 13 above; the missing row is
   `ribbon`, escalated to the owner, not decided here). AND the "closed"
   total (560.625 vs 561) was a total covering two compensating errors --
   row +3.125 too tall, chrome -41 too short -- that a total-height check
   cannot see (S13-R30). Row rhythm re-fixed below to the frame's own
   32px/row, measured as its own term. Chrome is NOT re-fixed here -- see
   `w12_r30_chrome_before.json`/the row's dev_b evidence: the app's chrome
   does not structurally pair with the frame's five-piece breakdown (the
   frame draws a top-level 24px header + 1px hairline this dd never
   renders; the app carries a 43px Clear-all/Reset actions block the read
   frame node does not draw at all), so redistributing padding among the
   app's existing chrome elements to hit 145 in total would be tuning a
   second total one level down -- the same failure this amendment exists
   to name. Escalated, not guessed. */
.cind-dd { font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls); position: fixed;
  /* S17-B/CD-767 (POPUPS_S17B.md §2.3): opaque surface.panel, not the
     translucent shell tint -- this dd never had a S11-R18-style acceptance
     for background, so the frame wins outright. Token, not a literal hex
     (this file is migrationDebt -- the ratchet blocks a literal here). */
  background: var(--ra-surface-panel);
  border: var(--s11-modal-shell-border);
  border-radius: var(--s11-modal-shell-radius);
  /* CD-755 (S13-R31) + S17-B/CD-767 (POPUPS_S17B.md §2.3, re-verified live
     this session against a fresh REST read of 443:785): the frame's stroke
     is strokeAlign INSIDE -- it paints without consuming the auto-layout's
     own padding offset, so Figma's authored padding (14 top/bottom, 16
     left/right) IS the effective inset from the panel's outer edge. This
     rule's `border` is a real CSS border in border-box sizing, which DOES
     consume 1px of box space per side -- so matching the frame's effective
     inset means padding = (frame padding - 1px border), not the frame's
     raw padding number: 14-1=13 (unchanged, S13-R30 already got this
     right) and 16-1=15 (NEW -- S13/S13-R31 left this at 0, "the app's 0
     means rows run edge-to-edge", because no frame counterpart was known
     to be missing at the time). Verified: effectiveLeftPad measures 16
     (not 17) after this change -- design/s17/dev_a/POPUP_443-785.md.
     CD-S44-026 (component-auditor, re-measured 723:3919): supersedes the
     border-inset derivation above for THIS frame -- 14px/16px raw, no
     1px-border subtraction. Applied as measured; not re-derived here. */
  padding: var(--ra-sp-14) var(--ra-sp-xl);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  /* S17-B/CD-767: POPUPS_S17B.md §2.3 reads this container's raw CSS `gap`
     (0px) against the frame's top-level itemSpacing (12) and flags a
     delta. Re-verified live against a FRESH get_metadata read of 443:785
     (not a quote of the LEAD_RULINGS S17-R20-voided MODAL_SPEC row data):
     itemSpacing 12 is the frame's TOP-LEVEL gap between five children --
     header/hairline/OVERLAYS-label/row-list/SUB-PANES-label/row-list --
     while EACH row-list's own nested itemSpacing is 0 (9 rows at an exact
     32px pitch, 443:791..443:919, zero gap; confirmed again on the
     SUB-PANES row-list, 443:927..443:937). This dd's DOM is flat (`.hdr`,
     `.itm`x9, `.div`, `.hdr`, `.itm`x3, `.div`, `.actions` -- one level,
     no per-section wrapper), so a container-level `gap` cannot reproduce
     BOTH the top-level 12 and the nested 0 at once -- it would apply
     uniformly to all 17 children. `gap:0` here is deliberately correct:
     the 12px label->row-list seam is already realized via `.hdr`'s own
     margin-bottom (below), pixel-measured at exactly 12px live; row-to-row
     is pixel-measured at exactly 0px live, matching the frame's nested
     nesting. Setting `gap:12` would DOUBLE the header seam to 24px and
     insert a spurious 12px between every row (~+180px total height) --
     a regression, not a fix. NOT changed. Flagged to the lead, not
     silently dropped: design/s17/dev_a/POPUP_443-785.md.
     CD-S44-026 + OR-S44-11 (design/s44/OWNER_RULINGS_S44.md:310): still
     NOT a container `gap` for the reason above -- unchanged, still true
     with the `.div`/`.actions` deletions below (they don't add a
     per-section wrapper). The frame's top-level 12 is realised instead
     by `.cind-dd .hdr:not(:first-child)` (below), which now also carries
     the seam the deleted overlays/sub-panes hairline (CD-S44-031) used
     to provide -- row-to-row inside each list stays at 0. */
  gap: 0;
  white-space: nowrap;
  min-width: 235px;
  max-height: 80vh;
  overflow: auto;
  backdrop-filter: blur(12px); }
.cind-dd[hidden] { display: none; }
/* S17 #6 IND-01/02 (ELEMENT_DIFF_S17C.md §3): the frame draws a header
   (title + filled close chip) on every one of the five S17 pop-ups; this
   one never had it. The header itself (`.pop-head`) is a SHARED
   component now consumed by Modes/TFs/Replay (dev-a's markup,
   screener.js) and Indicators (this file's own indicators-dd.js) alike
   -- declared once at css/components.css (next to the modal-shell
   contract, same "declare once, never open a consumer" convention),
   not restated per pop-up.
   The divider below is every pop-up's now (owner, 2026-09-23 — Figma "seal/dec/hairline-added", 3057:26635 Modes /
   26746 TFs / 26785 Columns): under the header, 12 below the title; `.sec` between two sections, 16 below the last
   row (the next section label brings its own 16). Consumers: Indicators / Patterns (indicators-dd.js), Modes / TFs /
   Columns (screener.js), Replay Control (screener-rpl-pop.html). */
.cind-hairline { height: 1px; background: var(--ra-border-card); margin-top: var(--ra-sp-lg); flex: none; }
.cind-hairline.sec { margin-top: var(--ra-sp-xl); }
.cind-dd .hdr {
  /* CD-755 (S13-R31): bottom 2px -> 1px (16px measured section-label
     height, was 17 -- content is a fixed 11px, so padding-top4+bottom1=5
     lands exactly on the frame's 16). margin-bottom: 0 -> 12px, new --
     the frame leaves a 12px gap between a section label and its first
     row (443:785: OVERLAYS->rows1 and SUBPANES-label->rows2, both 12);
     this dd had none (.itm started immediately after .hdr, 0px). One
     rule serves both sections since both use .hdr. */
  /* S17 #6 IND-03 (ELEMENT_DIFF_S17C.md §3): frame is 11/500, ls 0.2px,
     --ra-surface-scrollbar-thumb (#737A85), not the 9/700 uppercase-heavy
     treatment this carried before. margin-bottom (the 12px header->row
     seam, CD-755 above) is unaffected -- untouched on purpose.
     S19 #7 (task #7, step D): the numbers this rule already carried are
     EXACTLY --ts-h6-table-header (tokens.css §2.3) -- reads the shared
     scale now instead of repeating them, zero visual change (the token's
     line-height, 16px, was previously unset/inherited from .cind-dd's own
     11px; content here is a single line so this is not observable). One
     more site off H4's "no type-scale layer" register. */
  padding: var(--ra-sp-2xs) var(--ra-sp-lg) var(--ra-xsp-1);
  margin-bottom: var(--ra-sp-lg);
  font: var(--ts-h6-table-header);
  letter-spacing: var(--ts-h6-table-header-ls);
  text-transform: uppercase;
  color: var(--ra-surface-scrollbar-thumb);
}
/* CD-S44-026 / CD-S44-031, OR-S44-11 (OWNER_RULINGS_S44.md:310): a section
   heading's top seam -- the frame's top-level 12px, applied here rather than
   as a `.cind-dd` flex `gap` (see the gap:0 comment above for why). The
   Overlays/Sub-panes hairline S44 deleted is back (owner, 2026-09-23: "Indicators
   is also missing the line") as `.cind-hairline.sec` (indicators-dd.js), so
   this 12 + the 4 padding-top is the 16 from that line to the label. */
.cind-dd .hdr:not(:first-child) { margin-top: var(--ra-sp-lg); }
.cind-dd .itm {
  cursor: pointer;
  color: var(--text-2);
  display: flex;
  align-items: center;
  /* S17 #6 IND-07: frame's checkbox sits 4px from the row edge with a
     12px gap to the label (app had it backwards -- 12px inset, 8px gap).
     Only the LEFT inset and the gap move; top/bottom/right are untouched
     (not flagged in ELEMENT_DIFF_S17C.md's own row). */
  gap: var(--ra-sp-lg);
  /* design-sync 2026-10-03 (Figma indicators-dd 943:24, `size/menu-item/md`): an option row is 36 tall,
     whatever its lead piece (Indicators' illustration, Patterns' tinted arrow, both 24 px) -- replaces the
     CD-755 / CD-S44-024 padding arithmetic against the old 32 px frame. */
  height: var(--ra-size-menu-item-md);
  padding: 0 var(--ra-sp-2xs);
  user-select: none;
}
/* S17 #6 IND-11: label 11px (inherited from .cind-dd's own font-size) ->
   13px, matching the frame. */
.cind-dd .itm .lbl { font-size: var(--ra-fs-13); }
/* design-sync 2026-10-03 (indicators-dd 943:24): an Indicators row has no checkbox -- its label is primary,
   blue (the Modes / TFs `.active` colour) while the indicator is on; the illustration keeps its own colour. */
.cind-dd .itm .ic-svg { flex: none; }
.cind-dd .itm.ico { --ic-size: var(--ra-size-icon-2xl); } /* the 24 px illustration (the base .ic-svg rule would size it 16) */
.cind-dd .itm.ico .lbl { color: var(--ra-text-primary); }
.cind-dd .itm.ico.on .lbl { color: var(--info); }
/* ST2 (CD-619, design/PLAN.md §U1): --ra-text-primary (#EEF1F1) is
   low-chroma sRGB — hygiene fix, this site's own painted delta measures 0
   (the error is imperceptible at 4% alpha), but it's the same idiom and the
   same audited class. */
.cind-dd .itm:hover { background: oklch(from var(--ra-text-primary) l c h / 0.04); color: var(--text-1); }
.cind-dd .itm.on { color: var(--text-1); }
/* S17 #6 IND-12: the frame does not bold a checked row (its 12 rows are
   all checked, all Inter 400) -- was 600. */
.cind-dd .itm.on .lbl { font-weight: 400; }
/* CD-S44-030/031, OR-S44-11 (OWNER_RULINGS_S44.md:310): `.cind-dd .div`
   (both hairlines) and `.cind-dd .actions` + its two buttons (Clear all,
   Reset) deleted here -- owner-ruled, no counterpart in frame 723:3919.
   Markup removed in indicators-dd.js; the rules themselves are gone, not
   just unused, so a stale selector can't quietly come back. */

/* == ds/molecules/price-filter.css == */
/* ------------------------------------------------------------
   Price filter (Platform price-filter 343:5): one component in both scanners
   ------------------------------------------------------------ */
/* Fitts & Miller 04 and the parity pass (2026-10-17/18): two chunks, price and TPS, 16 apart, each one dx segmented
   control (atoms/segmented.css) with its inputs 8 from it; the price buckets do not overlap (< $2, $2-20, > $20, Todos)
   and the TPS presets are Todos, +5, +20 in both scanners, so they filter the same way. The row (News Scan): the inputs
   before their presets, each as wide as its label (51 / 54 / 50, the input's own 0 / 10 padding). `.stacked` (the
   Ganadoras filters popover, variant 3153:1568): a label over each chunk, the range's two inputs 80 wide under the price
   presets with a dash between them, TPS+ under its presets. The inputs keep the kit input's own skin (owner,
   2026-10-18); their labels are 11 Regular. */
.price-filter { display: flex; align-items: center; gap: var(--ra-sp-xl); }
.price-filter .pf-group, .price-filter .pf-range { display: flex; align-items: center; gap: var(--ra-sp-sm); }
.price-filter .input::placeholder { font-size: var(--ra-fs-11); letter-spacing: var(--ra-ls-px-0-2); }
.price-filter .segmented button { font-size: var(--ra-fs-9); line-height: normal; letter-spacing: normal; }
#ns-range-presets button, #scr-ex button, #scr-sess button { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); }
.price-filter .pf-min { width: 51px; }
.price-filter .pf-max { width: 54px; }
.price-filter .pf-tps { width: 50px; }
.price-filter.stacked, .price-filter.stacked .pf-group { flex-direction: column; align-items: flex-start; }
.price-filter.stacked .input { width: 80px; }
/* The presets keep the app's type: 9, the News Scan range and Ganadoras' Bolsa / Sesion 11 Medium (h6).
   A stacked chunk's label, 11 Medium in capitals (h6); the Ganadoras popover's other groups wear it too. */
.pf-label { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); text-transform: uppercase; color: var(--ra-text-secondary); }
.pf-dash { font: var(--ts-caption-11); letter-spacing: var(--ts-caption-11-ls); color: var(--ra-text-secondary); }
/* News-heat filter chips -- HOT/WARM/COLD multi-select, the Ganadoras popover's Temperatura group, 8 apart (reuse .nb
   h/w/c colors + .snd-news-cat on/off styling); the NEWS+SPY badge follows them in news-only mode. */
#scr-news-cats { display: flex; gap: var(--ra-sp-sm); align-items: center; }
/* CD-134 (A2/G01, design/PLAN.md §U3): the chip label keeps the base .snd-news-cat/chip-tiny contract (11/500 ls0.2). */
#scr-news-cats .snd-news-cat { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); height: var(--ra-size-control-sm); padding: 0 var(--ra-sp-xs); }
/* == ds/molecules/signal-legend.css == */
/* ------------------------------------------------------------
   Ganadoras' active filters, one line (the row under the gainers head)
   ------------------------------------------------------------ */
/* Fitts & Miller 04 (Organisms screener-gainers 13:1151): the filters that apply, as chips (atoms/chip.css) at the old active-filter pills' 12
   8 apart, and the Filtros select at the end of the line -- the kit's .select with its label 13 Regular, one step over
   the chips (the note's «Filtros 13» on the app's scale); open, its border is accent/dark (Platform select State=open). The filters
   themselves are in the Filtros popover (molecules/col-popover.css). */
.active-row { display: flex; align-items: center; gap: var(--ra-sp-sm); min-width: 0; }
.active-row .active-chips { display: flex; gap: var(--ra-sp-sm); min-width: 0; overflow: hidden; }
.active-row .chip { font: var(--ts-caption-helper); font-weight: 500; letter-spacing: var(--ts-caption-helper-ls); }
.active-row .gn-filters-btn { margin-left: auto; flex: none; font: var(--ts-dropdown-menu-item); letter-spacing: var(--ts-dropdown-menu-item-ls);
  white-space: nowrap; cursor: pointer; }
.active-row .gn-filters-btn[aria-expanded="true"] { border-color: var(--ra-accent-dark); }
/* == ds/molecules/news-item.css == */
/* ------------------------------------------------------------
   News list
   ------------------------------------------------------------ */
/* Every row is the News panel's (#p3, ds/organisms/news-rows.css draws its grid, hover, time and headline); what
   reaches it from here is the row's type (inherited by the moves and the signal) and the headline's wrap. */
.news-item { font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls); }
.news-item .hdl  { overflow-wrap: anywhere; }
#scr-news-body .nx .h.pos { color: var(--pos); }
#scr-news-body .nx .h.neg { color: var(--neg); }

/* == ds/atoms/layout-utils.css == */
/* ------------------------------------------------------------
   Small utilities
   ------------------------------------------------------------ */
.row  { display: flex; align-items: center; }
.stack{ display: flex; flex-direction: column; }
.g-2 { gap: var(--ra-sp-sm); } .g-3 { gap: var(--ra-sp-lg); } .g-4 { gap: var(--ra-sp-xl); }

/* == ds/templates/stream-grid.css == */
/* Stream pane resizable grid. Splitter rows/cols (6px) replace the old gap;
   sizes are CSS vars, user preferences in the database, set by shell/layout/stream-splitters.js. */
.grid-stream {
  display: grid;
  grid-template-columns: var(--str-cl, 1fr) 6px var(--str-cr, 1fr);
  grid-template-rows:
    var(--str-chart, 2fr) 6px
    var(--str-data1, 1fr) 6px
    var(--str-data2, 1fr) 6px
    var(--str-log, 140px);
  gap: 0;
  height: 100%;
  padding: var(--ra-sp-sm);
  min-height: 0;
}
.grid-stream > .panel { min-height: 0; overflow: hidden; }
.grid-stream .panel.chart { grid-column: 1 / -1; grid-row: 1; overflow: hidden; }
.grid-stream .panel.chart canvas { flex: 1; width: 100%; display: block; min-height: 0; }
.grid-stream .p-d3 { grid-column: 1; grid-row: 3; }
.grid-stream .p-d1 { grid-column: 3; grid-row: 3; }
.grid-stream .p-d2 { grid-column: 1; grid-row: 5; }
.grid-stream .p-d4 { grid-column: 3; grid-row: 5; }
.grid-stream .panel.log { grid-column: 1 / -1; grid-row: 7; min-height: 0; }
.grid-stream .panel.log .panel-body { min-height: 0; flex: 1; overflow: auto; }
.grid-stream .str-row-split[data-r=chart] { grid-column: 1 / -1; grid-row: 2; }
.grid-stream .str-row-split[data-r=data1] { grid-column: 1 / -1; grid-row: 4; }
.grid-stream .str-row-split[data-r=data2] { grid-column: 1 / -1; grid-row: 6; }
.grid-stream .str-col-split[data-c=top] { grid-column: 2; grid-row: 3; }
.grid-stream .str-col-split[data-c=bot] { grid-column: 2; grid-row: 5; }

/* The platform's one handle face since 2026-10-11 (ds/atoms/drag-handle.css, the owner's edit of the screener's): no
   bar at rest, five dots in --ra-text-axis (the grey he asked for), the bar one grey step up on hover or drag (was a
   tinted bar, a blue hover and a 30 px line). */
.str-col-split, .str-row-split { transition: background var(--mo-dur-control); position: relative; }
.str-col-split { cursor: col-resize; }
.str-row-split { cursor: row-resize; }
.str-col-split::after, .str-row-split::after { content:""; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:3px; height:3px; border-radius:1px; background:var(--ra-text-axis); }
.str-col-split::after { box-shadow: 0 -14px 0 var(--ra-text-axis), 0 -7px 0 var(--ra-text-axis), 0 7px 0 var(--ra-text-axis), 0 14px 0 var(--ra-text-axis); }
.str-row-split::after { box-shadow: -14px 0 0 var(--ra-text-axis), -7px 0 0 var(--ra-text-axis), 7px 0 0 var(--ra-text-axis), 14px 0 0 var(--ra-text-axis); }
.str-col-split:hover, .str-col-split.dragging, .str-row-split:hover, .str-row-split.dragging { background: var(--line-strong); }

.log-body > div { font: var(--ts-label-stat); letter-spacing: var(--ts-label-stat-ls); padding: var(--ra-sp-3xs) var(--ra-sp-xs); }
.log-body .l { color: var(--text-4); }
.log-body .e { color: var(--neg); }

/* == ds/atoms/status-chip.css == */
/* Status pill for connection */
/* A7 geometry (design/DESIGN_SPEC.md §1.4, 321:117 status-connected): 92x28
   radius 14. Height only — 999px radius already yields a full pill at any
   height (28 -> 14px corners automatically), and width is content-driven
   (padding, not a hardcoded px): "Connected" lands near 92 at this padding,
   "Disconnected" is legitimately wider — that's state, not the defect
   design-qa measured (T5-PARITY-REPORT.md A7: only height/radius flagged,
   the colour token fix already closed separately).
   Fitts & Miller 09.9 (Platform status-chip 828:15, redlines hdr-15/16/17): 32 tall, radius card, its words upper case
   in text/secondary until a tone says otherwise, the ok tone on an accent tint. Its one vocabulary is the data's state:
   live, delayed by N min, no connection, and connecting while nothing is known (the top bar's feed chunk). */
.st { font: var(--ts-label-badge); letter-spacing: var(--ts-label-badge-ls); display: inline-flex;
  align-items: center;
  gap: var(--ra-sp-xs);
  height: var(--ra-size-control-md);
  padding: 0 var(--ra-sp-md);
  border: 1px solid var(--ra-border-card);
  border-radius: var(--ra-radius-card);
  white-space: nowrap;
  color: var(--ra-text-secondary);
  background: var(--ra-k-bg-18-a80);
  text-transform: uppercase; }
.st::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--text-2); }
/* A7 (routed via dev-a, PLAN.md §6 assigns widgets.css to dev-b): .ok used
   --pos, the market-up/hue-personalisable token — wrong FAMILY for a
   connection-status indicator, which DESIGN_SPEC §1.4's status-connected
   pattern ties to the fixed accent.primary, not to whatever hue the user
   has their candles set to. .warn/.bad are untouched — those genuinely
   are warning/error semantics. */
.st.ok   { color: var(--ra-accent-primary); border-color: var(--ra-accent-primary); background: color-mix(in srgb, var(--ra-accent-primary) 12%, transparent); }
.st.ok::before   { background: var(--ra-accent-primary); }
.st.warn { color: var(--warn); border-color: var(--ra-k-h85-55-12-a50); }
.st.warn::before { background: var(--warn); }
.st.bad  { color: var(--neg); border-color: var(--ra-k-neg-55-14-a50); }
.st.bad::before  { background: var(--neg); }

/* == ds/atoms/row-tints.css == */
/* semantic row tints in simple tables */
table .buy  { color: var(--pos); }
table .sell { color: var(--neg); }
table .neu  { color: var(--text-2); }

/* == ds/atoms/news-badge.css == */
/* News heat badges (used in gainers). CD-145/146/147 (G24): the in-row
   badge is a SECOND, DARKER skin than the filter chip above — 32x20 (was
   auto-sized off 9px type), r6 (was 3), pad 4/2 (was 1/4), Inter 500/11/
   ls0.2 (was 400/9/ls0.04em). Colour: dedicated state-hot and state-warm
   tokens (already materialised, tokens.css:129-134) replace the generic
   --neg/--warn hue borrow — measured for `h` (CD-147); `w` follows the
   same token family that exists for it, `c` is unmeasured and untouched.
   CD-146 (S10-T4): THIS COMMENT WAS THE BUG, two lines up. The old text
   wrote the two state names back to back around a bare slash, with no
   space on either side of the asterisk that ends "hot" and the slash
   that starts "state-warm" -- an asterisk immediately followed by a
   slash is the two-character sequence that ends a CSS comment, so it
   closed THIS comment early, right there. Everything after that point
   up to the comment's real end above became raw CSS text the parser
   could not resync into a rule, so it silently dropped the ENTIRE
   .nb block below rather than just misreading a value -- .nb.h / .nb.w
   / .nb.c parsed fine because they are separate rules the parser
   resynced onto after that. Confirmed live: .nb was absent from
   document.styleSheets entirely (318 parsed rules where the source has
   319), so span.nb.h rendered at the browser's default inline box
   (23.375 by 14, no radius) while .nb.h's own colour rules kept
   applying normally, unaffected. The declared values below were ALREADY
   CORRECT -- matching frame node 321:556 (32 by 20, radius 6, pad 4/2)
   exactly -- this was never a wrong-number defect. A space now sits on
   each side of that slash, two lines up, so the two characters can
   never land adjacent again; no property below was changed. */
.nb { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; height: var(--ra-size-badge-sm); box-sizing: border-box;   /* design-sync 2026-10-03: 20 -> 22, the badge scale */
  padding: var(--ra-sp-2xs) var(--ra-sp-3xs); border-radius: var(--ra-radius-sm); margin-left: var(--ra-sp-2xs);
  border: 1px solid transparent; }
.nb.h { background: var(--ra-state-hot-bg); border-color: var(--ra-state-hot-border); color: var(--ra-state-hot-fg); }
.nb.w { background: var(--ra-state-warm-bg); border-color: var(--ra-state-warm-border); color: var(--ra-state-warm-fg); }
.nb.c { background: var(--ra-k-info-30-10-a35); color: var(--info); }
/* News heat toggle chips inside a sound-rule condition (reuse .nb h/w/c
   colors) AND the NSD %GAINERS filter row (pane.html, CD-132-135) — one
   shared component, both consumers. */
.snd-news-cats { display: flex; gap: var(--ra-sp-2xs); align-items: center; flex: 1; flex-wrap: wrap; }
.snd-news-cat { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); margin-left: 0; height: var(--ra-size-control-sm); padding: 0 var(--ra-xsp-9); display: inline-flex; align-items: center;
  border: 1px solid var(--ra-surface-card-elevated); border-radius: var(--ra-radius-sm);
  cursor: pointer; transition: opacity var(--mo-dur-control); position: relative; }
/* The `.on` ring is this layer fading in (opacity only, PLAN_S31 §3.6): the same inset ring the chip drew on
   itself, on its padding box, so its radius is the chip's minus the hairline border. Nothing on this chip
   sets a filter but `none`, so no filter transition is needed. */
.snd-news-cat::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  border-radius: calc(var(--ra-radius-sm) - var(--ra-border-hairline-w));
  box-shadow: inset 0 0 0 1.5px currentColor; opacity: 0; transition: opacity var(--mo-dur-control); }
/* CD-132/133/134/135: the frame paints OFF chips one neutral fill/border/
   label (surface.card / card-elevated / text.secondary) at full opacity —
   "off" is not a dimmed/greyscaled version of the hue, it's a different,
   colourless skin. on = the .nb h/w/c hue + a ring, unchanged. */
/* CD-133 (A2/G01, design/PLAN.md §U3): `.nb.h`/`.nb.w` (below) set their
   own semantic border-color (hot/warm hue) unconditionally, at the SAME
   specificity (0,2,0) as this rule, and source order let them win —
   the OFF chip showed HOT/WARM's own hue as its border instead of the
   frame's neutral ghost-outline. `.nb.c` never sets border-color, which
   is why COLD already read correctly (same ghost-border class as CD-108).
   Declared AFTER .nb.h/.nb.w below would also win on source order alone,
   but the explicit override here makes the intent legible without relying
   on file position. */
/* The theme-aware aliases of the frame's surface.card / card-elevated / text.secondary: the same
   values in dark, and they flip in light (the --ra-* ones have no light value). */
.snd-news-cat.off {
  opacity: 1; filter: none;
  background: var(--bg-3); color: var(--text-2);
  border-color: var(--surf-hi);
}
.snd-news-cat.off:hover { color: var(--ra-text-hover); }
.snd-news-cat.on::after { opacity: 1; }
.snd-news-cat:hover { opacity: 1; filter: none; }

/* AI investor-interest badges — prefixed to news headlines.
   design-sync 2026-10-03 (Figma size/badge/xs): the ALTO / MEDIO / BAJO verdict is 16 tall, its text centred. */
.ai-badge { display: inline-flex; align-items: center; justify-content: center; height: var(--ra-size-badge-xs);
  font-size: var(--ra-fs-9); padding: 0 var(--ra-sp-5); border-radius: 3px; margin-right: var(--ra-sp-xs);
  font-weight: 600; letter-spacing: var(--ra-ls-em-04); white-space: nowrap; vertical-align: baseline; }
.ai-badge.hi { background: var(--ra-k-pos-55-20); color: var(--ra-text-inverse); }
.ai-badge.md { background: var(--ra-k-h70-30-10-a55); color: var(--warn); }
.ai-badge.lo { background: var(--ra-k-h260-28-04-a50); color: var(--text-2); }
.ai-badge.pending { background: var(--ra-k-bg-24-02-a70); color: var(--text-2);
  letter-spacing: var(--ra-ls-em-12); animation: aiPulse var(--mo-dur-pulse) infinite ease-in-out; }
@keyframes aiPulse { 0%,100%{opacity:0.45} 50%{opacity:1} }

/* == ds/molecules/news-ai.css == */
/* On-demand AI news assessment (window.NewsAI) — the Screener's per-symbol
   news list (.nx rows).
   Hover a row -> "AI Analyze" -> verdict chip -> click expands the full
   Squeeze / Investor Interest / Trap inline. */
.ai-slot { flex-shrink: 0; display: inline-flex; align-items: center; }
/* The verdict chip is a <button> now (click -> toggle analysis): reset the
   native chrome but keep the .ai-badge look above. */
button.ai-badge { font-family: inherit; border: 0; cursor: pointer; line-height: 1; }
/* "AI Analyze" — revealed on row hover / keyboard focus so it never
   clutters the dense feed. */
.ai-analyze-btn {
  flex-shrink: 0; white-space: nowrap; cursor: pointer;
  padding: var(--ra-sp-3xs) var(--ra-sp-sm); font-size: var(--ra-fs-9); font-weight: 600; letter-spacing: var(--ra-ls-em-04);
  border: 1px solid var(--line); border-radius: 4px;
  background: transparent; color: var(--text-2);
  opacity: 0; pointer-events: none;
  transition: opacity var(--mo-dur-control) ease, color var(--mo-dur-control), border-color var(--mo-dur-control);
}
.nx:hover .ai-analyze-btn,
.ai-analyze-btn:focus-visible { opacity: 1; pointer-events: auto; }
.ai-analyze-btn:hover { color: var(--text-1); border-color: var(--info); }
/* Inline analysis block (grid-column:1/-1 spans a grid row; harmless in the
   Screener's non-grid .nx list). */
.ai-analysis {
  grid-column: 1 / -1; position: relative;
  margin-top: var(--ra-sp-xs); padding: var(--ra-sp-md) var(--ra-xsp-28) var(--ra-sp-md) var(--ra-sp-lg);
  background: var(--ra-k-bg-20-a60);
  border: 1px solid var(--line); border-radius: 6px;
  display: flex; flex-direction: column; gap: var(--ra-sp-xs);
  font-size: var(--ra-fs-11); line-height: 1.5;
}
.ai-an-row { display: flex; gap: var(--ra-sp-md); align-items: baseline; }
.ai-an-lbl {
  flex: 0 0 auto; min-width: 96px;
  color: var(--text-4); font-family: var(--font-mono); font-size: var(--ra-fs-9);
  text-transform: uppercase; letter-spacing: var(--ra-ls-em-08);
}
.ai-an-txt { flex: 1 1 auto; color: var(--text-1); white-space: normal; }
.ai-an-int { font-weight: 700; letter-spacing: var(--ra-ls-em-04); }
.ai-an-int.hi { color: var(--pos); }
.ai-an-int.md { color: var(--warn); }
.ai-an-int.lo { color: var(--text-2); }
.ai-an-close {
  position: absolute; top: 4px; right: 6px;
  background: transparent; border: 0; cursor: pointer;
  color: var(--text-4); font-size: var(--ra-fs-14); line-height: 1; padding: var(--ra-sp-3xs) var(--ra-sp-2xs);
}
.ai-an-close:hover { color: var(--text-1); }
/* The standing research-thesis row — set off from the news-only verdict
   above it with a hairline, since it comes from the Intelligence module. */
.ai-an-intel { margin-top: var(--ra-sp-3xs); padding-top: var(--ra-sp-sm); border-top: 1px solid var(--line); }
.ai-an-intel .ai-an-lbl { color: var(--info); }
.ai-an-bias {
  display: inline-block; margin-left: var(--ra-sp-xs); padding: var(--ra-xsp-chip-y) var(--ra-sp-xs); border-radius: 3px;
  background: var(--ra-k-h260-28-04-a50); color: var(--text-2);
  font-family: var(--font-mono); font-size: var(--ra-fs-9); letter-spacing: var(--ra-ls-em-04);
  text-transform: uppercase; white-space: nowrap;
}

/* == ds/molecules/tape-rows.css == */
/* ============================================================
   D3 TRADES — row color coding (bullish / bearish)
   ============================================================ */
table.t3 tr.buy  td { color: var(--pos); background: var(--ra-k-pos-50-14-a6); }
table.t3 tr.sell td { color: var(--neg); background: var(--ra-k-neg-50-16-a6); }
table.t3 tr.neu  td { color: var(--text-2); }

/* D4 order book — strong highlight on large (>=10k) resting orders, both sides */
table.t4 tr.big.buy  td { background: var(--ra-k-pos-50-20-a28); font-weight: 600; color: var(--pos); box-shadow: inset 3px 0 0 var(--pos); }
table.t4 tr.big.sell td { background: var(--ra-k-neg-50-22-a28); font-weight: 600; color: var(--neg); box-shadow: inset 3px 0 0 var(--neg); }

/* == ds/molecules/big-trades-slider.css == */
/* ============================================================
   BIG-TRADES aggression slider — appears when B (bigTrades) is on
   Fitts & Miller (page 7's note, Organisms screener-chart-head bubble-thresh 13:1036): the bubble floor reads
   «≥ $250k», 24 tall on the tape surface with a card hairline, r4; «≥» 12 Regular in text/secondary, a 50 wide track,
   the value Semi Bold in text/primary at the app's Label/Stat (the app's format, btFmtUsd).
   ============================================================ */
.bubble-thresh {
  display: inline-flex; align-items: center; gap: var(--ra-sp-xs);
  height: var(--ra-size-control-sm); padding: 0 var(--ra-sp-sm);
  background: var(--ra-surface-tape); border: 1px solid var(--ra-surface-card);
  border-radius: var(--ra-radius-xs);
}
.bubble-thresh-lbl { font: var(--ts-caption-helper); letter-spacing: var(--ra-ls-em-08); color: var(--ra-text-secondary); }
.bubble-thresh-val { font: var(--ts-label-stat); font-weight: 600; letter-spacing: var(--ts-label-stat-ls); color: var(--ra-text-primary); min-width: 40px; text-align: right; }
.bubble-thresh-slider {
  -webkit-appearance: none; appearance: none;
  width: 50px; height: 3px; background: var(--ra-k-h240-30-01);
  border-radius: 2px; outline: none; cursor: pointer;
}
.bubble-thresh-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--ra-k-info-72-14);
  border: 1px solid var(--ra-k-info-85-14);
  cursor: pointer;
}
.bubble-thresh-slider::-moz-range-thumb {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--ra-k-info-72-14);
  border: 1px solid var(--ra-k-info-85-14); cursor: pointer;
}

/* == ds/organisms/signals-drawer.css == */
/* ============================================================
   SIGNALS — the alerts drawer, the alert cards and the bell's dot
   D16 (2026-10-05): tokens only. Fitts & Miller (Organisms shell-popovers Popover=signals-drawer 380 x 418, alert-row
   729:743; proposal frame 17): a panel under the top bar, 16 from the right edge, with a left rule, hugging its alerts up
   to 640 and scrolling after: the head (count, new ones, sort, close), the chips, the alerts, the actions.
   ============================================================ */
.sig-drawer {
  position: fixed; top: calc(var(--ra-size-bar-top) + var(--ra-sp-2xs)); right: var(--ra-sp-xl); z-index: 90;
  width: 380px; max-height: min(640px, calc(100vh - var(--ra-size-bar-top) - var(--ra-sp-2xl)));
  display: none; flex-direction: column;
  background: var(--bg-1); border-left: 1px solid var(--ra-border-card); box-shadow: var(--ra-shadow-popover);
}
.sig-drawer.open { display: flex; animation: sigDrop var(--mo-dur-menu) var(--mo-ease-settle); }
@keyframes sigDrop { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.sig-drawer [hidden] { display: none; }   /* the empty face, the list and the foot swap by the hidden attribute */
.sig-drawer-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ra-sp-sm); flex: none;
  padding: var(--ra-sp-md) var(--ra-sp-lg); border-bottom: 1px solid var(--ra-border-card); background: var(--bg-0);
}
.sig-drawer-title { font: var(--ts-h6-table-header); font-weight: 400; letter-spacing: var(--ts-eyebrow-ls); color: var(--ra-text-primary); }
.sig-head-actions { display: flex; align-items: center; gap: var(--ra-sp-sm); }

/* A chip per rule with its count; the current one is the selected row's fill and 600 (frame 17's note). */
.sig-feed-filters {
  display: flex; flex-wrap: wrap; gap: var(--ra-sp-sm); flex: none;
  padding: var(--ra-sp-md) var(--ra-sp-lg); border-bottom: 1px solid var(--ra-border-card);
}
.sig-chip {
  height: var(--ra-size-control-sm); max-width: 100%; padding: 0 var(--ra-sp-sm);
  border: 0; border-radius: var(--ra-radius-sm); background: var(--ra-surface-card); color: var(--ra-text-secondary);
  font: var(--ts-caption-10); line-height: normal; letter-spacing: var(--ts-label-status-upper-ls); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;
  transition: color var(--mo-dur-hover-in);
}
.sig-chip:hover { color: var(--ra-text-primary); }
.sig-chip.on { background: var(--ra-state-row-hover); font-weight: 600; }
.sig-feed-empty { display: flex; flex-direction: column; align-items: center; gap: var(--ra-sp-xs); padding: var(--ra-xsp-40) var(--ra-xsp-20); }
.sig-feed-empty-title { font: var(--ts-body-small); line-height: normal; color: var(--text-2); letter-spacing: var(--ts-tag-ls); text-transform: uppercase; }
.sig-feed-empty-sub { font: var(--ts-caption-11); line-height: normal; color: var(--text-4); text-align: center; }

/* An alert, two lines: ticker, move and price; the rule and its age. Unseen: the dot, the tape fill and a 600 age (a seen
   row starts its lines where the ticker is). A row opens its chart. */
.sig-feed-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.sig-row {
  width: 100%; display: flex; flex-direction: column; gap: var(--ra-sp-3xs);
  padding: var(--ra-sp-sm) var(--ra-sp-lg) var(--ra-sp-sm) var(--ra-xsp-26);
  border-bottom: 1px solid var(--ra-border-card); text-align: left;
  transition: background var(--mo-dur-hover-in);
}
.sig-row.unseen { padding-left: var(--ra-sp-lg); background: var(--bg-2); }
.sig-row:hover { background: var(--ra-state-row-hover); }
.sig-row-l1, .sig-row-l2 { display: flex; align-items: center; gap: var(--ra-sp-sm); min-width: 0; }
.sig-row-l1 { font: var(--ts-caption-11); line-height: normal; color: var(--ra-text-secondary); }
.sig-row-l2 { justify-content: space-between; font: var(--ts-caption-10); line-height: normal; color: var(--ra-text-secondary); }
.sig-row .sig-row-dot { display: none; flex: none; }   /* over the dot atom's inline-block */
.sig-row.unseen .sig-row-dot { display: inline-block; }
.sig-row-sym { font-weight: 600; letter-spacing: var(--ts-h6-table-header-ls); color: var(--ra-text-primary); }
.sig-row-move { font-weight: 600; }
.sig-row-rule { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sig-row-age { flex: none; }
.sig-row.unseen .sig-row-age { font-weight: 600; color: var(--ra-text-primary); }
.sig-feed-foot {
  display: flex; justify-content: flex-end; align-items: center; gap: var(--ra-sp-sm); flex: none;
  padding: var(--ra-sp-sm) var(--ra-sp-lg); border-top: 1px solid var(--ra-border-card); background: var(--bg-0);
}

/* The alert card and the Composer's preview card sit in the kit's toast lane (#ui-toasts, ui-kit.css; inventory L60):
   right-aligned at one width (every card alike, as the old stack stretched them to its widest), and a card that leaves
   (the toastAlert table drops .on) fades like a toast. */
#ui-toasts > .sig-alert { align-self: flex-end; width: 320px; transition: opacity var(--mo-dur-toast); }
#ui-toasts > .sig-alert:not(.on):not(.preview) { opacity: 0; }
.sig-alert {
  pointer-events: auto; position: relative;
  background: linear-gradient(180deg, var(--bg-1), var(--bg-0));
  border: 1px solid var(--line-strong); border-radius: var(--r);
  padding: var(--ra-sp-lg) var(--ra-sp-lg) var(--ra-sp-lg) var(--ra-xsp-18);
  display: grid; grid-template-columns: 1fr auto; gap: var(--ra-sp-md);
  /* S31 PLAN §2/§3.3 (dev-b): same house-curve promotion as the drawer
     above (--mo-ease-settle); 260ms -> the class-B tier, --mo-dur-view. */
  animation: sigSlideIn var(--mo-dur-view) var(--mo-ease-settle); overflow: hidden;
}
@keyframes sigSlideIn { from { opacity: 0; transform: translateX(16px) scale(0.96); } to { opacity: 1; transform: translateX(0) scale(1); } }
/* box-shadow deleted (design/PLAN.md §3 D11), not just its glow layers:
   the "0 0 0 1px <hue>/0.25" ring layer looked like the kept case (a flat
   line), but it's redundant here — .sig-alert already carries a real
   `border: 1px solid var(--line-strong)` and this rule already changes
   border-color to the same hue two lines up, so the ring duplicated a
   colour change the border property was already making. Removing the
   whole declaration drops the two real glow layers AND the redundant
   ring with no loss: border-color alone still carries the up/down state. */
.sig-alert.up   { border-color: var(--ra-sig-alert-up-line); }
.sig-alert.down { border-color: var(--ra-sig-alert-down-line); }
.sig-alert-accent { position: absolute; left: 0; top: 0; bottom: 0; width: 4px; border-radius: var(--r) 0 0 var(--r); }
/* The 16px halo around this bar deleted; the bar itself (drawn by
   `background`, not by the shadow) is untouched — it's the D11 "flat
   accent line" case, the glow bleeding outward from it is not. */
.sig-alert.up   .sig-alert-accent { background: linear-gradient(180deg, var(--ra-sig-accent-up), var(--pos)); }
.sig-alert.down .sig-alert-accent { background: linear-gradient(180deg, var(--ra-sig-accent-down), var(--neg)); }
.sig-alert-body { min-width: 0; position: relative; z-index: 1; }
.sig-alert-row1 { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: var(--ra-sp-xs); }
.sig-alert-sig { font: var(--ts-caption-10); font-weight: 700; line-height: normal; text-transform: uppercase; letter-spacing: var(--ts-eyebrow-ls); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sig-alert.up   .sig-alert-sig { color: var(--pos); }
.sig-alert.down .sig-alert-sig { color: var(--neg); }
.sig-alert-time { font: var(--ts-caption-tight); line-height: normal; color: var(--text-2); letter-spacing: var(--ts-tag-ls); }
.sig-alert-row2 { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: var(--ra-sp-2xs); gap: var(--ra-sp-sm); }
.sig-alert-sym { font-size: var(--ra-xfs-18); font-weight: 700; color: var(--text-1); letter-spacing: var(--ts-label-badge-ls); }
/* the -0.01em tracking is .num's (ds/base/reset.css): the figure carries that class */
.sig-alert-pct { font: var(--ts-h4-card-subtitle); font-weight: 700; line-height: normal; }
.sig-alert-row3 { display: flex; justify-content: space-between; font: var(--ts-caption-11); line-height: normal; color: var(--text-2); }
.sig-alert-actions { display: flex; flex-direction: column; gap: var(--ra-sp-2xs); align-items: stretch; justify-content: center; position: relative; z-index: 1; }
/* line-height 1 is .btn's own (ds/atoms/button.css), kept under the type style */
.sig-alert-actions .btn.sm { height: 26px; padding: 0 var(--ra-sp-lg); font: var(--ts-caption-11); font-weight: 600; line-height: 1; }
/* S27 H4 #1 (24px floor): the Dismiss icon button was pinned to 20px, under
   the floor. It has to restate 24px explicitly rather than just dropping the
   declaration -- the .btn.sm rule directly above (same file, `.sig-alert-
   actions .btn.sm`) also matches this element and outranks the house
   `.btn.sm` (24px) at higher specificity with its own 26px for the "Open"
   text button in this same action column, so an undeclared height here would
   inherit 26, not the house's 24. 24px matches the house `.btn.sm` value
   exactly -- not a bigger local literal. Width also matches the house
   `.btn.sm.icon` value (24px, components.css) for a square icon control. */
.sig-alert-actions .btn.icon.sm { width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; padding: 0; }

/* The bell's unseen dot (Figma `bell · unseen dot`, frame 17: the alert rows' 6 px dot) on its top-right corner, ringed with
   the bar's fill; the bell itself is the kit's .btn.on while the drawer is open (features/signals/signals.js). */
.topbar-bell-wrap { position: relative; display: inline-flex; }
/* the kit's on face (Platform button State=on: accent at 12 %, an accent ring; the glyph keeps the bar's grey) over the top
   bar's icon tint (app-topbar.css) */
#sigBell.on { background: color-mix(in oklch, var(--ra-accent-primary) 12%, transparent); border-color: var(--ra-accent-primary); color: var(--ra-text-axis); }
.topbar-bell-badge {
  position: absolute; top: 1px; right: 1px; width: 6px; height: 6px; border-radius: 50%;
  background: var(--pos); box-shadow: 0 0 0 1.5px var(--bg-1); pointer-events: none;
}
/* == ds/organisms/price-alert.css == */
/* ds/organisms/price-alert.css — the price-alert dialog's body (features/alerts/price-alert.js, PV2.ui.modal with its head):
   the price field and its label, the line that says what will happen, the symbol's waiting alerts (list rows, each with its
   24 px delete) and the kit's footer. No Figma frame: kit pieces and tokens only. */
.pa-dlg { display: flex; flex-direction: column; gap: var(--ra-sp-sm); }
.pa-field { display: flex; flex-direction: column; gap: var(--ra-sp-2xs); }
.pa-lbl { font-size: var(--ra-fs-11); color: var(--ra-text-tertiary); }
.pa-echo { font-size: var(--ra-fs-12); color: var(--ra-text-secondary); }
.pa-row { justify-content: space-between; color: var(--ra-text-primary); }
.pa-none { font-size: var(--ra-fs-12); color: var(--ra-text-tertiary); padding: var(--ra-sp-3xs) var(--ra-sp-xs); }
.pa-dlg > .ui-modal-ft { padding-top: var(--ra-sp-xs); }
/* == ds/atoms/text.css == */
/* ds/atoms/text.css — text roles (S51 WP6-A, 2026-09-14).
   One class per named text style of the type scale (tokens/tokens.css §1c): the class IS the token pair
   (`font: var(--ts-X); letter-spacing: var(--ts-X-ls)`), so editing the token moves every site that
   carries the role, and editing the role class moves them all the same way. Roles never set colour:
   ink comes from ds/atoms/ink.css (`.ink-1 … .ink-4`, `.pos-c/.neg-c`, `.info-c` …), so one role
   composes with any tone. Modifiers (after the roles on purpose: same specificity, later wins):
   `.upper` uppercase, `.mono` the mono family, `.w4 .w5 .w6 .w7` weight, `.num` (ds/base/reset.css)
   tabular figures. Two roles are app-minted for S51 WP6 (see the tokens' own comment): `.t-caption-10`,
   the 10 px step the panels wrote as a literal in ~150 rules, and `.t-eyebrow`, the 9 px tracked
   uppercase label they wrote in ~40. Tokens only; no literal lives here. */
.t-h2-section         { font: var(--ts-h2-section);         letter-spacing: var(--ts-h2-section-ls); }
.t-h3-card-title      { font: var(--ts-h3-card-title);      letter-spacing: var(--ts-h3-card-title-ls); }
.t-h4-card-subtitle   { font: var(--ts-h4-card-subtitle);   letter-spacing: var(--ts-h4-card-subtitle-ls); }
.t-h5-widget-title    { font: var(--ts-h5-widget-title);    letter-spacing: var(--ts-h5-widget-title-ls); }
.t-h6-table-header    { font: var(--ts-h6-table-header);    letter-spacing: var(--ts-h6-table-header-ls); }
.t-body-large         { font: var(--ts-body-large);         letter-spacing: var(--ts-body-large-ls); }
.t-body-medium        { font: var(--ts-body-medium);        letter-spacing: var(--ts-body-medium-ls); }
.t-body-small         { font: var(--ts-body-small);         letter-spacing: var(--ts-body-small-ls); }
.t-caption-helper     { font: var(--ts-caption-helper);     letter-spacing: var(--ts-caption-helper-ls); }
.t-caption-tight      { font: var(--ts-caption-tight);      letter-spacing: var(--ts-caption-tight-ls); }
.t-caption-11         { font: var(--ts-caption-11);         letter-spacing: var(--ts-caption-11-ls); }
.t-caption-11m        { font: var(--ts-caption-11m);        letter-spacing: var(--ts-caption-11m-ls); }
.t-caption-10         { font: var(--ts-caption-10);         letter-spacing: var(--ts-caption-10-ls); }
.t-caption-9          { font: var(--ts-caption-9);          letter-spacing: var(--ts-caption-9-ls); }
.t-label-form         { font: var(--ts-label-form);         letter-spacing: var(--ts-label-form-ls); }
.t-label-badge        { font: var(--ts-label-badge);        letter-spacing: var(--ts-label-badge-ls); }
.t-label-stat         { font: var(--ts-label-stat);         letter-spacing: var(--ts-label-stat-ls); }
.t-label-nav-tab      { font: var(--ts-label-nav-tab);      letter-spacing: var(--ts-label-nav-tab-ls); }
.t-tag                { font: var(--ts-tag);                letter-spacing: var(--ts-tag-ls); }
.t-button-small       { font: var(--ts-button-small);       letter-spacing: var(--ts-button-small-ls); }
.t-button-medium      { font: var(--ts-button-medium);      letter-spacing: var(--ts-button-medium-ls); }
.t-dropdown-menu-item { font: var(--ts-dropdown-menu-item); letter-spacing: var(--ts-dropdown-menu-item-ls); }
.t-dropdown-icon-label{ font: var(--ts-dropdown-icon-label);letter-spacing: var(--ts-dropdown-icon-label-ls); }
.t-eyebrow            { font: var(--ts-eyebrow);            letter-spacing: var(--ts-eyebrow-ls); text-transform: uppercase; }

/* modifiers */
.upper { text-transform: uppercase; }
.mono  { font-family: var(--font-mono); }
.w4 { font-weight: 400; }
.w5 { font-weight: 500; }
.w6 { font-weight: 600; }
.w7 { font-weight: 700; }
/* == ds/atoms/ink.css == */
/* ds/atoms/ink.css — text inks (S51 WP6-A, 2026-09-14).
   The colour half of a text role: the four text ranks and the semantic tones. TWO families on purpose:
   `.ink-1 … .ink-4` are the legacy ranks `--text-1 … --text-4` (in dark they alias `--ra-text-primary …
   -faint`; in light, ds/themes/light.css gives them their own contrast-tuned values), and `.ink-primary …
   .ink-faint` are the `--ra-text-*` tokens themselves. A site keeps the family it had, so nothing moves
   in either theme; unifying the two light palettes is one token edit for the owner to take, not a
   migration's. The market pair `.pos-c` / `.neg-c` predates this file and stays where it is
   (ds/molecules/table.css) so its cascade position does not move; it is the same atom. Tokens only. */
.ink-1   { color: var(--text-1); }
.ink-2   { color: var(--text-2); }
.ink-3   { color: var(--text-2); }
.ink-4   { color: var(--text-4); }
.ink-primary   { color: var(--ra-text-primary); }
.ink-secondary { color: var(--ra-text-secondary); }
.ink-tertiary  { color: var(--ra-text-tertiary); }
.ink-faint     { color: var(--ra-text-faint); }
.ink-dim { color: var(--ra-text-dim); }
.info-c        { color: var(--info); }
.warn-c        { color: var(--warn); }
.accent-c      { color: var(--ra-accent-primary); }
.accent-soft-c { color: var(--ra-accent-primary-soft); }
.down-muted-c  { color: var(--ra-market-down-muted); }
/* == ds/atoms/surface.css == */
/* ds/atoms/surface.css — surfaces, strokes, radii, dividers (S51 WP6-A, 2026-09-14).
   `.surface` paints one of the named surface tokens (default: panel); `.stroked` draws the hairline
   in one of the named border tokens; `.r-*` rounds to one of the named radii; `.divider` is the
   hairline between blocks. A card is the composition `surface-card stroked r-sm`, an inset field
   `surface-inset r-xs`, a hover row's fill `.lrow` (ds/molecules/list-row.css). Every state is ONE
   complete, namespaced class (`.surface-card`, `.stroked-strong`, `.divider-v`), never a compound:
   an element tagged with a bare `chip` or `strong` beside `surface` would also match the kit's
   `.chip` and the panels' `.strong` (the trading range bar became a chip that way). Tokens only
   (the hairline width is `--ra-border-hairline-w`). */
.surface          { background: var(--ra-surface-panel); }
.surface-base     { background: var(--ra-surface-base); }
.surface-header   { background: var(--ra-surface-header); }
.surface-tape     { background: var(--ra-surface-tape); }
.surface-card     { background: var(--ra-surface-card); }
.surface-elevated { background: var(--ra-surface-card-elevated); }
.surface-inset    { background: var(--ra-surface-inset); }
.surface-chip     { background: var(--ra-surface-chip); }
.surface-field    { background: var(--ra-surface-field); }
.surface-plot     { background: var(--ra-surface-plot); }
.surface-deepest  { background: var(--ra-surface-deepest); }
/* the legacy ranks: themes/light.css gives --bg-0 and --bg-1 their own values (--ra-surface-base is not
   redefined there, --ra-surface-panel is another value), so a site on --bg-0/--bg-1 keeps the rank it had -- the same
   reason ink.css has two families. --bg-2/--bg-3 are true aliases in both themes: .surface-tape / .surface-card. */
.surface-0        { background: var(--bg-0); }
.surface-1        { background: var(--bg-1); }

.stroked           { border: var(--ra-border-hairline-w) solid var(--ra-border-default); }
.stroked-strong    { border: var(--ra-border-hairline-w) solid var(--ra-border-strong); }
.stroked-card      { border: var(--ra-border-hairline-w) solid var(--ra-border-card); }
.stroked-track     { border: var(--ra-border-hairline-w) solid var(--ra-border-track); }
.stroked-dashed    { border: var(--ra-border-hairline-w) dashed var(--ra-border-default); }

.r-3xs  { border-radius: var(--ra-radius-3xs); }
.r-2xs  { border-radius: var(--ra-radius-2xs); }
.r-xs   { border-radius: var(--ra-radius-xs); }
.r-sm   { border-radius: var(--ra-radius-sm); }
.r-md   { border-radius: var(--ra-radius-md); }
.r-card { border-radius: var(--ra-radius-card); }
.r-xl   { border-radius: var(--ra-radius-xl); }
.r-pill { border-radius: var(--ra-radius-pill); }
.r-full { border-radius: var(--ra-radius-full); }

.divider   { border-top: var(--ra-border-hairline-w) solid var(--ra-border-default); }
.divider-v { border-top: 0; border-left: var(--ra-border-hairline-w) solid var(--ra-border-default); align-self: stretch; }
/* == ds/atoms/dot.css == */
/* ds/atoms/dot.css — the status dot (S51 WP6-A, 2026-09-14).
   A filled circle: size states `.xs .sm .lg .xl` (4 / 6 / 8 / 10 px on the spacing scale; the
   default is 6), tone states `.pos .neg .info .warn .accent .off`. `.dot` was already the name the
   pill, the status pill and several panels used for the same thing; their own rules (`.pill .dot`,
   `.status-pill .dot`, `.rv-record .dot` …) carry two classes and outrank this base, so adopting
   the atom changes nothing until a site drops its rule. Tokens only. */
.dot {
  display: inline-block;
  width: var(--ra-sp-xs);
  height: var(--ra-sp-xs);
  border-radius: var(--ra-radius-full);
  background: var(--ra-text-faint);
}
.dot.xs { width: var(--ra-sp-2xs); height: var(--ra-sp-2xs); }
.dot.sm { width: var(--ra-sp-xs);  height: var(--ra-sp-xs); }
.dot.lg { width: var(--ra-sp-sm);  height: var(--ra-sp-sm); }
.dot.xl { width: var(--ra-sp-md);  height: var(--ra-sp-md); }
.dot.pos    { background: var(--pos); }
.dot.neg    { background: var(--neg); }
.dot.info   { background: var(--info); }
.dot.warn   { background: var(--warn); }
.dot.accent { background: var(--ra-accent-primary); }
.dot.off    { background: var(--ra-border-track); }
/* == ds/atoms/meter.css == */
/* ds/atoms/meter.css — the fill bar (S51 WP6-A, 2026-09-14).
   A track with one fill whose width the caller sets (`style="width:42%"`, `A.meter({pct})`):
   size states `.xs .sm .lg` (3 / 4 / 8 px; the default is 6), fill tones `.pos .neg .info .warn`
   (default: the accent). The Intelligence blocks and the screener re-rolled this shape a dozen
   ways (`*-bar i`, `*-fill`, `*-pctile`); they compose it from here. Tokens only. */
.meter {
  position: relative;
  display: block;
  height: var(--ra-sp-xs);
  border-radius: var(--ra-radius-full);
  background: var(--ra-border-track);
  overflow: hidden;
}
.meter.xs { height: var(--ra-sp-3); }
.meter.sm { height: var(--ra-sp-2xs); }
.meter.lg { height: var(--ra-sp-sm); }
.meter > .fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  border-radius: inherit;
  background: var(--ra-accent-primary);
}
.meter > .fill.pos  { background: var(--pos); }
.meter > .fill.neg  { background: var(--neg); }
.meter > .fill.info { background: var(--info); }
.meter > .fill.warn { background: var(--warn); }
/* == ds/atoms/radio.css == */
/* ds/atoms/radio.css — the radio button, Figma Platform `radio` (2820:1206, the 2026-10-06 review; builder
   ds/atoms/radio.js). Size big 20 / small 10 (`--ra-size-icon-xl` / `-xs`), Value unchecked / checked (`.on`), State
   default / disabled (`[disabled]`, `--ra-radio-opacity-disabled`). The ring is an inset box-shadow, not a border:
   Chromium draws a 2.5px or 1.25px border-width at 2px / 1px at device pixel ratio 1 (fig-tokens), a shadow at its width.
   Figma's «Gap» ellipse is filled with the control's own `--ra-surface-base`, so it needs no layer. The tone is
   `--radio-tone` (the accent); a consumer sets it (A.txSide's sell side). Tokens only. */
.radio {
  --radio-tone: var(--ra-accent-primary);
  display: inline-grid;
  place-items: center;
  flex: none;
  width: var(--ra-size-icon-xl);
  height: var(--ra-size-icon-xl);
  border-radius: var(--ra-radius-full);
  background: var(--ra-surface-base);
  box-shadow: inset 0 0 0 var(--ra-radio-border-big-default) var(--ra-surface-subtle);
  opacity: var(--ra-radio-opacity-default);
}
.radio.small {
  width: var(--ra-size-icon-xs);
  height: var(--ra-size-icon-xs);
  box-shadow: inset 0 0 0 var(--ra-border-hairline-w) var(--ra-surface-subtle);
}
.radio.on       { box-shadow: inset 0 0 0 var(--ra-radio-border-big-selected) var(--radio-tone); }
.radio.small.on { box-shadow: inset 0 0 0 var(--ra-radio-border-small-selected) var(--radio-tone); }
.radio.on::after {
  content: "";
  width: var(--ra-radio-dot-big);
  height: var(--ra-radio-dot-big);
  border-radius: var(--ra-radius-full);
  background: var(--radio-tone);
}
.radio.small.on::after { width: var(--ra-radio-dot-small); height: var(--ra-radio-dot-small); }
.radio[disabled] { opacity: var(--ra-radio-opacity-disabled); }
/* == ds/atoms/tx-side.css == */
/* ds/atoms/tx-side.css — the order ticket's side button, Figma Platform `tx-side` (2832:34, the 2026-10-06 review;
   builder ds/atoms/tx-side.js). Tipo buy / sell x Estado on / off: 40 tall, 2 / 10 inset, r6. One tone per side
   (`--tx-tone`: buy `--ra-tx-side-buy`, sell `--ra-tx-side-sell`; in dark Figma's #0FD4BB / #FF5555, in light the app's
   --pos / --neg, tokens/tokens.css section 1k) draws the word, the ring while on and the small radio; on = the side's
   tint (buy its tone at 12 %, sell `--ra-tx-side-sell-on`, dark #EF4444, at 16 %, the colour Figma's sell fill has), off = the card fill with a ring in the same card colour, so it shows none (Figma: buy /
   off's stroke is #23282C, the card; sell / off has no stroke). The sub line in caption 11, the price 15 / 500 (dimmed
   while off) beside the radio, which follows the button's own `.on`. The trading ticket's BUY / SELL are this atom
   since the Figma round of 2026-10-09 (ds/organisms/trading-ticket.html, `pxId` names their live price spans).
   Tokens only. */
.tx-side {
  --tx-tone: var(--ra-tx-side-buy);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ra-sp-sm);
  box-sizing: border-box;
  height: var(--ra-sp-40);
  padding: var(--ra-sp-3xs) var(--ra-sp-md);
  border: var(--ra-border-hairline-w) solid var(--ra-surface-card);
  border-radius: var(--ra-radius-sm);
  background: var(--ra-surface-card);
  cursor: pointer;
}
.tx-side.sell { --tx-tone: var(--ra-tx-side-sell); }
.tx-side.on { border-color: var(--tx-tone); }
.tx-side.buy.on  { background: color-mix(in srgb, var(--tx-tone) 12%, transparent); }
.tx-side.sell.on { background: color-mix(in srgb, var(--ra-tx-side-sell-on) 16%, transparent); }
.tx-side-l { display: flex; flex-direction: column; text-align: left; }
.tx-side-l:has(+ .tx-side-r) > .tx-side-sub { margin-top: calc(-1 * var(--ra-sp-3)); }
.tx-side-lbl { font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); color: var(--tx-tone); }
.tx-side-sub { font: var(--ts-caption-11); letter-spacing: var(--ts-caption-11-ls); color: var(--ra-text-secondary); }
.tx-side-r { display: flex; align-items: center; gap: var(--ra-sp-xs); }
.tx-side-px { font: 500 var(--ra-fs-15) var(--font-sans); color: var(--ra-text-secondary); font-variant-numeric: tabular-nums; }
.tx-side.on .tx-side-px { color: var(--text-1); }
.tx-side .radio { --radio-tone: var(--tx-tone); }
/* The small radio follows the button's own `.on`: a caller that switches sides toggles `.on` on the button (the trading
   ticket's ibSetSide), and the radio, built checked or not, reads the side from it. */
.tx-side.on .radio.small { box-shadow: inset 0 0 0 var(--ra-radio-border-small-selected) var(--radio-tone); }
.tx-side.on .radio.small::after {
  content: "";
  width: var(--ra-radio-dot-small);
  height: var(--ra-radio-dot-small);
  border-radius: var(--ra-radius-full);
  background: var(--radio-tone);
}
.tx-side:not(.on) .radio.small { box-shadow: inset 0 0 0 var(--ra-border-hairline-w) var(--ra-surface-subtle); }
.tx-side:not(.on) .radio.small::after { content: none; }
/* == ds/atoms/send-button.css == */
/* ds/atoms/send-button.css — the message send / pause button, Figma Platform `send-button` (2951:2488, the 2026-10-08
   review; builder ds/atoms/send-button.js). Square, r6, a 16 icon in `--ra-text-primary` at every size: small 24
   (`--ra-size-control-sm`), medium 32 (`-md`), large 40 (the icon plus Figma's 12 inset each side). Action send = the
   `--ra-button-message-send-accent`, pause (`.pause`) = `--ra-button-message-pause-accent`. Style boxed: a 1 px ring in
   the accent; hover fills it with the accent; pressed is the panel fill with the button border; focus is a 2 px ring
   (`--ra-button-message-focus-border-width`) inside the box, no halo (Figma's spread-2 drop shadow does not render: the
   variant has no fill). Style iconOnly (`.icon-only`): no ring; hover fills
   `--ra-surface-subtle`; pressed and focus draw nothing in Figma (focus keeps the app's global ring, see the round's
   ledger). Disabled: `--ra-button-message-disabled-opacity`. `[data-state=hover|pressed|focus]` shows a state's face
   statically (the catalog); the app gets it from the pseudo-classes. Tokens only. */
.send-button {
  --sb-accent: var(--ra-button-message-send-accent);
  --ic-size: var(--ra-size-icon-lg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  box-sizing: border-box;
  width: var(--ra-size-control-sm);
  height: var(--ra-size-control-sm);
  padding: 0;
  border: var(--ra-border-hairline-w) solid var(--sb-accent);
  border-radius: var(--ra-radius-sm);
  background: transparent;
  color: var(--ra-text-primary);
  cursor: pointer;
  transition: background var(--mo-dur-control) ease, border-color var(--mo-dur-control) ease, opacity var(--mo-dur-control) ease;
}
.send-button.medium { width: var(--ra-size-control-md); height: var(--ra-size-control-md); }
.send-button.large  { width: calc(var(--ra-size-icon-lg) + 2 * var(--ra-sp-lg)); height: calc(var(--ra-size-icon-lg) + 2 * var(--ra-sp-lg)); }
.send-button.pause  { --sb-accent: var(--ra-button-message-pause-accent); }
.send-button:is(:hover, [data-state=hover]):not(:disabled) { background: var(--sb-accent); }
.send-button:is(:active, [data-state=pressed]):not(:disabled) { background: var(--ra-surface-panel); border-color: var(--ra-control-button-border); }
.send-button:not(.icon-only):is(:focus-visible, [data-state=focus]) {
  outline: none;
  border-width: var(--ra-button-message-focus-border-width);
}
.send-button.icon-only { border-color: transparent; }
.send-button.icon-only:is(:hover, [data-state=hover]):not(:disabled) { background: var(--ra-surface-subtle); }
.send-button.icon-only:is(:active, [data-state=pressed]):not(:disabled) { background: transparent; border-color: transparent; }
.send-button:disabled { opacity: var(--ra-button-message-disabled-opacity); cursor: default; }
/* == ds/atoms/on.css == */
/* ds/atoms/on.css — the underlined tab, Figma Platform `on` (2944:2274, the 2026-10-08 review; builder ds/atoms/on.js,
   class .on-tab). Figma's fixed 31 tall (the md control less a hairline), 12 inset each side, the h6 table header in
   capitals; off = `--ra-text-muted`, On (`.on`) = `--ra-text-primary` over a 2 px `--ra-accent-dark` underline. Figma
   draws the underline as an inside stroke, so it is an inset shadow here: it moves no text between the two states.
   Tokens only. */
.on-tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  box-sizing: border-box;
  height: calc(var(--ra-size-control-md) - var(--ra-border-hairline-w));
  padding: 0 var(--ra-sp-lg);
  border: 0;
  background: transparent;
  font: var(--ts-h6-table-header);
  letter-spacing: var(--ts-h6-table-header-ls);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ra-text-muted);
  cursor: pointer;
}
.on-tab.on { color: var(--ra-text-primary); box-shadow: inset 0 calc(-1 * var(--ra-sp-3xs)) 0 var(--ra-accent-dark); }
/* == ds/molecules/list-row.css == */
/* ds/molecules/list-row.css — the list row (S51 WP6-A, 2026-09-14).
   One horizontal row of a list: flex, centred, the small gap and inset, the small radius. States:
   `.pick` makes it a target (pointer + the hover fill token), `.on` the selected fill, `.dense` the
   tighter inset the rails and drawers use, `.dragging` the faded row while it is carried. The
   panels' rows (`.wl-row`, `.auto-sb-row`, `.sr-item`, `.rv-rail-row`, `.acct-item` …) compose it;
   their own columns stay their own. Tokens only. */
.lrow {
  display: flex;
  align-items: center;
  gap: var(--ra-sp-sm);
  padding: var(--ra-sp-xs) var(--ra-sp-sm);
  border-radius: var(--ra-radius-sm);
}
.lrow.dense { gap: var(--ra-sp-xs); padding: var(--ra-sp-3xs) var(--ra-sp-xs); }
.lrow.pick { cursor: pointer; }
.lrow.pick:hover { background: var(--ra-state-row-hover); }
.lrow.on { background: var(--ra-surface-tape); }
.lrow.dragging { opacity: 0.4; }
/* == ds/molecules/brief-tile.css == */
/* ds/molecules/brief-tile.css — the brief tile (Figma Platform `tiles / brief / tile` 2869:962, 2026-10-06): one KPI of
   a briefing strip. A column, 4 apart: the uppercase label (h6/table-header-upper, --ra-text-muted), then the value
   (h2/section; a tone class `.pos-c` / `.neg-c` / `.warn-c` colours it) over the sub line (caption/11, --text-2), 2 apart.
   Padding 24 / 16 and a hairline on the right (--line), the strip's separator. The info icon (12, --ra-text-muted) sits
   on the label's first line at the right; `.has-info` keeps the label clear of it. Tokens only, the ones Figma binds. */
.brief-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--ra-sp-2xs);
  min-width: 0;
  padding: var(--ra-sp-2xl) var(--ra-sp-xl);
  border-right: var(--ra-border-hairline-w) solid var(--line);
  color: var(--ra-text-primary);
}
.brief-tile-l { min-height: var(--ra-sp-3xl); color: var(--ra-text-muted); }   /* two label lines (32), as Figma's label box: the values line up across a strip */
.brief-tile.has-info .brief-tile-l { padding-right: var(--ra-sp-lg); }
.brief-tile-body { display: flex; flex-direction: column; gap: var(--ra-sp-3xs); }
.brief-tile-s { color: var(--text-2); text-box: trim-both cap alphabetic; }   /* Figma's sub line box is cap-trimmed: two lines box 24, the tile 138 (2869:958 read 2026-10-11: the style lost leadingTrim, its box and render did not) */
.brief-tile-info {
  position: absolute;
  top: var(--ra-sp-2xl);
  right: var(--ra-sp-lg);
  height: var(--ra-sp-xl);
  display: flex;
  align-items: center;
  color: var(--ra-text-muted);
  --ic-size: var(--ra-size-icon-sm);
}
/* == ds/molecules/thread-row.css == */
/* ds/molecules/thread-row.css — the Desk AI history row (Figma Platform `list-row / Row / Chats Thread` 2945:2364, eight
   variants: Type Chat / Folder x State Default / Selected / Pinned / Hover; read 2026-10-11). A row, 8 inset, a hairline
   below (--ra-border-card). Chat: the symbol over its meta (caption/11, --ra-text-primary / --ra-text-muted) in a lead
   at least 36 wide (Figma's fixed 36 meta), 8 from the title (h6/table-header) over the sub line (caption/11), both cut
   with an ellipsis. Folder: the folder icon (16, centred in a 30 box), 14 from the name; the sub line ends in the
   folder's symbols, the kit's small tags (A.chip({tiny, tag: 'small'}), chip.css: Figma chip Tag=small, 15 tall, 9 / 600,
   r3, the default face since 2026-10-15), 6 apart; the row's items centred, the pin still at the top. Selected `.on` = --ra-state-row-hover and the
   chevron (caret-right 14) at the right; hover (and `.hover`, and while its menu is open) = --ra-surface-card-elevated and shows the row-menu button
   (ellipsis-vertical 14, --ra-text-primary; it takes the chevron's place on a selected row); `.pinned` = the pushpin (10,
   --ra-accent-primary) at the top right. Tokens only, the ones Figma binds. */
.thread-row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--ra-sp-sm);
  padding: var(--ra-sp-sm) var(--ra-sp-sm) calc(var(--ra-sp-sm) - var(--ra-border-hairline-w));   /* the hairline inside the 50, as Figma's stroke */
  border-bottom: var(--ra-border-hairline-w) solid var(--ra-border-card);
  cursor: pointer;
  transition: background-color var(--mo-dur-hover-out);
}
.thread-row:hover, .thread-row.hover, .thread-row:where(:has(> .thread-row-more[aria-expanded=true])) { background: var(--ra-surface-card-elevated); transition-duration: var(--mo-dur-hover-in); }   /* its menu open: the hover face (Desk AI - hover 481:281315) */
.thread-row.on { background: var(--ra-state-row-hover); }
.thread-row.folder { align-items: center; gap: var(--ra-sp-14); }
.thread-row-lead, .thread-row-text { display: flex; flex-direction: column; gap: var(--ra-sp-3xs); }
.thread-row-lead { flex: none; min-width: calc(var(--ra-sp-3xl) + var(--ra-sp-2xs)); }   /* Figma's meta is a fixed 36: titles line up at 52, a longer symbol still grows */
.thread-row-text { flex: 1; min-width: 0; }
.thread-row-sym, .thread-row-title { color: var(--ra-text-primary); }
.thread-row-sub { color: var(--ra-text-secondary); }
.thread-row-meta { color: var(--ra-text-muted); }
.thread-row-title, .thread-row-sub { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.thread-row-icon { flex: none; display: flex; justify-content: center; width: calc(var(--ra-sp-2xl) + var(--ra-sp-xs)); color: var(--ra-text-primary); --ic-size: var(--ra-size-icon-lg); }
.thread-row-subline { display: flex; align-items: center; gap: var(--ra-sp-xs); min-width: 0; }
.thread-row-subline .thread-row-sub { flex: 0 1 auto; min-width: 0; }
.thread-row-tag { flex: none; }
.thread-row-go, .thread-row-pin { flex: none; display: flex; }
.thread-row-go { align-self: center; color: var(--ra-text-primary); --ic-size: var(--ra-size-icon-md); }
.thread-row-pin { align-self: flex-start; color: var(--ra-accent-primary); --ic-size: var(--ra-size-icon-xs); }   /* at the top right on a centred folder row too (2971:1489: 8 from the top) */
.thread-row-more {
  flex: none;
  align-self: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ra-text-primary);
  cursor: pointer;
  display: flex;
  position: absolute;   /* hidden: out of the flow (the title keeps Figma's width) but in the tab order, so focus can show it */
  opacity: 0;
  pointer-events: none;
  --ic-size: var(--ra-size-icon-md);
}
.thread-row:hover .thread-row-more, .thread-row.hover .thread-row-more, .thread-row:focus-within .thread-row-more, .thread-row-more[aria-expanded=true] { position: static; opacity: 1; pointer-events: auto; }
.thread-row:has(> .thread-row-more):is(:hover, .hover, :focus-within) > .thread-row-go, .thread-row:has(> .thread-row-more[aria-expanded=true]) > .thread-row-go { display: none; }
/* == ds/molecules/source-item.css == */
/* ds/molecules/source-item.css — the Desk AI evidence column's source row (Figma Platform `ai-desk/source-item` 3085:2321,
   five variants: State Used / Unused / Loading x Hover; read 2026-10-17). M.sourceItem (source-item.js). A row, 6 / 8
   inside, radius 6: the 16 icon, 8, the text: the name and the symbol 4 apart (h5/widget-title, text/primary) over the sub
   line 2 below (caption/9, text/secondary, its box cap-trimmed as Figma's leading trim). Unused: every ink text/muted;
   Loading: the icon at the disabled opacity; hover (and `.hover`): surface/card-elevated. Tokens only. */
.source-item {
  display: flex;
  align-items: center;
  gap: var(--ra-sp-sm);
  padding: var(--ra-sp-xs) var(--ra-sp-sm);
  border-radius: var(--ra-radius-sm);
  color: var(--ra-text-primary);
  --ic-size: var(--ra-size-icon-lg);
  transition: background-color var(--mo-dur-hover-out);
}
.source-item:hover, .source-item.hover { background: var(--ra-surface-card-elevated); transition-duration: var(--mo-dur-hover-in); }
.source-item > .ic { flex: none; }
.source-item-text { display: flex; flex-direction: column; gap: var(--ra-sp-3xs); min-width: 0; }
.source-item-title { display: flex; align-items: center; gap: var(--ra-sp-2xs); min-width: 0; white-space: nowrap; }
.source-item-title > * { overflow: hidden; text-overflow: ellipsis; }
.source-item-sym { flex: none; }
.source-item-sub { color: var(--ra-text-secondary); white-space: nowrap; text-box: trim-both cap alphabetic; }
.source-item.unused, .source-item.unused .source-item-sub { color: var(--ra-text-muted); }
.source-item.loading > .ic { opacity: var(--ra-opacity-disabled); }
/* == ds/molecules/file-card.css == */
/* ds/molecules/file-card.css — the Desk AI evidence column's saved-file card (Figma Platform `ai-desk/file-card` 3086:2393,
   two variants: hover off / on; read 2026-10-17). M.fileCard (file-card.js). 88 wide, 4 between the tile and the text: the
   tile (56 tall, surface/tape, a surface/chip ring, radius 8) holds the file's glyph (32 x 40, accent/primary, its corner
   folded in accent/primary-soft, the extension in text/on-accent at its foot); the name (caption/11, text/primary, cut
   with an ellipsis) 2 over the size (h6/table-header upper, text/muted). Hover (and `.hover`): the tile's ring in
   semantic/info (Figma's unbound #00A5E7CC). Tokens only. */
.file-card {
  display: flex;
  flex-direction: column;
  gap: var(--ra-sp-2xs);
  flex: none;
  width: calc(var(--ra-sp-4xl) + var(--ra-sp-40));
  color: var(--ra-text-primary);
  text-decoration: none;
}
.file-card-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(var(--ra-sp-4xl) + var(--ra-sp-sm));
  background: var(--ra-surface-tape);
  border: var(--ra-border-hairline-w) solid var(--ra-border-strong);   /* the chip's hex, with a light value */
  border-radius: var(--ra-radius-md);
  transition: border-color var(--mo-dur-hover-out);
}
.file-card:hover .file-card-tile, .file-card.hover .file-card-tile { border-color: var(--ra-semantic-info); transition-duration: var(--mo-dur-hover-in); }
.file-card-glyph {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  width: var(--ra-sp-3xl);
  height: var(--ra-sp-40);
  padding-bottom: var(--ra-sp-2xs);
  background: var(--ra-accent-primary);
  border-radius: var(--ra-radius-3xs);
  clip-path: polygon(0 0, calc(100% - var(--ra-sp-md)) 0, 100% var(--ra-sp-md), 100% 100%, 0 100%);
  color: var(--ra-text-on-accent);
  text-transform: uppercase;
}
.file-card-glyph::before { content: ""; position: absolute; top: 0; right: 0; width: var(--ra-sp-md); height: var(--ra-sp-md); background: var(--ra-accent-primary-soft); clip-path: polygon(0 0, 0 100%, 100% 100%); }
.file-card-info { display: flex; flex-direction: column; gap: var(--ra-sp-3xs); min-width: 0; }
.file-card-name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.file-card-size { color: var(--ra-text-muted); text-transform: uppercase; }
/* == ds/molecules/coverage-range.css == */
/* ds/molecules/coverage-range.css — .cvr, the coverage bar (S56, 2026-09-16): a day axis with the recorded
   stretches as bands and two handles that can only rest on recorded data. Built by M.coverageRange, driven by
   PV2.ui.coverageRange (coverage-range.js). The replay control composes it (`.rpl-cvr`); the Recorder's scrub
   bar can. States: `.empty` (range unknown: no handles, no window), `.playing` (the playhead shows). Tokens only. */
/* The track sits 24 down (was 14): the scale's labels (12px type on an 18px line) reached the 14px handles, so a handle
   near a mark ran into its label (owner, 2026-09-23 — "the top slider needs some gap"). .cvr-h's top follows. */
.cvr { position: relative; display: block; padding: var(--ra-sp-2xl) 0 var(--ra-sp-md); user-select: none; touch-action: none; container-type: inline-size; }
/* A narrow range (the replay transport bar on a small chart) keeps only the day's two ends on its scale: the inner marks
   would run into them. */
@container (max-width: 240px) { .cvr-scale > span:not(:first-child):not(:last-child) { display: none; } }
/* Too narrow even for those two (under 80, 2026-10-18), the scale goes rather than its ends running into each other; the
   track still shows the window and the playhead. */
@container (max-width: 80px) { .cvr-scale { display: none; } }
.cvr-scale { position: absolute; left: 0; right: 0; top: 0; height: var(--ra-sp-lg); font: var(--ts-caption-helper); letter-spacing: var(--ts-caption-helper-ls); color: var(--ra-text-muted); }
.cvr-scale > span { position: absolute; transform: translateX(-50%); white-space: nowrap; }
.cvr-scale > span:first-child { transform: none; }
.cvr-scale > span:last-child { transform: translateX(-100%); }
/* Fitts & Miller 19 (Platform coverage-range 346:10): the gaps are hatched (text/muted lines on the track), regular hours
   are the chip surface over them, the recorded stretches teal over both, the chosen window an outline (1.5, text/primary)
   over the data, the playhead a 2 px text/primary line. */
.cvr-track { position: relative; height: var(--ra-sp-sm); border-radius: var(--ra-radius-full); background: repeating-linear-gradient(135deg, var(--ra-text-muted) 0 1px, transparent 1px 3px), var(--ra-border-track); overflow: hidden; }
.cvr-sess, .cvr-band, .cvr-win, .cvr-play { position: absolute; top: 0; bottom: 0; }
.cvr-sess { background: var(--ra-surface-chip); }
.cvr-band { background: var(--ra-accent-primary); }
.cvr-win { box-shadow: inset 0 0 0 1.5px var(--ra-text-primary); }
.cvr-play { width: var(--ra-sp-3xs); background: var(--ra-text-primary); display: none; }
.cvr.playing .cvr-play { display: block; }
.cvr-h { position: absolute; left: 0; top: calc(var(--ra-sp-2xl) + var(--ra-sp-sm) / 2); width: var(--ra-sp-14); height: var(--ra-sp-14); padding: 0; border: 0; border-radius: var(--ra-radius-full); background: var(--ra-text-thumb); transform: translate(-50%, -50%); cursor: ew-resize; }
.cvr-h:focus-visible { outline: 2px solid var(--ra-accent-primary); outline-offset: 1px; }
.cvr.empty .cvr-h, .cvr.empty .cvr-win { display: none; }
.cvr-tip { position: absolute; top: 0; transform: translateX(-50%); font: var(--ts-caption-helper); letter-spacing: var(--ts-caption-helper-ls); color: var(--text-1); background: var(--bg-1); padding: 0 var(--ra-sp-2xs); border-radius: var(--ra-radius-2xs); white-space: nowrap; pointer-events: none; }
.cvr-tip[hidden] { display: none; }
/* == ds/molecules/month-nav.css == */
/* ds/molecules/month-nav.css — the month navigator (Figma Platform month-navigator 3064:2260, round 2026-10-16): a tape tray,
   24 tall, an elevated hairline, r6, 4 inside and between; previous and next are 20 px quiet icon buttons (caret-left,
   caret-right), the period between them a quiet button with the calendar icon and its h6 label, all in secondary ink.
   Markup: <div class=month-nav> around three A.btn({kind: 'quiet'}) (the Review journal's filter bar). Tokens only. */
.month-nav { display: inline-flex; align-items: center; gap: var(--ra-sp-2xs); height: var(--ra-size-control-sm); padding: 0 var(--ra-sp-2xs);
  background: var(--ra-surface-tape); border: var(--ra-border-hairline-w) solid var(--ra-surface-card-elevated); border-radius: var(--ra-radius-sm); }
.month-nav .btn { height: var(--ra-size-icon-xl); padding: 0; color: var(--ra-text-secondary);
  font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); }
.month-nav .btn.icon { width: var(--ra-size-icon-xl); }
.month-nav .btn:hover { color: var(--ra-text-primary); }
/* == ds/themes/light.css == */
/* ============================================================
   LIGHT THEME — paper surfaces, deeper blue, softer market colors
   ============================================================ */
/* The light theme's custom properties (the palette and its repoints) live in tokens/tokens.css, the last
   [data-theme="light"] block there (moved verbatim by E1, 2026-10-05: the only file allowed a colour literal).
   This file keeps the light theme's RULES; every colour they paint is a token of that block. */
[data-theme="light"] body { background: var(--bg-0); }
[data-theme="light"] .panel { background: var(--ra-lt-white); border-color: var(--ra-lt-h75-40-015-a14); box-shadow: 0 1px 2px var(--ra-lt-h75-20-02-a6), 0 4px 12px -2px var(--ra-lt-h75-20-02-a8); }
[data-theme="light"] .panel-head { background: var(--ra-lt-h75-985-005-a60); border-bottom-color: var(--ra-lt-h75-40-015-a10); }
[data-theme="light"] .topbar, [data-theme="light"] .footer, [data-theme="light"] .ticker-bar { background: var(--ra-lt-h75-97-006); border-color: var(--ra-lt-h75-40-015-a15); }
[data-theme="light"] .nav { background: var(--bg-1); border-color: var(--line); }
[data-theme="light"] .nav button.on { color: var(--ra-text-inverse); background: linear-gradient(180deg, var(--ra-lt-h235-55-16), var(--ra-lt-h235-45-17)); box-shadow: inset 0 0 0 1px var(--ra-lt-h235-35-16-a70), 0 2px 8px var(--ra-lt-h235-50-14-a30); }
[data-theme="light"] .user-wrap { background: var(--ra-lt-h70-98-005); border-color: var(--line); }
[data-theme="light"] .btn { background: var(--ra-lt-h70-98-004); border-color: var(--line); color: var(--text-2); }
[data-theme="light"] .btn:hover { background: var(--ra-lt-h70-94-008); border-color: var(--line-strong); color: var(--text-1); }
[data-theme="light"] .btn.ghost { background: transparent; }
[data-theme="light"] .btn.ghost:hover { background: var(--ra-lt-h70-94-008-a70); }
/* S19 §3.3/S19-R2 (LEAD RULING) -- `.btn.on` (components.css) and this
   file's plain `[data-theme="light"] .btn` two lines up TIE at (0,2,0);
   widgets.css loads AFTER components.css (index.html), so the plain rule
   wins on source order and every "on" control loses its active state the
   moment the theme flips to light -- 26 of 116 `.btn` elements in the
   loaded DOM, measured (design/s19/arch_s19_cascade.js, transitions killed
   before reading -- `.btn`'s own transition on all properties returns an
   INTERPOLATION, not a value, to an immediate post-flip read). This is a
   DEFECT FIX, not a new light palette: the state exists correctly in dark
   and is restored here with the SAME tokens the dark rule already uses
   (--ra-accent-primary, theme-invariant -- no light override exists or is
   added). (0,3,0) beats both rules above it without touching either. */
[data-theme="light"] .btn.on {
  color: var(--ra-accent-primary);
  border-color: var(--ra-accent-primary);
  background: color-mix(in oklch, var(--ra-accent-primary) 12%, transparent);
}
/* SND-48 (CLOSING_AUDIT_S19.md) -- SND-40/S19-R2's EXACT mechanism, recurring
   on a class that did not exist when S19-R2 was written: `.btn.cta`
   (components.css, task #5) landed AFTER this file's `[data-theme="light"]
   .btn` rule, at the SAME (0,2,0) specificity, and widgets.css loads after
   components.css -- so in light the generic rule's neutral background/
   border-color/color win outright over `.btn.cta`'s transparent/transparent/
   --ra-text-primary, and the Done button becomes a plain neutral button
   wearing a teal gradient ring (the ring survives because it is painted by
   `::before`, untouched by either rule). Measured live before this fix:
   the light .btn fill (--ra-lt-h70-98-004), label --text-2,
   border --line -- none of them `.btn.cta`'s own
   values. Fixed the SAME shape as `.btn.on` above: (0,3,0) restores the
   dark rule's own tokens, not a new light palette -- `--ra-text-primary`
   is already theme-aware (this file's own :root[light] block), so `color`
   here needs no separate light value.
   THIS IS AN INSTANCE FIX, KNOWINGLY, NOT THE MECHANISM FIX -- recorded so
   nobody reads a second `[data-theme="light"] .btn.<class>` rule as
   evidence the underlying tie was ever resolved. Repointing every `.btn`
   variant (116+ across the app) to win the cascade unconditionally is a
   refactor that cannot be verified responsibly at session close; the next
   new `.btn.*` modifier will inherit this exact exposure until someone
   does that refactor. Filed, not silently deferred: this comment IS the
   filing.
   MEASURED BEYOND THE NAMED DEFECT, per "measure the resulting row, not
   just the property you changed": SND-48 only measured REST state, but
   the SAME (0,3,0)-ties-lose-on-load-order mechanism also silently killed
   `.btn.cta:hover`/`:active` in light (they'd fall back to the generic
   `[data-theme="light"] .btn:hover` grey pre-fix, and to fully invisible
   once REST alone was fixed here -- a hovered Done button with zero visual
   feedback, a worse regression than the one this task was asked to close).
   Restored with the SAME theme-invariant tokens `.btn.cta:hover`/`:active`
   already use in components.css (`--ra-accent-primary`, no light override
   anywhere, same value both themes) -- not a new light choice, the exact
   values dark already had. */
[data-theme="light"] .btn.cta {
  background: transparent;
  border-color: transparent;
  color: var(--ra-text-primary);
}
[data-theme="light"] .btn.cta:hover  { background: color-mix(in oklch, var(--ra-accent-primary) 12%, transparent); }
[data-theme="light"] .btn.cta:active { background: color-mix(in oklch, var(--ra-accent-primary) 20%, transparent); }
/* Figma round 2026-10-09 (button Kind=delete): at rest the light .btn rule above paints it (a default button, as in
   dark); its hover keeps the danger cue the generic light hover would erase, in the theme-aware --neg. */
[data-theme="light"] .btn.delete:hover { color: var(--neg); border-color: color-mix(in oklch, var(--neg) 60%, transparent); }
/* S18 #14 (WIDTH_SWEEP_S18.md §8): TFs-menu/Modes-menu/Indicators are
   `class="btn ghost sm …"` (pane.html) carrying their OWN unconditional
   dark-chip skin (screener.css:1625, S7-era, 4db894ac):
   `.btn.cmode-menu,.btn.ctf-menu,.btn.cind{color:var(--ra-text-inverse);
   background:var(--ra-text-neutral-900)}` at (0,2,0). The `.btn.ghost`
   rule above already strips their background in light (0,3,0 beats
   0,2,0) -- but nothing here ever stripped the matching `color`, so the
   plate disappears and the white label doesn't: TFs/Modes measured
   1.00:1 (literally white-on-white), Indicators 1.04:1. Neither half's
   rule is wrong alone; completing the SAME pair every other `.btn.ghost`
   already gets is the fix -- not inventing a value (no light frame exists
   for these nodes, HANDOFF_S18 §A) but reusing the one plain ghost
   buttons already carry two rules up: `[data-theme="light"] .btn { …
   color: var(--text-2) }`. Same specificity tier as the background rule
   above, so it wins the same way.
   S33 #64 (design-qa #54 §5, LIGHT_SWEEP_VERDICT.md): `.cols-chip`
   (screener.css:1938, `class="btn ghost sm cols-chip"` -- pane.html:188)
   is the IDENTICAL shape and was simply never added to this list --
   screener.css:1929's own comment says it was built copying "the same
   fill/border/label idiom as the chart's TFs▾/Modes▾", but postdates
   this fix (or was never swept into it). Confirmed by measurement AND by
   eye: white text, transparent-in-light background, contrast 1.00 --
   the "Cols" button rendered as an empty outline. Ten occurrences (one
   per screener instance on the bench). PRE-EXISTING (banked pre-#53
   light distribution already records screener min 1.00) -- not a
   consequence of #53's raise, only surfaced because #54's sweep asked
   what the minimum WAS. Same fix, same reused value, no new token: `--
   ra-surface-neutral-900` and `--ra-text-inverse` themselves are
   correctly left untouched (both real, intentionally theme-invariant
   dark chips elsewhere -- e.g. intelligence.css's `.intel-rail-cmp`, a
   documented Figma-literal "seal" fill, S3-40 -- would break if this
   fix touched the TOKEN instead of naming the three extra selectors).
   Fitts & Miller 08 (2026-10-18): the TFs, Modes and Indicators triggers left this list; they are the kit's .select now. */
[data-theme="light"] .btn.cpat,
[data-theme="light"] .btn.cols-chip { color: var(--text-2); }
[data-theme="light"] .btn.cols-chip:hover { color: var(--text-1); }
/* S19 §3.4/H2 -- app-wide half, TWO fixes in one line, not one:
   (1) This was the `background` SHORTHAND at (0,2,0), which beats
   `.select`'s own (0,1,0) `background-image` rule (components.css) on
   specificity alone and resets it to `none` -- killing the dropdown caret
   for EVERY `.select` in the product, in light theme only (dark was never
   touched by this rule). Longhand `background-color` never competes with
   `background-image` on the same property, so the fix alone would have
   been enough for the caret.
   (2) `.select` is DROPPED from this selector entirely, not just switched
   to longhand -- §2.1/§3.4 give `.select` its own token-driven fill
   (--ra-surface-card since 2026-10-18) and stroke (--ra-border-strong), BOTH already
   theme-aware at the token itself. Leaving `.select` in this rule would
   have kept it here at (0,2,0), permanently outranking `.select`'s own
   (0,1,0)/(0,1,1) rules (including dev-b's `.snd-cond select` /
   `.snd-sound-picker select`) and silently overriding the new token with
   this literal in light theme only -- the exact "declaration is intent,
   computed value is fact" trap. `.input` keeps its own literal here,
   unchanged (not part of this repair). `.select`'s `color` was already
   theme-aware via --text-1 (widgets.css light block) with no direct rule
   needed. */
[data-theme="light"] .input { background-color: var(--ra-lt-white); border-color: var(--line); color: var(--text-1); }
/* the watchlist's name and add fields: their variant's dark fill (input.variants.css .input.wl-in, listed after this file)
   yields to the light field */
[data-theme="light"] .input.wl-in { background-color: var(--ra-lt-white); }
[data-theme="light"] .pill { background: var(--ra-lt-h70-98-005); border-color: var(--line); color: var(--text-2); }
/* figma round 2026-10-09 (fig-intel, scoped in the fig-followups task): the rule above ties `.pill.pos` / `.neg` and,
   loading later, greys their tone in light. The Intelligence peers average (`#intel-peer-avg`, the pill the intel list
   changed: the panel-head slot of View=brief and View=peers) takes its tone back, the border mixed from the light tone
   at 50 % as `.pill.warn` does (the dark `--ra-k-pos-55-12-a50` / `--ra-k-neg-55-14-a50` borders are another hue).
   Every other kit pill keeps the neutral light pill above, as before the round. */
[data-theme="light"] #intel-peer-avg.pill.pos { color: var(--pos); border-color: color-mix(in oklch, var(--pos) 50%, transparent); }
[data-theme="light"] #intel-peer-avg.pill.neg { color: var(--neg); border-color: color-mix(in oklch, var(--neg) 50%, transparent); }
[data-theme="light"] .kbd { background: var(--ra-lt-h70-97-006); border-color: var(--line); }
[data-theme="light"] table.data th { background: var(--ra-lt-h70-96-008); border-bottom-color: var(--line-strong); color: var(--text-2); }
[data-theme="light"] table.data td { border-bottom-color: var(--ra-lt-h70-90-006-a50); }
[data-theme="light"] table.data tr:hover td { background: var(--ra-lt-h70-94-008-a70); }
[data-theme="light"] .segmented { background: var(--ra-lt-h70-96-006); border-color: var(--line); }
[data-theme="light"] .segmented button.on { color: var(--text-1); background: var(--ra-lt-white); box-shadow: inset 0 0 0 1px var(--line-strong); }
[data-theme="light"] .segmented button:hover { color: var(--text-1); }
[data-theme="light"] .sig-alert { background: linear-gradient(180deg, var(--ra-lt-white), var(--bg-1)); }
/* Chart floating controls — transparent so the panel bg shows through in light mode */
[data-theme="light"] .ch-sb button { background: var(--ra-lt-white-a90); border-color: var(--line); color: var(--text-1); box-shadow: 0 1px 2px var(--ra-lt-h75-20-02-a8); }
[data-theme="light"] .ch-sb button:hover { background: var(--ra-lt-h70-96-008); }
[data-theme="light"] .ch-sb button.on { background: var(--info-bg); color: var(--info); }
[data-theme="light"] .chart-loading { background: var(--ra-lt-white-a90); color: var(--text-2); border-color: var(--line); }
[data-theme="light"] .chart-zoom button { background: var(--ra-lt-white-a90); border-color: var(--line); color: var(--text-1); box-shadow: 0 1px 2px var(--ra-lt-h75-20-02-a8); }
[data-theme="light"] .chart-zoom button:hover { background: var(--ra-lt-h70-96-008); }
[data-theme="light"] .st { background: var(--ra-lt-h70-98-005); }
[data-theme="light"] .search-pill, [data-theme="light"] .st, [data-theme="light"] .kbd { background: var(--ra-lt-h70-98-005); }
[data-theme="light"] .hidden-strip { color: var(--text-2); } /* design-sync 2026-10-03: fill is --ra-surface-panel (themed) */
[data-theme="light"] .cind-dd { background: var(--ra-lt-white); box-shadow: 0 8px 24px var(--ra-lt-h75-20-02-a15); }
/* Not on an OFF filter chip (.snd-news-cat.off, atoms/news-badge.css): it keeps its neutral skin, else OFF read as ON. */
[data-theme="light"] .nb.h:not(.off) { background: var(--ra-lt-h25-55-18-a85); color: var(--ra-text-inverse); font-weight: 600; }
[data-theme="light"] .nb.w:not(.off) { background: var(--ra-lt-h70-58-17-a85); color: var(--ra-text-inverse); font-weight: 600; }
[data-theme="light"] .nb.c:not(.off) { background: var(--ra-lt-h235-50-14-a85); color: var(--ra-text-inverse); font-weight: 600; }
[data-theme="light"] .bubble-thresh { background: var(--ra-lt-h70-98-005); }
[data-theme="light"] .bubble-thresh-slider { background: var(--ra-lt-h75-82-01); }
[data-theme="light"] table.t3 tr.buy  td { background: var(--ra-lt-h155-88-08-a25); color: var(--pos); }
[data-theme="light"] table.t3 tr.sell td { background: var(--ra-lt-h25-88-08-a25); color: var(--neg); }

/* S51 WP4 -- the kit VARIANTS in light. The panels these variants reproduce had no light-theme rule of
   their own (they looked the same in both themes), and the panel stylesheets used to win by SOURCE ORDER
   (injected after this file). A kit variant lives in the bundle BEFORE this file and ties the plain
   `[data-theme="light"] .btn` / `.segmented` / `.input` rules above at (0,2,0), so each variant gets the
   same (0,3,0) twin S19-R2 gave `.btn.on` and `.btn.cta`: it restates the theme-invariant values. */
[data-theme="light"] .btn.quiet, [data-theme="light"] .btn.quiet:hover { background: transparent; border-color: currentColor; }
[data-theme="light"] .input.bare { background-color: transparent; border-color: currentColor; }
[data-theme="light"] .segmented.soft { background: var(--ra-surface-tape); border-color: currentColor; }
[data-theme="light"] .segmented.soft button.on { background: var(--ra-surface-card-elevated); color: var(--ra-text-primary); box-shadow: none; }
[data-theme="light"] .segmented.dx { background: var(--ra-surface-card-elevated); border-color: currentColor; }
[data-theme="light"] .segmented.dx button.on { background: var(--ra-surface-tape); color: var(--ra-text-primary); box-shadow: none; }
/* The solid primary (Fitts & Miller 09.9) and the danger kind keep their faces in light: each restates its kit values. */
[data-theme="light"] .btn.solid { background: var(--ra-accent-primary); border-color: transparent; color: var(--ra-text-on-accent); }
[data-theme="light"] .btn.solid:hover { background: var(--ra-accent-primary-soft); border-color: transparent; color: var(--ra-text-on-accent); }
[data-theme="light"] .btn.danger { background: linear-gradient(180deg, var(--neg-bg), transparent); border-color: var(--ra-k-neg-55-14-a60); color: var(--neg); }
/* == ds/atoms/chip.variants.css == */
/* ds/atoms/chip.variants.css — the panels' named variants of ds/atoms/chip.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/chip.css; edit one panel's state here. */

/* WP7 screener: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_screener.json). */
/* design-sync 2026-10-03: the expanded strip's own chip skin is gone -- its chips are the kit's (A.chip: icon+label,
   icon+divider+label, tiny; Figma `screener-hidden-strip`, ds/organisms/hidden-strip.css). The hover stays (D-LF8). */
.hidden-strip .chip:hover{color:var(--text-1);border-color:var(--ra-k-info-55-14-a55)}

/* WP7 composer: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_composer.json). */
/* `.chip` variant (kit: ds/atoms/chip.css): the enum toggles are pill-shaped 10px chips (A.chip({cls:'cmp-chip-tag'})
   in renderChip; the delegated handler reads data-* off the button, so no inner span). */
.chip.cmp-chip-tag {
  display: inline-block; align-items: normal; gap: normal; height: auto; white-space: normal; line-height: inherit; font-weight: inherit;
  padding: var(--ra-xsp-chip-y) var(--ra-sp-xs);
  border: 1px solid var(--line);
  border-radius: var(--ra-radius-full);
  color: var(--text-2);
  background: var(--bg-1);
  cursor: pointer;
}
/* on: the kit's on face (chip.css .chip.on), restated over this variant's fill and weight */
.chip.cmp-chip-tag.on { background: var(--ra-state-row-hover); font-weight: 600; }

/* WP7 watchlists: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_watchlists.json). */
  /* class display:flex would otherwise beat [hidden] */
  /* S31 PLAN §2/§5 B3 (dev-b), class D: --mo-dur-hover-in/-out split
     across the rest rule and the :hover/:focus-visible rule (whichever is
     ACTIVE when the property changes supplies the duration). This file
     had zero transitions on any hover before this pass. */
  /* S51 WP4: this panel composes the kit's atoms. Each rule below is that atom's VARIANT for this panel
     (only the deltas from ds/atoms/*.css), never a re-rolled primitive; the census graded every element
     at zero against the pre-migration baseline (design/s51/CHAIN.md, wp4-watchlists). */
  /* .chip variant — the list-selector chip: A.chip({tag:'div', cls:'wl-chip'}) (watchlists.js renderChips). */
  .chip.wl-chip{gap:var(--ra-sp-xs);height:auto;padding:var(--ra-sp-2xs) var(--ra-sp-md);
    border:1px solid var(--line);border-radius:var(--ra-radius-xs);cursor:pointer;
    background:var(--ra-k-bg-20);color:var(--text-2);
    font-size:inherit;font-weight:inherit;white-space:normal;transition:color var(--mo-dur-hover-out)}
  .chip.wl-chip:hover,.chip.wl-chip:focus-visible{color:var(--text-1);transition-duration:var(--mo-dur-hover-in)}
  .chip.wl-chip.on{background:var(--ra-state-row-hover);font-weight:600}

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
    /* S3-33b — this column's cluster is a real `popout` vector
       (pane.html, PV2.icon), replacing the ⇱ that used to sit here. (A
       `.pip-pop{display:none}` suppression rule lived here — deleted:
       dev-a removed shell/pip.js's auto-injection at its source in
       93fa3302, so the selector matches zero elements app-wide. Checked
       live before deleting, not assumed.) */
    /* S3-40 — restyle, not a rebuild: the frame draws this as a
       `seal/chip-tiny` (421:504, 62x23.14 — same instance height as
       Trading's broker-selector chip, 432:5594, not this card's other
       22px chip family), matching the K9 context in design/PLAN.md's
       chip table (fill #1A1A1A, label white) rather than K1/K7/K8's
       #15191C/teal-border pair — checked the fill directly (a pixel
       sample of the exported frame reads flat #1A1A1A, not any accent
       tint) before assuming a context. Cited via already-materialised
       tokens: --ra-text-neutral-900 (#1A1A1A, TOKENS.json) for fill,
       --ra-border-strong (#1A2332) for the border, --ra-text-inverse
       (#FFFFFF) for the label — no new value minted. Radius 3->6 (QA's
       finding). Also dropped `text-transform:uppercase`: the frame's
       label reads "Compare", not "COMPARE" — confirmed in the same
       pixel crop used for the fill, not assumed from the geometry fix
       alone. `.on` is left undecided by the frame (a single static
       instance shows no toggled state) — kept on the existing
       info-tinted treatment rather than inventing one, per this
       session's standing rule against resolving an undrawn state. */
    /* text-transform:capitalize, not none: the underlying text is the
       lowercase i18n key itself (data-it=compare -> "compare" in
       English, i18n.js:424), so dropping the transform outright would
       have rendered lowercase "compare" against the frame's "Compare" —
       capitalize gets the frame's casing without touching the i18n key
       (which the Spanish "comparar" entry is keyed on verbatim). */
    /* G01b (CD-28/29/30 ≡ CD-400-403, the same "Compare" chip filed under
       both shell's and intel's own numbering — one fix). Radius/border
       already matched the de-scaled contract (6px/1px, [SUB-1PX] class).
       CD-29/403: type 500/11/ls.2 (was 600/10/ls.6). CD-402: padding 6px
       inline (was 8). Text-transform kept — the frame's own text is
       literally "Compare" (capital), the app's i18n string is lowercase
       "compare" needing the transform to read the same, not a delta. */
    /* CD-403 (=CD-29, §T7.7 D8): line-height normal -> 16, one element one
       fix. CD-402: 63.41 -> 62 wide (padding-inline 6 already correct). */
    /* S51 WP4: `.chip` variant (kit: ds/atoms/chip.css) -- the rail's compare toggle: 23x62, neutral-900 fill, h6 uppercase. */
    .chip.intel-rail-cmp{display:inline-flex;align-items:center;gap:var(--ra-sp-xs);height:var(--ra-size-control-sm);width:62px;box-sizing:border-box;padding:0 var(--ra-sp-xs);background:var(--ra-text-neutral-900);border:1px solid var(--ra-border-strong);border-radius:var(--ra-radius-sm);color:var(--ra-text-inverse);font:var(--ts-h6-table-header);text-transform:uppercase;letter-spacing:var(--ts-h6-table-header-ls);white-space:inherit;cursor:pointer;margin-right:var(--ra-sp-2xs);transition:color var(--mo-dur-hover-out)}
    .chip.intel-rail-cmp:hover,.chip.intel-rail-cmp:focus-visible{color:var(--ra-text-hover);transition-duration:var(--mo-dur-hover-in)}
    .chip.intel-rail-cmp.on{color:var(--info);border-color:var(--ra-k-info-55-14-a55);background:var(--info-bg)}

/* WP7 trading: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_trading.json). */
    /* G01b/G06b (CD-200-208,210): the order-ticket chip-tiny contract
       (§T2.1, verified by components.css's .chip-tiny — panels are NOT
       migrated onto that class, "no refactor" ruling, each corrected to
       the table in its own file). height:22px — CLOSED per
       COMPONENT_DIFF.md §2.6: seal/chip-tiny has two authored heights
       (h22 unscaled, h23.14 on the one x1.19-scaled instance that
       de-scales to 19.44); the order ticket is overwhelmingly the
       unscaled variant, so 22 is correct here. */
    /* CD-207/CD-210 (B2): TYPE (txTypeRow) and TIF (txTifRow) share this one
       .tx-pill rule, which is why both rows carry the same residual — the
       Figma 3/7 was taken literally instead of read through §10's "1px
       INSIDE stroke" rule (Figma padding includes the stroke; CSS
       padding + border is additive), which converts to 2/6. QTY and PRICE
       chips (.tx-chip, a different rule below) already measure exact. */
    /* S51 WP4: `.chip-tiny` variant (kit: ds/atoms/chip.css) -- the TYPE / TIF presets: this panel's mono type,
       2/4 inset and bg-3 fill over the kit's chip-tiny box. */
    #p_trad .chip-tiny.tx-pill{
      justify-content:normal;white-space:inherit;
      background:var(--bg-3);border:1px solid var(--surf-hi);color:var(--text-2);
      height:var(--ra-size-control-sm);padding:var(--ra-sp-3xs) var(--ra-sp-xs);box-sizing:border-box;border-radius:var(--ra-radius-sm);
      font:500 var(--ra-fs-11)/var(--ra-lh-16) var(--font-mono);letter-spacing:var(--ra-ls-px-0-2);cursor:pointer;
      display:inline-flex;align-items:center;
    }
    #p_trad .chip-tiny.tx-pill:hover{color:var(--text-1);border-color:var(--line-strong)}
    /* CD-200/201 (G06b): active fill/border/label are the accent itself
       (surface.tape + accent.dark border + text.primary), not the
       info-hue blue the app had been using. */
    #p_trad .chip-tiny.tx-pill.on{background:var(--bg-2);border-color:var(--ra-accent-dark);color:var(--text-1)}
    /* G01b (CD-202-208,210): same contract as .tx-pill above. */
    /* S51 WP4: `.chip-tiny` variant -- the QTY / PRICE presets (same contract as .tx-pill, 2/7 inset, tabular). */
    #p_trad .chip-tiny.tx-chip{
      justify-content:normal;white-space:inherit;
      background:var(--bg-3);border:1px solid var(--surf-hi);color:var(--text-2);
      height:var(--ra-size-control-sm);padding:var(--ra-sp-3xs) var(--ra-sp-7);box-sizing:border-box;border-radius:var(--ra-radius-sm);
      font:500 var(--ra-fs-11)/var(--ra-lh-16) var(--font-mono);letter-spacing:var(--ra-ls-px-0-2);cursor:pointer;
      display:inline-flex;align-items:center;
      font-variant-numeric:tabular-nums;
    }
    #p_trad .chip-tiny.tx-chip:hover{color:var(--text-1);border-color:var(--line-strong)}

/* WP7 harness-chat: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_harness-chat.json). */
/* `.chip` is the platform's own (components.css); the declarations below only
   add what a chip needs to act as a toggle and restate the recipe as a
   fallback for a page without components.css. */
.hc-shell .chip {
  display: inline-flex; align-items: center; gap: var(--ra-sp-2xs);
  height: var(--ra-size-control-sm); padding: 0 var(--ra-sp-sm);
  border-radius: var(--ra-radius-sm, 6px);
  background: var(--ra-surface-card, #23282C);
  border: 1px solid transparent;
  color: var(--ra-text-secondary, #9AA1A4);
  font-family: var(--font-sans, "Inter", ui-sans-serif, system-ui, sans-serif); font-size: var(--ra-fs-10); font-weight: 500;
  white-space: nowrap; cursor: pointer;
  transition: filter var(--mo-dur-hover-in); /* motion-lint-ignore: filter: a brightness hover lifts fill and ink together, no opacity layer reproduces it */
}
.hc-shell .chip:hover { filter: brightness(1.12); }
.hc-shell .chip:disabled { opacity: 0.5; cursor: default; pointer-events: none; }
/* a switched-on shell chip takes the kit's on face (chip.css .chip.on, restated over the shell's fill and weight); the
   persona chips keep the tiny chip's colours (harness-chat-head.css) and share its Semi Bold */
.hc-shell .chip.on { background: var(--ra-state-row-hover); font-weight: 600; }
.hc-shell .hc-persona.on {
  background: var(--ra-surface-tape, #15191C);
  border-color: var(--ra-accent-dark, #35A08B);
  color: var(--ra-text-primary, #EEF1F1);
}
.hc-shell .chip-tiny {
  display: inline-flex; align-items: center; height: 18px; padding: 0 var(--ra-sp-7);
  border-radius: var(--ra-radius-sm, 6px);
  background: var(--ra-surface-card, #23282C); border: 1px solid var(--ra-surface-card-elevated, #2A2F33);
  color: var(--ra-text-secondary, #9AA1A4);
  font: var(--ts-caption-helper); letter-spacing: var(--ra-ls-px-0-2); white-space: nowrap;
}
/* == ds/molecules/empty-state.variants.css == */
/* ds/molecules/empty-state.variants.css — the panels' named variants of ds/molecules/empty-state.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/molecules/empty-state.css; edit one panel's state here. */

/* WP7 screener: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_screener.json). */
/* S51 WP4: `.empty-state.note` variant (kit: ds/molecules/empty-state.css) -- the activity cards' dot + line right under
   the head, left, 0 / 6 inside, the app's Label/Stat 12, tracked (Figma State=empty, 2026-10-18). */
.empty-state.note.scr-act-empty{ font: var(--ts-label-stat); font-weight: 400; letter-spacing: var(--ra-ls-em-04); display:flex;align-items:center;gap:var(--ra-sp-xs);padding:0 var(--ra-sp-xs);color:var(--ra-text-muted);text-align:inherit; }
/* S51 WP4: `.empty-state.note` variant -- the news scan's one-line empty. */
.empty-state.note.scr-ns-empty{padding:var(--ra-sp-xl);text-align:center;color:var(--text-4);font-size: var(--ra-xfs-10-5);}

/* WP7 news: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_news.json). */
/* ds/organisms/news-states.css — the news panel's composite, moved verbatim from panels/news/news.css
   (S51 WP6, design/PLAN_S51.md section 7). Delivered with its panel: listed in panels/news/manifest.json
   `styles` by relative path, injected as <style data-panel> exactly as before. Class names unchanged; the look
   composes the kit's atoms (text roles, inks, surfaces) where the extractor placed them. */

/* ── Every state (§F) — shared empty-state treatment, centred, --text-3.
   First load / Empty / Filtered-to-nothing / Error all render through
   this one block; Stale and Degraded are banners ABOVE whatever rows
   already exist (below), not takeovers, per their own §F wording
   ("rows stay" / "the list does not pretend to be live"). ── */
/* `.empty-state` variant (kit: ds/molecules/empty-state.css -- flex column, centred, flex:1): this
   panel's gap, type, colour and inset (M.emptyState({cls:'news-empty', html, action}) in panels/news/states.js). */
#p3 .empty-state.news-empty{
  gap:var(--ra-sp-2xs);color:var(--text-2);font:var(--ts-body-small);padding:var(--ra-sp-2xl);
}

/* WP7 composer: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_composer.json). */
/* `.empty-state.note` variant (kit): the empty group's italic note (M.emptyState({note:true, cls:'cmp-group-empty'})). */
.empty-state.note.cmp-group-empty {
  font-size: var(--ra-fs-10);
  font-style: italic;
  text-align: start;
  padding: var(--ra-sp-sm) var(--ra-sp-2xs);
}
/* `.empty-state.note` variant (kit): the catalog's "no matches" line (M.emptyState({note:true, cls:'cmp-cat-empty'})). */
.empty-state.note.cmp-cat-empty { font: var(--ts-label-stat); text-align: start; padding: var(--ra-sp-2xs); }
/* `.empty-state.note` variant (kit): the saved rail's italic note (M.emptyState({note:true, cls:'cmp-saved-empty'})). */
.empty-state.note.cmp-saved-empty {
  font: var(--ts-label-stat);
  font-style: italic;
  text-align: start;
  padding: var(--ra-sp-sm) var(--ra-sp-2xs);
}

/* WP7 automations: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_automations.json). */
/* `.empty-state.note` variant: the sidebar's italic note (M.emptyState({note:true, cls:'auto-sb-empty'})). */
.empty-state.note.auto-sb-empty {
  padding: var(--ra-xsp-30) var(--ra-sp-xl);
  font: var(--ts-body-small);
  font-style: italic;
}
/* `.empty-state.note` variant: the empty strategy's indented italic note (M.emptyState({note:true, cls:'auto-sb-strat-empty'})). */
.empty-state.note.auto-sb-strat-empty {
  padding: var(--ra-sp-sm) var(--ra-sp-14) var(--ra-sp-sm) var(--ra-xsp-28);
  font-size: var(--ra-fs-10);
  font-style: italic;
  text-align: start;
}
/* `.empty-state` variant (kit: flex:1, centred, text-align center): the workspace's one-line italic prompt in a row. */
.empty-state.auto-workspace-empty {
  flex-direction: row; gap: normal;
  color: var(--text-4);
  font-style: italic;
  padding: var(--ra-xsp-40);
}

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
/* S51 WP4: `.empty-state.note` variant (M.emptyState({note:true})) -- the thesis card's one-line empty, a filled r4 box. */
.empty-state.note.tk-thesis-empty {
  font-size: var(--ra-fs-11);
  color: var(--text-2);
  padding: var(--ra-sp-lg) var(--ra-sp-14);
  background: var(--bg-2);
  border-radius: 4px;
  text-align: center;
}
    /* S51 WP4: `.empty-state.note` variant (M.emptyState({note:true})) -- the briefing's one-line states. */
    .intel-brief .empty-state.note.brief-empty{padding:var(--ra-xsp-30) 0;text-align:center;color:var(--text-4);font:var(--ts-body-small);letter-spacing:var(--ra-ls-em-06)}
    /* S51 WP4: `.empty-state.note` variant (M.emptyState({note:true})) -- the rail / peers loading and no-data lines. */
    .empty-state.note.intel-empty{padding:var(--ra-xsp-30);text-align:center;color:var(--text-4);font:var(--ts-body-small);letter-spacing:var(--ra-ls-em-06)}
    /* CD-499/600 (G56, "high severity" per the audit): the Today aside's
       empty states are an actual card (fill/border/radius/padding), not
       bare text — scoped here so the rail's generic loading/no-data
       .intel-empty (industry list, peers table) is untouched, since
       nothing in this session's audit covers that context. */
    /* CD-499a: fill text.secondary (was text.faint), ls 0 (was .66px). */
    #intel-today .empty-state.note.intel-empty{
      box-sizing:border-box;padding:var(--ra-sp-lg);border-radius:var(--ra-radius-sm);
      background:var(--ra-surface-panel);border:1px solid var(--ra-border-card);   /* 561:10645 empty box: panel fill, #23282C stroke */
      text-align:center;color:var(--ra-text-secondary);letter-spacing:0;
    }

/* WP7 review: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_review.json). */
/* ===== empty / unavailable states (first-class, not afterthoughts) ======= */
/* S51 WP4: `.empty-state` variant (kit: ds/molecules/empty-state.css; M.emptyState({title, helper, action})) -- the
   journal's empty / unavailable block: h3-card-title over body-medium, gap md, inset 4xl/xl, in normal flow (the kit's
   flex:1 + centring reset). The bodies that host it stay block-level: the kit's `.panel-body:has(.empty-state)` column
   rule would otherwise flip them. */
.empty-state.rv-empty { flex: 0 1 auto; justify-content: normal; gap: var(--ra-sp-md); padding: var(--ra-sp-4xl) var(--ra-sp-xl); }
/* its type, moved here from review-entries.css when the old views were retired (2026-10-18); the journal's empty period and
   its "not loading" block draw it (ds/organisms/review-faces.js block()) */
.empty-state.rv-empty .title { font: var(--ts-h3-card-title); letter-spacing: var(--ts-h3-card-title-ls); color: var(--ra-text-primary); }
.empty-state.rv-empty .helper { max-width: 44em; font: var(--ts-body-medium); letter-spacing: var(--ts-body-medium-ls); color: var(--ra-text-secondary); }
.empty-state.rv-empty.warn .helper { color: var(--ra-semantic-warning); }

/* WP7 trading: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_trading.json). */
    /* Empty state inside an activity section — design/DESIGN_SPEC.md
       §2.1.5: 48×48 r24 circle + 20×20 icon + 36px text stack, the shared
       .empty-state component (components.css). Replaces the bespoke
       .tx-empty* (32px square-ish icon, single-line layout) below, which
       is deleted, not restyled — mirrors the .tx-card/.tx-act deletions
       above. .icon/.title/.helper class names come from the shared
       component; only sizing the glyph itself is local. */
    /* CD-236/237 (G39): trading-local corrections over the shared
       .empty-state default (components.css, dev-a) — same "context
       override, not a shared-class rewrite" rule as G01/T2.1. Frame: gap
       12 (was 8), no padding (was 24/16), circle fill Background/Dark
       (--bg-2, was surface.card), title lh 16.94, helper 12/14.52 (was 11/16). */
    #p_trad .empty-state{gap:var(--ra-sp-lg);padding:0}
    /* design-sync 2026-10-03: the kit's title / helper grew to h2/section / body/medium; these activity cards keep their
       frame's h4 title and label-form helper (trading-activity.css, later, keeps the line-heights). */
    #p_trad .empty-state .title{font:var(--ts-h4-card-subtitle);letter-spacing:var(--ts-h4-card-subtitle-ls)}
    #p_trad .empty-state .helper{font:var(--ts-label-form);letter-spacing:var(--ts-label-form-ls)}
/* == ds/molecules/kv.variants.css == */
/* ds/molecules/kv.variants.css — the panels' named variants of ds/molecules/kv.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/molecules/kv.css; edit one panel's state here. */

/* WP7 screener: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_screener.json). */
/* S51 WP4: `.kv.row` variant (kit: ds/molecules/kv.css) -- the account's summary rows and the Closed P&L line: baseline-aligned,
   no padding of their own (Fitts & Miller 22: the summary spaces them 6 apart), 8 between label and value; their type is
   ds/organisms/screener-account.css. */
.kv.row.scr-acc-row{align-items:baseline;gap:var(--ra-sp-sm)}

/* WP7 review and stats: their variants (the range and dashboard tiles, the stats metric tiles, account grid and trade
   detail) went with the old Review views and the Stats view on 2026-10-18. */

/* WP7 trading: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_trading.json). */
    /* S51 WP4: the rail stats, the symbol facts, the preview and the closed summary are the kit's .kv (em.k over b.v;
       ds/molecules/kv.css); every rule below is this panel's variant -- its own type, colour and 1px rhythm over the kit's. */
    #p_trad .kv.tx-stat{display:flex;flex-direction:column;align-items:center;gap:var(--ra-xsp-stack-gap);min-width:0}
/* == ds/atoms/pill.variants.css == */
/* ds/atoms/pill.variants.css — the panels' named variants of ds/atoms/pill.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/pill.css; edit one panel's state here. */

/* WP7 screener: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_screener.json). */
/* Fitts & Miller 18 (frame 18: «tag-pill neutral for sounds»): a sound rule's sound, a neutral chip -- the card line, no
   fill, its 12 note and its name in capitals in text-2, the tag pill's own type (panels/screener/sound-rules.js). */
.tag-pill.snd-sound{flex:none;border-color:var(--ra-border-card);background:none;color:var(--text-2);text-transform:uppercase}
.tag-pill.snd-sound svg{width:var(--ra-size-icon-sm);height:var(--ra-size-icon-sm)}
/* design-sync 2026-10-03 (10-02 «labels on the badge scale»): the AI price chips 26 -> 24, the broker/account status
   chip 23 -> 24 below. */
/* S51 WP4: `.pill` variant (kit: ds/atoms/pill.css) -- the target / current price chips: 24 tall, r9, no stroke, the tones below. */
.pill.ai-pt-chip {
  border: 0; font-size: inherit; color: inherit; background: transparent;
  display: inline-flex; align-items: center;
  /* CD-533: 4 -> 6. */
  gap: var(--ra-sp-xs);
  height: var(--ra-size-badge-md);
  /* CD-532 (PANEL_MATRIX §5's eighth chip-geometry context): 10 -> 12. */
  padding: 0 var(--ra-sp-lg);
  border-radius: 9px;
  font-family: var(--font-sans); white-space: nowrap;
}

/* WP7 composer: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_composer.json). */
/* `.pill` variant (kit: ds/atoms/pill.css): the toast's PREVIEW stamp (A.pill({cls:'cmp-preview-badge'}) in _paintFloatingPreview). */
.sig-alert.preview .pill.cmp-preview-badge {
  position: absolute; display: block; align-items: normal; gap: normal; white-space: normal; height: auto;
  top: 6px; right: 8px;
  font: 700 var(--ra-xfs-8) var(--font-sans);
  letter-spacing: var(--ra-ls-em-16);
  color: var(--text-4);
  background: var(--ra-k-bg-16-a70);
  padding: var(--ra-sp-3xs) var(--ra-sp-xs);
  text-transform: uppercase;
}

/* WP7 automations: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_automations.json). */
/* `.pill` variant: the counts pill, mono type on the panel's fill (A.pill({cls:'auto-card-counts'})). */
.pill.auto-card-counts {
  display: inline-block; align-items: normal; gap: normal; height: auto;
  font-family: var(--font-mono);
  color: var(--text-2);
  padding: var(--ra-sp-3xs) var(--ra-sp-sm);
  background: var(--bg-1);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ra-ls-em-04);
}
.auto-card-hd.dirty .pill.auto-card-counts {
  color: var(--warn);
  border-color: var(--warn);
}
.auto-drawer-hd .pill {
  font-size: var(--ra-fs-9);
  letter-spacing: var(--ra-ls-em-14);
  text-transform: uppercase;
  padding: var(--ra-sp-3) var(--ra-sp-md);
  border-radius: 999px;
  border: 1px solid currentColor;
  font-family: var(--font-mono);
  font-weight: 500;
}
.auto-drawer-hd .pill.trigger { color: var(--pos);  background: var(--pos-bg);  border-color: var(--pos); }
.auto-drawer-hd .pill.cond    { color: var(--info); background: var(--info-bg); border-color: var(--info); }
.auto-drawer-hd .pill.act     { color: var(--ra-k-h320-78-16); background: var(--ra-k-h320-30-10-a18); border-color: var(--ra-k-h320-78-16); }

/* WP7 review: its variant (the old views' broker/account chip) went with them on 2026-10-18. */

/* WP7 trading: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_trading.json). */
    /* Replay paper-trading: badge shown while trading the isolated replay
       account; ticket greyed when replaying with paper off (trading disabled). */
    /* S51 WP4: `.pill` variant (kit: ds/atoms/pill.css) -- the REPLAY PAPER stamp, shown by body.replay-paper only. */
    #p_trad .pill.tx-rpl-badge{display:none;height:auto;gap:normal;align-items:normal;white-space:inherit;border:0;font:var(--ts-label-stat);letter-spacing:var(--ra-ls-em-04);color:var(--ra-text-inverse);background:var(--neg);padding:var(--ra-sp-3xs) var(--ra-sp-sm);border-radius:99px}
    body.replay-paper #p_trad .pill.tx-rpl-badge{display:inline-block}
/* == ds/atoms/button.variants.css == */
/* ds/atoms/button.variants.css — the panels' named variants of ds/atoms/button.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/button.css; edit one panel's state here. */

/* WP7 screener: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_screener.json). */
/* ds/organisms/screener-replay.css — the screener panel's composite, moved verbatim from panels/screener/screener.css
   (S51 WP6, design/PLAN_S51.md section 7). Delivered with its panel: listed in panels/screener/manifest.json
   `styles` by relative path, injected as <style data-panel> exactly as before. Class names unchanged; the look
   composes the kit's atoms (text roles, inks, surfaces) where the extractor placed them. */

/* S51 WP4: `.btn` variant -- the 32px zoom buttons. */
.chart-zoom .btn{gap:normal;font-size:inherit;line-height:inherit;white-space:inherit;transition:initial;padding:0;width:var(--ra-size-control-md);height:var(--ra-size-control-md);background:var(--ra-surface-card);border:1px solid var(--ra-surface-card-elevated);color:var(--ra-text-inverse);border-radius:var(--ra-radius-sm);display:inline-flex;align-items:center;justify-content:center}
.chart-zoom .btn:hover{color:var(--text-1);border-color:var(--line-strong);background:var(--ra-surface-card)}
/* The chart head's Indicators and Patterns triggers (CD-169, G34, ruling #3; base.md §8.2 node `321:1760`): the
   header-dropdown skin, FILLED with the card surface and its elevated border, an 11/500 ls0.2 label (CD-164/165, G28),
   24 tall (design-sync 2026-10-03, «Dropdown heights»). The compound `.btn.cind` ties `.btn.sm`'s (0,2,0) and wins on
   source order. The chart's timeframe and mode triggers left this family with Fitts & Miller 08: they are the kit's
   select now (ds/organisms/screener-chart-modes.css). */
.btn.cind,.btn.cpat{ font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); min-width:28px;padding:0 var(--ra-sp-xs); color: var(--ra-text-secondary); background: var(--ra-surface-card); border-color: var(--ra-surface-card-elevated);border-radius: var(--ra-radius-sm);height:var(--ra-size-control-sm);border-radius:var(--ra-radius-sm);text-transform:none; }
.btn.cind:hover,.btn.cpat:hover{color:var(--text-1)}
/* ds/organisms/screener-sound-rules.css — the screener panel's composite, moved verbatim from panels/screener/screener.css
   (S51 WP6, design/PLAN_S51.md section 7). Delivered with its panel: listed in panels/screener/manifest.json
   `styles` by relative path, injected as <style data-panel> exactly as before. Class names unchanged; the look
   composes the kit's atoms (text roles, inks, surfaces) where the extractor placed them. */

/* ---------- Sound rules popover (NSD %Gainers + News Scan heads) ---------- */
/* CD-139 (G34, ruling #3): base.md §6.2 `410:395` "Cols ▾" — a labelled
   seal/chip-tiny, same fill/border/label idiom as the chart's TFs▾/Modes▾
   (`.ctf-menu`/`.cmode-menu` above), but this card (321:468) is UNSCALED —
   height 22, not those chips' 19.44 (23.14/1.19, chart-10S's own x1.19
   layer). Geometry stays local; only the skin is shared.
   `.btn.cols-chip` (not bare `.cols-chip`): `.btn.ghost`/`.btn.sm`
   (components.css) are both (0,2,0) and would otherwise beat a single-class
   selector on background/font-size — same tie-break as `.btn.ctf-menu`
   above. */
.btn.cols-chip{ font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); height:var(--ra-size-control-sm);--ic-size:var(--ra-size-icon-sm);padding:0 var(--ra-sp-7) 0 var(--ra-sp-xs);gap:var(--ra-sp-2xs);color:var(--text-1);background: var(--ra-surface-card);border-color:var(--ra-surface-card-elevated);border-radius:var(--ra-radius-sm); }
/* design-sync 2026-10-03: Cols is a `select` since the 2/10 review (screener-gainers): value text-1, icons secondary. */
.btn.cols-chip .ic{color:var(--ra-text-secondary)}
.btn.cols-chip:hover{background:var(--ra-surface-card-elevated);color:var(--ra-text-hover)}
/* Same class-not-inline move for the remove control (was
   style="width:22px;height:22px;padding:0;margin-left:auto;
   color:var(--text-4)"). Compound with .btn to win over .btn.sm, same
   convention `.btn.cta`/`.btn.ghost.block` (components.css) use. */
.btn.snd-cond-rm{width:var(--ra-size-control-sm);height:var(--ra-size-control-sm);padding:0;margin-left:auto;color:var(--text-4)}
/* S17 #21 (S17-R25): `--ra-text-inverse` is FIXED; this button's own
   `background:var(--ra-surface-card)` FLIPS -- measured RED, live,
   before this fix. `--ra-text-primary` (flip-safe). NOTE, not silently
   left for a later session to re-discover: this rule is explicitly
   modelled on `.chart-zoom button`'s own geometry (comment above,
   "target is .chart-zoom button's own 6px") and `.chart-zoom button`
   carries the IDENTICAL bg-flips/text-fixed pairing -- but it is not one
   of this task's named 21 census sites (S17-R25's own scope), so it is
   NOT fixed here. Fixing one sibling and not the other is a real,
   visible inconsistency this note exists to flag, not hide -- same
   disposition the ruling itself gives the rest of the tail (published,
   not chased). */
/* S51 WP4: `.btn` variant -- the EN / TP / SL chart-order buttons trading.js builds beside the zoom pair. */
.chart-protect .btn{ font: var(--ts-caption-helper); gap:normal;white-space:inherit;transition:initial;padding:0;width:28px;height:var(--ra-size-control-sm);background:var(--ra-surface-card);border:1px solid var(--ra-surface-card-elevated);color:var(--ra-text-primary);border-radius:var(--ra-radius-sm);letter-spacing:var(--ra-ls-em-03);display:inline-flex;align-items:center;justify-content:center; }
.chart-protect .btn:hover{color:var(--text-1);border-color:var(--line-strong);background:var(--ra-surface-card)}
.chart-protect .btn.on{color:var(--info);border-color:var(--info);background:var(--ra-surface-card)}
.chart-protect .btn:disabled{opacity:.3;cursor:default}
/* 1. chart-settings-icons — REMOVED, task #41 (S47): the 5-button cluster
 * this rule used to lay out is gone from pane.html; `.ai-icon-btn`
 * (below) is reused by the header row's two new controls instead
 * (`ai-agent.js`'s `_ensureHeader`), so it stays. */
/* S51 WP4: `.btn` variant (kit: ds/atoms/button.css) -- the card head's 22px tiles. */
.btn.ai-icon-btn {
  width: var(--ra-size-control-sm); height: var(--ra-size-control-sm); flex-shrink: 0; gap: normal; font-size: inherit; line-height: inherit; white-space: inherit; transition: initial;
  display: flex; align-items: center; justify-content: center;
  background: var(--ra-surface-card); border: 0; border-radius: var(--ra-radius-sm);
  /* CD-509 (design/COMPONENT_DIFF.md §14.4 item 1 — the CD-0 seed defect's
     same colour class): frame text.axis (#606060), not text.tertiary
     (#6B7276) — the other `chart-settings-icons` instance this file's own
     comment above names. */
  color: var(--ra-text-axis); padding: 0;
}
.btn.ai-icon-btn:hover { color: var(--ra-text-primary); background: var(--ra-surface-card-elevated); border-color: transparent; }
/* CD-542/543: 38x38 (was 22x22 — existence is ACCEPTED-S3/D6, only the
   geometry was never dispositioned until now), r9 (was --ra-radius-sm/6). */
/* CD-547: icon colour text.neutral-300 (was text.tertiary). CD-546 (icon
   SET/order — frame: retry/copy/thumbs-down/thumbs-up; app: copy/
   regenerate/thumbs-up/thumbs-down) NOT reordered: "regenerate" and
   "retry" read as the same action under different names but I'm not
   certain enough of that to reorder + relabel four live, working
   controls on a guess — flagged, not silently left unmeasured. */
/* S51 WP4: `.btn.quiet` variant -- the answer actions: 38px, r9, neutral-300 ink. */
.btn.quiet.ai-action-btn {
  width: var(--ra-size-control-md); height: var(--ra-size-control-md); flex-shrink: 0; gap: normal; font-size: inherit; line-height: inherit; white-space: inherit; transition: initial;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 0; border-radius: 9px;
  color: var(--ra-text-neutral-300); padding: 0;
}
.btn.quiet.ai-action-btn:hover { color: var(--ra-text-primary); background: var(--ra-surface-card); }
/* CD-548: the frame's own export renders this glyph as a colour emoji
   (font-fallback artefact) despite declaring a monochrome fill — P14's
   default (ship the monochrome mark) is already what this button does,
   an inline stroked SVG, not a character glyph. CD-549: colour
   text.axis (was text.tertiary). */
/* S51 WP4: `.btn.quiet` variant -- the 16px attach glyph. */
.btn.quiet.ai-attach-btn {
  flex-shrink: 0; width: var(--ra-size-icon-lg); height: var(--ra-size-icon-lg); padding: 0; gap: normal; border-radius: 0; font-size: inherit; line-height: inherit; white-space: inherit; transition: initial;
  background: transparent; border: 0; color: var(--ra-text-axis);
  display: flex; align-items: center; justify-content: center;
}
/* S51 WP4: `.btn` variant -- the round send button; 44 + 20 px icon since design-sync 2026-10-03 (control-lg). */
.btn.ai-send-btn {
  --ic-size: var(--ra-size-icon-xl);
  flex-shrink: 0; width: var(--ra-size-control-lg); height: var(--ra-size-control-lg); border-radius: 22px; padding: 0; gap: normal; font-size: inherit; line-height: inherit; white-space: inherit; transition: initial;
  background: var(--ra-accent-primary-soft); border: 0;
  /* CD-552 (S7-T3, dev-a): same wrong-token species as CD-536, same
     remedy — --ra-text-on-accent is #010203, the frame's glyph is
     #15191C, which --ra-surface-tape already carries exactly. */
  color: var(--ra-surface-tape);
  display: flex; align-items: center; justify-content: center;
}
.btn.ai-send-btn:hover { background: color-mix(in oklch, var(--ra-accent-primary-soft) 85%, white); color: var(--ra-surface-tape); border-color: transparent; }
/* WP7 news: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_news.json). */
/* `.btn` variant (kit: ds/atoms/button.css): the Retry / Clear-filter action, an outlined accent button. */
#p3 .news-empty .btn.nw-retry{
  display:inline-block;align-items:normal;justify-content:normal;gap:normal;white-space:normal;transition:initial;height:auto;
  margin-top:var(--ra-sp-sm);font:var(--ts-h6-table-header);letter-spacing:var(--ts-h6-table-header-ls);
  color:var(--ra-accent-primary);border-color:var(--ra-accent-primary);
  padding:var(--ra-sp-3xs) var(--ra-sp-md);background:none;
}

/* WP7 composer: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_composer.json). */
/* `.btn` variant (kit: ds/atoms/button.css): the "+ rule / + group / x" mini buttons -- caption type, r3,
   a translucent fill (A.btn({cls:'cmp-mini-btn'}) in renderGroup; the delegated handler reads data-* off
   the button itself, so no inner span). `.danger` here is a hover-only tone, so the kit's resting danger
   fill is restated to the mini button's own. */
.btn.cmp-mini-btn {
  display: inline-block; align-items: normal; justify-content: normal; gap: normal; white-space: normal; height: auto;
  font: var(--ts-caption-helper);
  padding: var(--ra-sp-3xs) var(--ra-sp-xs);
  border-radius: var(--ra-radius-2xs);
  background: var(--ra-k-bg-18-a60);
  color: var(--text-2);
  letter-spacing: var(--ra-ls-em-04);
  /* S31 PLAN §2/§5 B3 (dev-b): `all` -> the three properties :hover
     actually changes, named + tokenised. */
  transition-property: color, border-color, background;
  transition-duration: var(--mo-dur-hover-out);
}
.btn.cmp-mini-btn:hover, .btn.cmp-mini-btn:focus-visible {
  color: var(--text-1);
  border-color: var(--line-strong);
  background: var(--bg-2);
  transition-duration: var(--mo-dur-hover-in);
}
.btn.cmp-mini-btn.danger { border-color: var(--line); font-weight: inherit; }
.btn.cmp-mini-btn.danger:hover, .btn.cmp-mini-btn.danger:focus-visible {
  color: var(--neg);
  border-color: var(--ra-k-neg-55-14-a50);
}
/* CD-657 (G22, dev-a): was a bare U+00D7 text glyph at font-size:14px —
   swapped for the shared close.svg asset (foundation/icons.js's PV2.icon,
   called directly in the template string this button is built from), so
   font-size/line-height no longer apply to its content. */
/* `.btn.quiet` variant (kit: ds/atoms/button.css): the 18px remove glyph (A.btn({kind:'quiet', cls:'cmp-chip-x'})). */
.btn.quiet.cmp-chip-x {
  width: var(--ra-size-control-xs); height: var(--ra-size-control-xs); padding: 0; gap: normal; white-space: normal; font-size: inherit; line-height: inherit;
  --ic-size: var(--ra-size-icon-sm);
  border-radius: var(--ra-radius-2xs);
  color: var(--text-4);
  margin-left: var(--ra-sp-3xs);
  /* S31 PLAN §2/§5 B3 (dev-b): was `color` only though :hover also
     changes `background` -- completed the property list while
     tokenising. */
  transition-property: color, background;
  transition-duration: var(--mo-dur-hover-out);
}
.btn.quiet.cmp-chip-x:hover, .btn.quiet.cmp-chip-x:focus-visible {
  color: var(--neg);
  background: var(--ra-k-neg-55-14-a15);
  transition-duration: var(--mo-dur-hover-in);
}
/* `.btn.quiet` variants (kit): the pin star and the delete glyph of a saved row (A.btn({kind:'quiet'}) in
   renderSavedList; the delegated handlers read data-pin / data-del off the button itself, so no inner span). */
.btn.quiet.cmp-saved-pin {
  display: inline-block; align-items: normal; justify-content: normal; gap: normal; white-space: normal; height: auto; border-radius: 0; line-height: normal;
  font-size: var(--ra-fs-12); color: var(--text-4);
  padding: 0 var(--ra-sp-xs);
  transition: color var(--mo-dur-hover-out);
}
.btn.quiet.cmp-saved-pin.on { color: var(--warn); background: transparent; border-color: currentColor; }
.btn.quiet.cmp-saved-pin:hover, .btn.quiet.cmp-saved-pin:focus-visible { color: var(--text-1); background: transparent; transition-duration: var(--mo-dur-hover-in); }
.btn.quiet.cmp-saved-x {
  display: inline-block; align-items: normal; justify-content: normal; gap: normal; white-space: normal; height: auto; padding: 0; border-radius: 0; line-height: normal;
  width: 22px;
  color: var(--text-4);
  border-left: 1px solid var(--line);
  font-size: var(--ra-fs-13);
  /* S31 PLAN §2/§5 B3 (dev-b): `all` -> `color`/`background`, named +
     tokenised. */
  transition-property: color, background;
  transition-duration: var(--mo-dur-hover-out);
}
.btn.quiet.cmp-saved-x:hover, .btn.quiet.cmp-saved-x:focus-visible {
  color: var(--neg);
  background: var(--ra-k-neg-55-14-a12);
  transition-duration: var(--mo-dur-hover-in);
}

/* WP7 automations: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_automations.json). */
.auto-sb-toolbar .btn { height: var(--ra-size-control-sm); }
/* `.btn.quiet` variant: the 18px add / delete glyphs of a strategy header (A.btn({kind:'quiet', cls:'auto-sb-strat-btn'})). */
.btn.quiet.auto-sb-strat-btn {
  padding: 0; width: var(--ra-size-control-xs); height: var(--ra-size-control-xs); --ic-size: var(--ra-size-icon-sm);
  border-radius: var(--ra-radius-2xs);
  color: var(--text-4); font-size: var(--ra-fs-13); line-height: 1;
  display: flex; gap: normal; white-space: normal; transition: initial;
}
.btn.quiet.auto-sb-strat-btn:hover { background: var(--bg-2); color: var(--text-1); }
/* `.btn` variant: the canvas toolbar's fit button (A.btn({cls:'auto-fit'}) in renderCanvas). */
.auto-canvas-toolbar .btn {
  height: var(--ra-size-control-sm);
  padding: 0 var(--ra-sp-md);
  background: var(--bg-1);
  border-radius: var(--r-sm);
  font-size: var(--ra-fs-10); line-height: normal; white-space: normal; transition: initial;
  color: var(--text-2);
  letter-spacing: var(--ra-ls-em-06);
}
.auto-canvas-toolbar .btn:hover { color: var(--text-1); border-color: var(--line-strong); background: var(--bg-1); }
/* `.btn.quiet` variant: the round red delete-from-chain glyph on an edge (A.btn({kind:'quiet', cls:'auto-edge-del'})). */
.btn.quiet.auto-edge-del {
  position: absolute;
  width: var(--ra-size-control-sm); height: var(--ra-size-control-sm); padding: 0;
  border-radius: 50%;
  background: var(--neg);
  color: var(--text-1);
  font-size: var(--ra-fs-14); line-height: 0; gap: normal; white-space: normal; transition: initial;
  display: flex;
  z-index: 5;
  transform: translate(-50%, -50%);
  box-shadow: 0 2px 6px var(--ra-k-black-a40);
}
.btn.quiet.auto-edge-del:hover {
  background: var(--neg);
  filter: brightness(1.1);
  transform: translate(-50%, -50%) scale(1.1);
}
/* `.btn.quiet` variants: the socket menu's two items (A.btn({kind:'quiet', cls:'add-cond'|'add-act'})). */
.auto-socket-menu .btn.quiet {
  display: block; align-items: normal; justify-content: normal; gap: normal; white-space: normal; height: auto; transition: initial;
  text-align: left;
  padding: var(--ra-sp-xs) var(--ra-sp-md); border-radius: var(--ra-radius-xs);
  color: var(--text-1);
  font: inherit;
}
.auto-socket-menu .btn.quiet:hover { background: var(--bg-3); }
.auto-bottom-tabs-right .btn {
  height: var(--ra-size-control-sm);
  font-size: var(--ra-fs-10);
  padding: 0 var(--ra-sp-md);
}
/* `.btn.ghost` variants: the drawer's close glyph and its full-width remove button (A.btn({kind:'ghost'})). */
.auto-drawer-hd .btn.ghost.close-x {
  display: inline-block; align-items: normal; justify-content: normal; gap: normal; white-space: normal; line-height: normal; transition: initial;
  border-radius: var(--r-sm); padding: 0; width: var(--ra-size-control-sm); height: var(--ra-size-control-sm);
  color: var(--text-2);
  font-size: var(--ra-fs-13);
}
.auto-drawer-hd .btn.ghost.close-x:hover { border-color: var(--text-1); color: var(--text-1); background: transparent; }
.auto-drawer .btn.ghost.auto-node-del {
  display: inline-block; align-items: normal; justify-content: normal; gap: normal; white-space: normal; line-height: normal; transition: initial; height: auto;
  border-radius: var(--r-sm); padding: var(--ra-sp-xs) var(--ra-sp-md);
  color: var(--text-2); font-size: var(--ra-fs-11);
  margin-top: var(--ra-sp-xl);
  width: 100%;
}
.auto-drawer .btn.ghost.auto-node-del:hover {
  border-color: var(--neg); color: var(--neg);
  background: var(--neg-bg);
}

/* WP7 watchlists: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_watchlists.json). */
  /* .btn variant — the two icon-only head-bar buttons (pane.html). */
  .btn.wl-btn{display:inline-block;align-items:normal;justify-content:normal;gap:normal;height:auto;
    padding:var(--ra-sp-2xs) var(--ra-xsp-9);border-radius:var(--ra-radius-xs);background:transparent;color:var(--text-2);
    font:inherit;line-height:1;white-space:normal;
    transition:color var(--mo-dur-hover-out),border-color var(--mo-dur-hover-out)}
  .btn.wl-btn:hover,.btn.wl-btn:focus-visible{color:var(--text-1);border-color:var(--text-4);background:transparent;transition-duration:var(--mo-dur-hover-in)}

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
.btn.oil2-iconbtn { display: inline-block; align-items: normal; justify-content: normal; gap: normal; padding: 0; font-size: inherit; line-height: inherit; white-space: inherit; background: var(--bg-2); color: var(--text-2); border: 1px solid var(--line); border-radius: 3px; width: var(--ra-size-control-sm); height: var(--ra-size-control-sm); transition: background var(--mo-dur-hover-out), color var(--mo-dur-hover-out); }
.btn.oil2-iconbtn:hover, .btn.oil2-iconbtn:focus-visible { background: var(--bg-3); color: var(--text-1); border-color: var(--line); transition-duration: var(--mo-dur-hover-in); }
/* S51 WP4: `.btn.quiet` variant -- the sidebar config's Reset, a bare uppercase text button in normal flow. */
.btn.quiet.td-cfg-reset {
  display: inline-block; align-items: normal; justify-content: normal; gap: normal; height: auto; border-radius: 0; white-space: inherit;
  font: var(--ts-label-stat); letter-spacing: var(--ra-ls-em-04); text-transform: uppercase;
  color: var(--text-4); padding: 0;
  transition: color var(--mo-dur-hover-out);
}
.btn.quiet.td-cfg-reset:hover, .btn.quiet.td-cfg-reset:focus-visible { color: var(--info); transition-duration: var(--mo-dur-hover-in); }
/* The aggregate-count caption borrowed .oil2-eye's type (Inter 600 10px
   ls.08em uppercase) too — same rename, its own name, identical values so
   nothing visibly changes. */
/* Owner 2026-09-11: the same recipe as the rail's own footer
   (.intel-rail-foot > span, this file) — 12px table-header type, uppercase,
   text.dim — instead of the 14px semibold stat label. */

/* S51 WP4: `.btn.quiet` variant -- the aside's refresh glyph, in normal flow at its own inset. */
.btn.quiet.td-foot-btn {
  display: inline-block; align-items: normal; justify-content: normal; gap: normal; height: auto; border-radius: 0; white-space: inherit;
  color: var(--text-2);
  font: var(--ts-dropdown-menu-item); padding: var(--ra-sp-2xs) var(--ra-sp-xs);
  transition: color var(--mo-dur-hover-out);
}
.btn.quiet.td-foot-btn:hover, .btn.quiet.td-foot-btn:focus-visible { color: var(--text-1); transition-duration: var(--mo-dur-hover-in); }
/* S51 WP4: `.btn` variant -- the thesis refresh: 10px uppercase, r3, bg-2, the two-property hover transition. */
.btn.tk-thesis-refresh {
  display: inline-block; align-items: normal; justify-content: normal; gap: normal; height: auto; line-height: inherit; white-space: inherit;
  margin-left: auto;
  font-size: var(--ra-fs-10);
  letter-spacing: var(--ra-ls-em-04);
  text-transform: uppercase;
  color: var(--text-2);
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: var(--ra-sp-3) var(--ra-sp-md);
  /* S31 PLAN §2/§5 B3 (dev-b): `all` -> the two properties :hover
     actually changes, named + tokenised. */
  transition-property: color, border-color;
  transition-duration: var(--mo-dur-hover-out);
}
.btn.tk-thesis-refresh:hover, .btn.tk-thesis-refresh:focus-visible { color: var(--text-1); background: var(--bg-2); border-color: var(--text-4); transition-duration: var(--mo-dur-hover-in); }

/* WP7 trading: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_trading.json). */
    /* QA T7 (round 1, flagged NEW): the frame's Badge is exact (skin
       already matched, this class's own contract above) — only hide-at-
       zero was missing, the same behaviour as .tx-act-count/#stTrCount
       below, just a different class the owner's P7 sweep didn't name. */
    /* CD-242/243 (G40): Connect/Disconnect + the three tool buttons are
       nav-bar chip-tiny / tool-button skins, scoped locally per T2.1's
       "context override, not a shared class" rule — .btn.sm itself
       (components.css, dev-a) is untouched.
       QA T4 (round 1): both were rendering 24 tall (height:auto let
       line-height+padding+border expand past the frame's h22) —
       box-sizing:border-box;height:22px pins it, matching the sibling
       .tx-rail-tools rule below which already had this. */
    #p_trad #ibBtnC.btn.primary.sm{
      background:var(--bg-2);border:1px solid var(--ra-accent-dark);
      border-radius:var(--ra-radius-sm);padding:0 var(--ra-sp-7);box-sizing:border-box;height:var(--ra-size-control-sm);
      color:var(--text-1);font:var(--ts-h6-table-header);letter-spacing:var(--ts-h6-table-header-ls);
      text-transform:none;
    }
    /* CD-657 (design/COMPONENT_DIFF.md, S6-T8): was a 20px U+00D7 text
       glyph — font-size no longer applies to the registry `close` svg
       (pane.html), dropped rather than left dead. */
    /* S51 WP4: `.btn.quiet` variant (kit: ds/atoms/button.css) -- the drawers' close glyph, in normal flow at its own inset. */
    #p_trad .btn.quiet.tx-drawer-x{
      display:inline-block;align-items:normal;justify-content:normal;gap:normal;height:auto;border-radius:0;
      font-size:inherit;line-height:inherit;white-space:inherit;transition:initial;
      color:var(--text-2);padding:var(--ra-sp-3xs) var(--ra-sp-xs);
    }
    #p_trad .btn.quiet.tx-drawer-x:hover{color:var(--text-1)}

/* WP7 harness-chat: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_harness-chat.json). */
/* `.btn.quiet` variant: the 22px gear. */
.btn.quiet.hc-icon-btn {
  width: var(--ra-size-control-sm); height: var(--ra-size-control-sm); padding: 0; gap: normal; white-space: inherit; transition: initial;
  border-radius: var(--ra-radius-sm, 6px);
  color: var(--ra-text-secondary, #9AA1A4); font-size: var(--ra-fs-14); line-height: 1;
}
.btn.quiet.hc-icon-btn:hover { background: var(--ra-surface-card, #23282C); color: var(--ra-text-primary, #EEF1F1); }
/* `.btn` variant: the round send / stop button; 44 + 20 px icon since design-sync 2026-10-03. */
.btn.hc-send {
  --ic-size: var(--ra-size-icon-xl);
  flex-shrink: 0; width: var(--ra-size-control-lg); height: var(--ra-size-control-lg); padding: 0; gap: normal; white-space: inherit;
  border-radius: var(--ra-radius-full, 999px);
  background: var(--ra-accent-primary-soft, #7EE0CD);
  border: 0;
  color: var(--ra-surface-tape, #15191C); font-size: var(--ra-fs-18); line-height: 1;
  transition: filter var(--mo-dur-hover-in); /* motion-lint-ignore: filter: a brightness hover lifts fill and ink together, no opacity layer reproduces it */
}
.btn.hc-send:hover { filter: brightness(1.08); background: var(--ra-accent-primary-soft, #7EE0CD); color: var(--ra-surface-tape, #15191C); border-color: transparent; }
.btn.hc-send[data-mode=stop], .btn.hc-send[data-mode=stop]:hover { background: var(--ra-semantic-warning, #F59E0B); }

/* The kit's disabled and busy faces, for every .btn (btn-states, 2026-10-07). Last in this file, so at the same
   specificity they win over the variants above; a variant with a measured face of its own keeps it
   (.chart-protect .btn:disabled, 30 %), and so do later organism rules (#setSave, .tx-submit). The variants above no
   longer restate .btn's cursor:pointer, so none of them beats these. Busy: aria-busy on the button (the chart
   popover's Place order, a Test or Save on the async table), or a disabled button inside a busy region (the
   drop-defaults popover's Remove / Save / Cancel): the progress cursor of ds/base/states.css, which loses to .btn's
   pointer without this rule. */
.btn:disabled { opacity: var(--ra-opacity-disabled); cursor: default; }
.btn[aria-busy="true"], [aria-busy="true"] .btn:disabled { cursor: progress; }
/* == ds/atoms/input.variants.css == */
/* ds/atoms/input.variants.css — the panels' named variants of ds/atoms/input.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/input.css; edit one panel's state here. */

/* WP7 screener: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_screener.json). */
/* The sound rules' Name field (Fitts & Miller 18): it fills its row, its value 11 Regular (the app's field). */
.snd-row .input{flex:1;font:400 var(--ra-fs-11)/var(--ra-lh-16) var(--font-sans)}
.pp-row .input{width:74px;height:var(--ra-size-control-sm);font-size:var(--ra-fs-11)}
/* CD-550: placeholder run 14px (was 12). */
/* S51 WP4: `.input.bare` variant (kit: ds/atoms/input.css) -- the prompt field, drawn by its pill row. */
.input.bare.ai-prompt-input { font: var(--ts-body-medium); letter-spacing: var(--ts-body-medium-ls); flex: 1; min-width: 0; height: 100%;
  outline: none;
  color: var(--ra-text-primary); }
.input.bare.ai-prompt-input::placeholder { color: var(--ra-text-axis); }

/* WP7 news: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_news.json). */
/* `.input.bare` variant (kit: ds/atoms/input.css): the field is the pill; this is its own flex and type. */
#p3 .nw-search .input.bare{
  flex:1 1 auto;min-width:0;
  font:var(--ts-body-small);color:var(--text-1);
}

/* WP7 composer: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_composer.json). */
/* S51 WP4: this panel composes the kit's atoms; each rule that remains for a primitive is a VARIANT
   (only this panel's deltas from ds/atoms|molecules/*.css), never a re-rolled copy. The census graded
   every element at zero (design/s51/CHAIN.md, wp4-composer). The condition ROW (.cmp-chip), the type
   cards (.cmp-type), the catalog items and the saved-panel rows are this panel's own composites. */
/* `.input` variant: the title field's box and type (its focus is the kit's). */
.input.cmp-title {
  width: 100%; height: var(--ra-size-control-sm);
  font: var(--ts-label-badge);
  padding: 0 var(--ra-sp-md);
  background-color: var(--ra-k-bg-12);
  border-radius: var(--r-sm);
}
/* `.select` variants (kit: ds/atoms/input.css): the row's field / operator pickers -- 22 tall, r3, ghost
   until hover, the row's own caret glyph and inset (A.select({cls:'cmp-chip-sel'|'cmp-chip-op'}) in renderChip). */
.select.cmp-chip-sel, .select.cmp-chip-op {
  height: var(--ra-size-control-sm);
  padding: 0 var(--ra-xsp-22) 0 var(--ra-sp-sm);
  border-color: transparent;
  border-radius: var(--ra-radius-2xs);
  background-color: transparent;
  background-image: var(--ra-caret-select-muted);
  background-position: right 6px center;
  font: var(--ts-body-small);
  cursor: pointer;
  /* S31 PLAN §2/§5 B3 (dev-b), class D. No :focus-visible peer -- these
     already have their own, more prominent :focus ring below; merging
     hover's cue in would blur "focused" with "focused and open". */
  transition: border-color var(--mo-dur-hover-out), background var(--mo-dur-hover-out);
}
.select.cmp-chip-sel { font-weight: 500; }
.select.cmp-chip-sel:hover, .select.cmp-chip-op:hover {
  background-color: var(--bg-2);
  border-color: var(--line);
  transition-duration: var(--mo-dur-hover-in);
}
.select.cmp-chip-sel:focus, .select.cmp-chip-op:focus {
  outline: none;
  border-color: var(--ra-k-info-55-12-a70);
  box-shadow: 0 0 0 2px var(--ra-k-info-55-12-a15);
}
/* `.input` variant (kit: ds/atoms/input.css): the row's value field (A.input({cls:'cmp-chip-val'})). */
.input.cmp-chip-val {
  /* Regression repair, not P4' redesign work (this file is otherwise
     cut): the `font:` shorthand resets font-variant-numeric to its
     initial value, and this input renders a numeric threshold
     (renderChip in panels/composer/conditions.js, type=number). Under JetBrains Mono this
     aligned for free (fixed-width glyphs); --font-mono now resolves to
     proportional Inter, so it needs tabular-nums explicitly. */
  height: var(--ra-size-control-sm);
  width: 64px;
  font: var(--ts-body-small);
  font-variant-numeric: tabular-nums;
  padding: 0 var(--ra-sp-xs);
  border-radius: var(--ra-radius-2xs);
  background-color: var(--ra-k-bg-12);
}
/* `.input.bare` variant (kit): the search field is drawn by .cmp-catalog-search; its own flex, type and inset. */
.input.bare.cmp-catalog-input {
  flex: 1; min-width: 0;
  font: var(--ts-body-small);
  padding: var(--ra-sp-3xs) 0;
}
.input.bare.cmp-catalog-input::placeholder { color: var(--text-4); }
/* `.select` variant (kit: ds/atoms/input.css): the three action pickers -- 22 tall, r3, this panel's caret glyph. */
.select.cmp-sound-cue {
  height: var(--ra-size-control-sm); padding: 0 var(--ra-xsp-22) 0 var(--ra-sp-sm);
  border-color: var(--line); border-radius: var(--ra-radius-2xs);
  background-color: var(--ra-k-bg-12);
  background-image: var(--ra-caret-select-muted);
  background-position: right 6px center;
  font: var(--ts-body-small); cursor: pointer; transition: initial;
}

/* WP7 automations: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_automations.json). */
/* S51 WP4: this panel composes the kit's atoms; each rule that remains for a primitive is a VARIANT
   (only this panel's deltas from ds/atoms|molecules/*.css), never a re-rolled copy. The census graded
   every element at zero (design/s51/CHAIN.md, wp4-automations). The run-history block (chips, filter,
   table, action pills) is rewritten by the 5 s poll and stays as it is; the strategy rows, the node
   cards, the trigger option cards, the bottom tabs and the live-gate label are this panel's composites. */
/* `.input` variant: the sidebar search box. */
.auto-sb-search .input {
  width: 100%; height: var(--ra-size-control-sm);
  background-color: var(--bg-0);
  border-radius: var(--r-sm);
  font: var(--ts-body-small);
  padding: 0 var(--ra-sp-sm); transition: initial;
}
.auto-sb-search .input::placeholder { color: var(--text-4); }
/* `.input` variant: the strategy's inline-rename field (A.input({cls:'auto-sb-strat-name'})). */
.input.auto-sb-strat-name {
  height: auto;
  font-weight: 500;
  color: var(--text-2);
  letter-spacing: var(--ra-ls-em-04);
  background-color: transparent; border-color: transparent;
  padding: var(--ra-sp-3xs) var(--ra-sp-2xs);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0; max-width: 100%;
  border-radius: var(--r-sm);
  transition: initial;
}
.input.auto-sb-strat-name:focus {
  background-color: var(--bg-0);
  color: var(--text-1);
}
/* `.input` variant: the rule's editable id (A.input({cls:'auto-card-id-edit'})). */
.input.auto-card-id-edit {
  flex: 1; height: auto;
  background-color: transparent; border-color: transparent;
  font-family: var(--font-mono); font-size: var(--ra-fs-13);
  padding: var(--ra-sp-2xs) var(--ra-sp-sm);
  border-radius: var(--r-sm);
  letter-spacing: var(--ra-ls-em-n01);
  min-width: 0; transition: initial;
}
.input.auto-card-id-edit:hover { border-color: var(--line); }
.input.auto-card-id-edit:focus { border-color: var(--info); background-color: var(--bg-0); }
/* `.input` / `.select` variants: the drawer's form controls in mono on the deepest fill; the select keeps its
   native arrow (the kit's caret is reset). The textarea has no kit atom and keeps its own rule. A typed param's controls
   sit in its `.auto-field` cell (panels/automations/drawer.js). */
.auto-row > .input, .auto-field > .input,
.auto-row > textarea, .auto-field > textarea,
.auto-row > .select, .auto-field > .select {
  background-color: var(--bg-0); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: var(--ra-sp-xs) var(--ra-sp-sm);
  font-family: var(--font-mono); font-size: var(--ra-fs-11);
  color: var(--text-1); width: 100%; height: auto;
  box-sizing: border-box;
  outline: none; transition: initial;
}
.auto-row > textarea:focus, .auto-field > textarea:focus,
.auto-row > .select:focus, .auto-field > .select:focus { border-color: var(--info); }
.auto-row > .select, .auto-field > .select { appearance: auto; -webkit-appearance: auto; background-image: none; }
/* `.input` variant: the custom-topic field (A.input({cls:'auto-trig-custom-in'})). */
.auto-trig-custom .input {
  background-color: var(--bg-0);
  border-radius: var(--r-sm); padding: var(--ra-sp-xs) var(--ra-sp-sm);
  font-family: var(--font-mono);
  width: 100%; height: auto;
  margin-bottom: var(--ra-sp-xs); transition: initial;
}

/* WP7 watchlists: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_watchlists.json). */
/* ds/organisms/watchlists-add.css — the watchlists panel's composite, moved verbatim from panels/watchlists/watchlists.css
   (S51 WP6, design/PLAN_S51.md section 7). Delivered with its panel: listed in panels/watchlists/manifest.json
   `styles` by relative path, injected as <style data-panel> exactly as before. Class names unchanged; the look
   composes the kit's atoms (text roles, inks, surfaces) where the extractor placed them. */

  /* .input variant — the name / add boxes (pane.html). */
  .input.wl-in{height:auto;padding:var(--ra-sp-2xs) var(--ra-sp-sm);border-radius:var(--ra-radius-xs);
    background-color:var(--ra-k-bg-14);font:inherit;transition:initial}
  /* The name is the editable field — make it a full-height, obviously-clickable
     target (a thin baseline-height input was easy to miss → felt un-renamable). */
  /* .input variant — the sub-list name field: A.input({cls:'wl-grp-name'}) (watchlists.js makeSection). */
  .wl-sec-head .input.wl-grp-name{height:auto;background-color:transparent;border:0;color:inherit;
    font:inherit;text-transform:inherit;letter-spacing:inherit;padding:var(--ra-sp-3) var(--ra-sp-2xs);border-radius:3px;cursor:text;
    transition:background var(--mo-dur-hover-out),color var(--mo-dur-hover-out)}
  /* No :focus-visible peer here (unlike its siblings) -- this field
     already has its OWN, more prominent :focus treatment two rules down
     (the actual editing state); merging hover's cue into it would blur
     "focused" with "focused AND editing". */
  .wl-sec-head .input.wl-grp-name:hover{background-color:var(--ra-k-bg-13);color:var(--text-1);transition-duration:var(--mo-dur-hover-in)}
  .wl-sec-head .input.wl-grp-name:focus{outline:none;color:var(--text-1);border-color:transparent;
    background-color:var(--ra-k-bg-12);box-shadow:inset 0 -1.5px 0 var(--ra-k-info-66-14)}

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
/* S51 WP4: the briefing head's date picker and refresh are the kit's `.select` / `.btn` (ds/atoms/input.css, button.css);
   each rule below is this panel's variant -- its own 3px radius, 20px box and type over the kit's, the select's native arrow kept. */
.select.oil2-select { height: auto; appearance: auto; -webkit-appearance: auto; transition: initial; background: var(--bg-2); color: var(--text-1); border: 1px solid var(--line); border-radius: 3px; font: inherit; font-size: var(--ra-fs-11); padding: var(--ra-xsp-chip-y) var(--ra-sp-5); }
    /* S51 WP4: `.select` variant -- the briefing date picker: 20px, r3, mono 10, the native arrow kept. */
    .intel-brief .select.brief-date{appearance:auto;-webkit-appearance:auto;height:var(--ra-size-control-sm);background:var(--ra-k-bg-14-a85);color:var(--text-2);border:1px solid var(--line);border-radius:3px;padding:0 var(--ra-sp-xs);font:var(--ra-fs-10) var(--font-mono);cursor:pointer;transition:color var(--mo-dur-hover-out),border-color var(--mo-dur-hover-out)}
    .intel-brief .select.brief-date:hover,.intel-brief .select.brief-date:focus-visible{color:var(--text-1);border-color:var(--ra-k-info-55-14-a55);transition-duration:var(--mo-dur-hover-in)}

/* WP7 review, stats and recorder: their variants (the search and ask fields, the stats date pickers, the recording rename
   field) went with the old Review views, the Stats view and the recordings library on 2026-10-18. */

/* WP7 trading: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_trading.json). */
    /* CD-233 (G38): 700/20/24.2 (was 600/24). */
    /* S51 WP4: `.input.bare` variant (kit: ds/atoms/input.css) -- the box-less symbol field. */
    #p_trad .input.bare.tx-syminput{
      outline:none;
      /* Ticker text (mostly letters), tabular-nums applied for
         consistency with sibling numeric fields, harmless either way. */
      color:var(--text-1);font:700 var(--ra-xfs-20)/var(--ra-lh-24-2) var(--font-mono);
      text-transform:uppercase;letter-spacing:var(--ra-ls-em-02);width:100%;
      font-variant-numeric:tabular-nums;
    }
    /* CD-221 (G35): radius 6 (--ra-radius-sm, was the legacy --r-sm=4px). */
    /* S51 WP4: `.input` variant (kit: ds/atoms/input.css) -- the ticket's fields: auto height, 8/10 inset, body-medium type. */
    #p_trad .input.tx-input{
      height:auto;transition:initial;
      background:var(--bg-2);border:1px solid var(--line);color:var(--text-1);
      padding:var(--ra-sp-sm) var(--ra-sp-md);border-radius:var(--ra-radius-sm);font:var(--ts-body-medium);width:100%;
      box-sizing:border-box;
    }
    #p_trad .tt-sub .input{width:120px;height:var(--ra-size-control-sm);font:var(--ts-body-small)}

/* WP7 harness-chat: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_harness-chat.json). */
/* S51 WP4: the shell's controls carry the kit classes (pane.html and shellHtml() in sync); each rule that
   remains for a primitive is a VARIANT -- this file's deltas from ds/atoms/*.css, with the same literal
   fallbacks the rest of this file keeps so the shell still renders without the DS stylesheet. The census
   graded the route at zero (design/s51/CHAIN.md, wp4-harness-chat). */
/* `.input` variant: the symbol field. */
.input.hc-sym {
  width: 84px; height: var(--ra-size-control-sm); padding: 0 var(--ra-sp-sm);
  background: var(--ra-surface-field, #1A1A1A);
  border-color: var(--ra-surface-card-elevated, #2A2F33);
  color: var(--ra-text-primary, #EEF1F1);
  font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls);
  text-transform: uppercase; outline: 0; transition: initial;
}
/* `.input` / `.select` variants: the settings fields (the select keeps its native arrow). Since 2026-10-13 they sit in
   the Desk AI settings dialog (harness-chat.js); the token's bare field is drawn by its box (`.hc-cfg-secret`, an
   `.input` holding the field and its eye: Figma input State=text+icon 3025:1745), so the variant skips it. */
.hc-settings .input:not(.bare), .hc-settings .select {
  height: var(--ra-size-control-sm); padding: 0 var(--ra-sp-sm);
  background: var(--ra-surface-field, #1A1A1A);
  border-color: var(--ra-surface-card-elevated, #2A2F33);
  color: var(--ra-text-primary, #EEF1F1);
  font: var(--ts-body-small); outline: 0; transition: initial;
}
.hc-settings .input:focus-within { border-color: var(--info); }
.hc-settings .select:focus { border-color: var(--ra-accent-dark, #35A08B); }
.hc-settings .select { appearance: auto; -webkit-appearance: auto; }
/* == ds/atoms/segmented.variants.css == */
/* ds/atoms/segmented.variants.css — the panels' named variants of ds/atoms/segmented.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/segmented.css; edit one panel's state here. */

/* WP7 screener: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_screener.json). */
/* task #9 (CD-S44-080) — the `Net | Split` segmented control that replaces
   the S43 NET-tick checkbox (`.gex-net-toggle`, retired — see gex.js's
   DEFAULT_PREFS comment for why it's a mode now, not a boolean). Wired
   through the shared `PV2.ui.seg()` (js/core/ui.js), which only toggles
   `.on`.
   S46 task #49 (GEX_HEADER_SPEC_S46.md §4.2) — REVERSES the plain-border
   look this control used to share with the (now-dead, task #47/#49) expiry-
   count buttons: that reason no longer applies once this is the header's
   only control, and the frame's track+inset-pill treatment is the SAME
   segmented pattern the shared `.segmented` class (components.css) already
   draws elsewhere in the app — reused here as values on this file's OWN
   class, not by switching to `.segmented` itself: that shared class layers
   PER-CONTEXT colour overrides in components.css (a file outside this
   task's scope) whose base `.on` rule sets an accent-coloured LABEL, not
   this frame's white one — adopting it unmodified would ship the wrong
   label colour, and adding a context override belongs to whoever owns that
   file. Every value below is the SAME token this session's colour audit
   already names for these hexes (§6): no literal, no lookalike.
   Frame defect (c), corrected rather than reproduced: the frame draws the
   selected segment 20px tall inset in a 24px track; every segment here is
   the SAME height instead (no explicit height at all — padding does it).

   S47 task #8 (FRAME_READS_S47.md §A.4) — "the SAME component as Net |
   Split" (brief §20.3) means the same RECIPE (zero component instances
   exist anywhere in the file, on this control or the five others it
   reproduces across). `Horizontal | Vertical` (gex.js orientSegHtml)
   reuses this EXACT class, unmodified — `.gex-orient-seg` on that markup
   is only an identity hook for gex.js's click wiring, it carries no CSS
   of its own. */
/* S51 WP4: the mode / orientation toggles ARE the kit's `.segmented.dx.wide` (ds/atoms/segmented.css: the Review
   recipe -- GP-07's owner ruling that this control matches `.rv-dx-seg`'s 24/20 label-badge geometry, kept there --
   with the 16px cell inset this control and News draw). The rules below keep only this control's deltas: an auto
   height (the kit's 24 is the same box when painted), cells whose label starts at the left edge, no motion. */
.segmented.dx.wide.gex-mode-seg { height: auto; align-items: normal; }

/* WP7 composer: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_composer.json). */
/* `.segmented` variant (kit: ds/atoms/segmented.css): the AND/OR pair's 1px track inset, its r3 cells in
   caption type and an inset-ring active state (M.segmented({cls:'cmp-group-logic'}) in renderGroup). */
.segmented.cmp-group-logic {
  height: auto;
  background: var(--bg-1);   /* restated: the theme layer re-fills .segmented in light, this track never changed */
  border-radius: var(--r-sm);
  padding: var(--ra-xsp-1);
  gap: var(--ra-xsp-hairline);
}


/* Review journal (Figma round 2026-10-16: the P&L card's Bars / Cumulative / Both and the journal's By day / By trade): a
   hairline tray with no fill, 2 inside and between; the cells are 11 Medium uppercase in secondary ink, 4 / 10 inside; the on
   cell is tape-filled in an accent hairline with accent ink. */
.segmented.rj-view { height: auto; padding: var(--ra-sp-3xs); gap: var(--ra-sp-3xs); background: none; border-color: var(--ra-surface-card-elevated); }
.segmented.rj-view button { max-height: none; padding: var(--ra-sp-2xs) var(--ra-sp-md); background: none; color: var(--ra-text-secondary);
  font: var(--ts-h6-table-header); letter-spacing: var(--ts-h6-table-header-ls); text-transform: uppercase; }
.segmented.rj-view button.on { background: var(--ra-surface-tape); border-color: var(--ra-accent-primary); color: var(--ra-accent-primary); font-weight: 500; }
.segmented.rj-view button:not(.on):hover { color: var(--ra-text-primary); }
/* == ds/atoms/tone-tag.variants.css == */
/* ds/atoms/tone-tag.variants.css — the panels' named variants of ds/atoms/tone-tag.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/tone-tag.css; edit one panel's state here. */

/* WP7 composer: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_composer.json). */
/* `.tone-tag` variant (kit: ds/atoms/tone-tag.css): the AND/OR conjunction between rows, 8px mono,
   sentence case (A.toneTag({cls:'cmp-conj', tone}) in renderGroup); tones below unchanged. */
.tone-tag.cmp-conj {
  align-self: flex-start;
  font: 600 var(--ra-xfs-8) var(--font-mono);
  letter-spacing: var(--ra-ls-em-12);
  text-transform: none;
  margin-left: var(--ra-sp-xs);
  line-height: 1.2;
}

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
/* A/B tags on rail rows. CD-414/415/416/417 (G48) — the "A" variant only;
   "B"/on-b are not named by any G48 row and are untouched (also outside
   intelligence.css's committed migrationDebt scope this session, T7.4). */
/* S51 WP4: `.tone-tag` variant (kit: ds/atoms/tone-tag.css; A.toneTag) -- the rail's A / B compare tag, inline, caption type. */
.tone-tag.intel-ind-tag {
  display: inline; text-transform: none; white-space: inherit;
  font: var(--ts-caption-helper);
  padding: var(--ra-xsp-chip-y) var(--ra-sp-2xs); border-radius: 3px;
  background: var(--ra-accent-blue-ink); color: var(--ra-accent-primary);
  letter-spacing: var(--ts-caption-helper-ls);
}
/* S51 WP4: `.tone-tag` variant -- the aside's winner / loser ticker links (A.toneTag({tag:'a'})); the .pos / .neg tones keep their paint. */
.tone-tag.td-mini-tk {
  line-height: inherit; letter-spacing: inherit; text-transform: none; white-space: inherit;
  padding: 0 var(--ra-sp-2xs); border-radius: 2px;
  font-family: var(--font-mono); font-size: var(--ra-fs-10); font-weight: 600;
}
/* S51 WP4: `.tone-tag` variant -- the catalyst category tag, inline; the .tag-* tones keep their paint. */
.tone-tag.td-cat-tag {
  display: inline; line-height: inherit; white-space: inherit;
  font-family: var(--font-mono); font-size: var(--ra-fs-9); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--ra-ls-em-06);
  padding: var(--ra-xsp-chip-y) var(--ra-sp-5); border-radius: 2px;
}
/* Urgency color encoding (P3 readout chips, timeline ticks) */
/* S51 WP4: `.tone-tag` variant -- the urgency chip; the four tones keep their paint. */
.tone-tag.bio2-urg {
  display: inline-flex; align-items: center; gap: var(--ra-sp-2xs);
  line-height: inherit; letter-spacing: inherit; text-transform: none; white-space: inherit;
  padding: var(--ra-xsp-chip-y) var(--ra-sp-xs); border-radius: 2px;
  font-family: var(--font-mono); font-size: var(--ra-fs-10); font-weight: 600;
  border: 1px solid transparent;
}
/* S51 WP4: `.tone-tag` variant -- the on / off constraint flag. */
.tone-tag.hc-flag {
  display: inline-block;
  font-family: inherit; font-weight: inherit; line-height: inherit; white-space: inherit;
  font-size: var(--ra-fs-9);
  text-transform: uppercase;
  letter-spacing: var(--ra-ls-em-04);
  padding: var(--ra-xsp-chip-y) var(--ra-sp-xs);
  border-radius: 2px;
  background: var(--bg-2);
  color: var(--text-4);
  border: 1px solid var(--line);
  margin-right: var(--ra-sp-2xs);
}
/* S51 WP4: `.tone-tag` variant -- the customer share chip. */
.tone-tag.hc-cust {
  display: inline-block;
  font-family: var(--font-mono); font-weight: inherit; line-height: inherit; letter-spacing: inherit; text-transform: none; color: inherit;
  font-size: var(--ra-fs-10);
  padding: var(--ra-xsp-chip-y) var(--ra-sp-5);
  margin-right: var(--ra-sp-2xs);
  background: var(--bg-2);
  border-radius: 2px;
  white-space: nowrap;
}
/* S51 WP4: `.tone-tag` variant -- the supplier / hyperscaler chip; .hs keeps its paint. */
.tone-tag.ai2-ss-chip {
  display: inline; font-weight: inherit; line-height: inherit; letter-spacing: inherit; text-transform: none;
  font-family: var(--font-mono);
  font-size: var(--ra-fs-10); padding: var(--ra-sp-3xs) var(--ra-sp-7);
  border-radius: 3px;
  background: var(--bg-3);
  color: var(--text-2);
  white-space: nowrap;
}
/* =============================================================
   GEOPOLITICAL CONTEXT — 3 severity tiers (active / structural /
   dialogue) with color-coded card borders + status pill + next-
   milestone right rail
   ============================================================= */

/* Freshness chip on the far right of the eyebrow row. Amber when
   refresh is due, neutral when fresh. Reuses --hue-info/70 tone. */
/* S51 WP4: `.tone-tag` variant -- the freshness chip; .stale / .ok keep their paint. */
.tone-tag.ai2-fresh-chip {
  display: inline; font-weight: inherit; line-height: inherit; color: inherit;
  margin-left: auto;
  font-family: var(--font-mono); font-size: var(--ra-fs-10);
  padding: var(--ra-sp-2xs) var(--ra-sp-md); border-radius: 4px;
  letter-spacing: var(--ra-ls-em-06);
  text-transform: none;
  white-space: nowrap;
}
/* S51 WP4: `.tone-tag` variant -- the regional share chip; .pos / .neg keep their paint. */
.tone-tag.semis-chip { display: inline; font-family: inherit; font-weight: inherit; line-height: inherit; letter-spacing: inherit; text-transform: none; color: inherit; white-space: inherit;
  font-size: var(--ra-fs-10); padding: var(--ra-xsp-chip-y) var(--ra-sp-xs); border-radius: 9px; background: var(--ra-k-h250-32-01-a60);
  font-variant-numeric: tabular-nums; }

/* WP7 stats: its variants (the trade side badge, the ctx tags) went with the Stats view on 2026-10-18. */

/* Review journal (Figma round 2026-10-16, «Heat / HOT · WARM · COOL» on the trade rows and in the trade drawer): the news heat at
   entry, 9 Bold, 1 / 5 inside, r4, its tone at 15 % with a 50 % hairline (hot the error tone, warm the warning tone, cool the
   secondary ink at 12 %). */
.tone-tag.rj-heat { font: var(--ts-caption-tight); font-weight: 700; letter-spacing: 0; border-radius: var(--ra-radius-xs); color: var(--rj-heat);
  background: color-mix(in srgb, var(--rj-heat) 15%, transparent); border: var(--ra-border-hairline-w) solid color-mix(in srgb, var(--rj-heat) 50%, transparent); }
.tone-tag.rj-heat.hot { --rj-heat: var(--ra-semantic-error); }
.tone-tag.rj-heat.warm { --rj-heat: var(--ra-semantic-warning); }
.tone-tag.rj-heat.cool { --rj-heat: var(--ra-text-secondary); background: color-mix(in srgb, var(--rj-heat) 12%, transparent); }
/* == ds/atoms/badge.variants.css == */
/* ds/atoms/badge.variants.css — the panels' named variants of ds/atoms/badge.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/badge.css; edit one panel's state here. */

/* WP7 composer: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_composer.json). */
/* `.badge` variant (kit: ds/atoms/badge.css): the 18px type glyph (A.badge({cls:'cmp-saved-tag t-…'}) in renderSavedList); tones below unchanged. */
.badge.cmp-saved-tag {
  width: 18px; height: 18px; min-width: 0; padding: 0;
  border-radius: var(--ra-radius-2xs);
  font: var(--ra-fs-11) var(--font-mono); letter-spacing: normal;
  flex-shrink: 0;
}

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
/* Per-list scroll containers. Each card's list overflows internally
   instead of growing unbounded — keeps the sidebar height bounded so
   the user can see ALL five cards at once even when a single card has
   many items (e.g. the Spanish-locale BREAKING/CATALYSTS titles —
   ÚLTIMO MOMENTO with 12 headlines, CATALIZADORES with 8 events — lang-lint-ignore).
   Different max-heights per list type because
   DISRUPTIONS rows are much taller (title + summary paragraph + ticker
   chip row) than NEXT UP rows (single 11px line).
   Heights tuned so 5-6 rows show at once before scroll kicks in. */
/* CD-487/488/489 (G54): over the shared .badge (components.css) — type
   400/11/16/ls0 (was 600/10/normal), fill text.primary (was secondary),
   padding 6L/R 2T/B with width hugging content (was 0T/B 4L/R + a forced
   min-width). Box fill/radius already matched (T1), untouched. */
#intel-today .badge{
  padding: var(--ra-sp-3xs) var(--ra-sp-xs); min-width: 0; height: auto;
  font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls);
  color: var(--ra-text-primary);
  background: var(--ra-surface-chip);   /* 561:10645 count chip fill #1A2332 */
}

/* WP7 trading: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_trading.json). */
    /* .tx-kbd-mini (the two order-ticket-hint chips) DELETED here — its
       only consumer, pane.html's `.tx-card-meta`, stopped emitting them
       (CD-234, design/PLAN.md S12.8.2 W-B8); `grep -rn tx-kbd-mini
       platform/frontend/` returns 0 hits after this commit. An orphan
       rule left behind is the removal failing quietly (S12-R3). */
    /* Regression repair, same mechanism as trading.css:47's .tx-stat b
       (full explanation there): renders id=ibHkCount, the hotkeys badge
       count. Every #p_trad-scoped rule below with this pattern has at
       least (1,1,0) specificity, which beats .num.num's (0,2,0)
       regardless of class count — ID always outranks classes in CSS
       specificity — so a shared .num fix can never rescue any of these
       even where the element also carries class=num; each needs its own
       trailing declaration. */
    /* CD-245 (G40): badge fill/radius/padding/type — the `3` -> `0` count
       itself is [DATA-STATE] (T4), only the chip skin is worklist. */
    /* S51 WP4: the hotkeys count is the kit's `.badge.count` (ds/atoms/badge.css, built from this recipe); this
       variant keeps the 4px lead and the inherited tracking. */
    #p_trad .badge.count.tx-tab-c{margin-left:var(--ra-sp-2xs);letter-spacing:inherit}
    /* S3-05b — the frame's info affordance (components.css:119 .card-info,
       S3-05a's shared idiom) replaces the ⇱ that used to sit far right
       on each of these three panel-heads. .card-info itself is
       sized/coloured by components.css; nothing to add here. (A
       `.pip-pop{display:none}` suppression rule lived here — deleted:
       dev-a removed shell/pip.js's auto-injection at its source in
       93fa3302, so the selector matches zero elements app-wide. Correct
       when written, made redundant by a later commit in a file this
       fix doesn't own — checked live before deleting, not assumed.) */
    /* ibOrdCount/ibPosCount/ibClosedCount (no class=num) and ibRpSum
       ("Σ +12.34", no class=num either) — both genuinely numeric. */
    /* CD-32/252 (G08, owner-released): the frame draws no counter chip
       here at all (this cluster is title + one info icon only), so there
       is no direct contract to cite — reskinned to the ONE counter-chip
       idiom the frame DOES establish (CD-245's hotkeys badge, same file)
       instead of the app's own invented pill. Hidden at zero in JS
       (loadOrders/loadPositions/loadClosed's _setCount). */
    /* S51 WP4: the activity counts are the kit's `.badge.count` (the same CD-245 idiom); hide-at-zero is the kit's. */
    #p_trad .badge.count.tx-act-count{margin-left:var(--ra-sp-2xs);letter-spacing:inherit}
/* == ds/atoms/status-pill.variants.css == */
/* ds/atoms/status-pill.variants.css — the panels' named variants of ds/atoms/status-pill.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/status-pill.css; edit one panel's state here. */

/* WP7 composer: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_composer.json). */
/* `.status-pill` variant (kit: ds/atoms/status-pill.css): the preview's match-count pill -- 22 tall, 1px stroke,
   its dot a ::before (the 1 Hz tick writes textContent, which would wipe a dot element), tones as classes. */
.status-pill.cmp-prev-status {
  height: var(--ra-size-badge-sm);
  padding: 0 var(--ra-sp-md);
  border: 1px solid var(--line);
  font: var(--ts-label-stat); letter-spacing: inherit;
  background: var(--ra-k-bg-18-a80);
  color: var(--text-2);
}
.status-pill.cmp-prev-status::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.status-pill.cmp-prev-status:empty { display: none; }
.status-pill.cmp-prev-status.warn {
  color: var(--warn);
  border-color: var(--ra-k-h85-55-12-a50);
}
.status-pill.cmp-prev-status.info {
  color: var(--info);
  border-color: var(--ra-k-info-55-12-a55);
  background: var(--info-bg);
}

/* WP7 automations: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_automations.json). */
/* `.status-pill` variant: the live / paused pill -- 22 tall, 1px stroke, mono uppercase, its dot a ::before
   (A.statusPill({cls:'auto-card-live', dot:false}) in renderWorkspace; `.on` is the armed tone). */
.status-pill.auto-card-live {
  height: var(--ra-size-badge-sm);
  padding: 0 var(--ra-sp-md);
  border: 1px solid var(--line);
  font-size: var(--ra-fs-10); font-weight: normal; line-height: normal;
  letter-spacing: var(--ra-ls-em-08);
  text-transform: uppercase;
  color: var(--text-2);
  background: var(--bg-1);
  font-family: var(--font-mono);
}
.status-pill.auto-card-live::before {
  content: '';
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-4);
}
.status-pill.auto-card-live.on {
  color: var(--pos);
  border-color: var(--pos);
  background: var(--pos-bg);
}
.status-pill.auto-card-live.on::before { background: var(--pos); box-shadow: 0 0 6px var(--pos); }

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
/* Left meta column: status pill + since + ongoing */
/* S51 WP4: `.status-pill` variant (kit: ds/atoms/status-pill.css) -- the geo risk pill: mono 9.5, r3, no stroke; the three
   states keep their paint and their dot colour. */
.status-pill.ai2-geo-pill {
  display: inline-flex; align-items: center; gap: var(--ra-sp-5);
  height: auto; border: 0; white-space: inherit; line-height: inherit; color: inherit;
  font-family: var(--font-mono); font-size: var(--ra-fs-10);
  padding: var(--ra-sp-3) var(--ra-sp-7); border-radius: 3px;
  letter-spacing: var(--ra-ls-em-10); font-weight: 500;
  margin-bottom: var(--ra-sp-md);
}

/* WP7 review and stats: their variants (the dashboard's broker badge, the stats status pill) went with the old Review views
   and the Stats view on 2026-10-18. */

/* WP7 harness-chat: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_harness-chat.json). */
/* `.status-pill` variant: the connection state pill (dot = the kit's `.dot`, 7px here; phases below unchanged). */
.status-pill.hc-status {
  margin-left: auto; height: var(--ra-size-badge-sm); padding: 0 var(--ra-sp-sm);
  border-radius: var(--ra-radius-pill, 27px);
  background: var(--ra-surface-header, #0D1114);
  border: 1px solid var(--ra-surface-card-elevated, #2A2F33);
  font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls);
  color: var(--ra-text-secondary, #9AA1A4);
  max-width: 46%; overflow: hidden; white-space: inherit;
}
/* == ds/atoms/switch.variants.css == */
/* ds/atoms/switch.variants.css — the panels' named variants of ds/atoms/switch.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/switch.css; edit one panel's state here. */

/* WP7 automations: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_automations.json). */
/* `.switch` variant (kit anatomy, ds/atoms/switch.css): the strategy master switch at 24x14, a
   1px-inset 10px thumb and the green ON tone (A.toggle({cls:'auto-sb-strat-toggle'})). */
.switch.auto-sb-strat-toggle { width: 24px; height: 14px; }
/* Sidebar / drawer toggle (rule enable + live trading switch). */
/* `.switch` variant (kit anatomy): the rule's enable switch at 32x18, a 1px-inset 14px thumb and the
   green ON tone (A.toggle({cls:'auto-toggle'}) in renderWorkspace). */
.switch.auto-toggle { width: 32px; height: 18px; }
/* == ds/atoms/dot.variants.css == */
/* ds/atoms/dot.variants.css — the panels' named variants of ds/atoms/dot.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/dot.css; edit one panel's state here. */

/* WP7 automations: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_automations.json). */
.auto-runs-filter .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-2);
}
.auto-runs-filter .dot.ok   { background: var(--pos); box-shadow: 0 0 5px var(--pos); }
.auto-runs-filter .dot.fail { background: var(--neg); }
.auto-runs-filter .dot.skip { background: var(--ra-k-h60-78-14); }

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
/* ---------- Catalyst Ribbon ---------- */
/* Matches the global .panel chrome (gradient bg, var(--r) radius,
   ::before radial-glow overlay) so the catalyst ribbon reads as part
   of the same card family as Stats / News / Trading. */
.intel-catalyst-legend .dot {
  width: 8px; height: 8px; border-radius: 50%; display: inline-block;
}
.intel-catalyst-legend .dot.imm  { background: var(--ra-k-h30-70-20); }
.intel-catalyst-legend .dot.near { background: var(--ra-k-h60-78-16); }
.intel-catalyst-legend .dot.mid  { background: var(--ra-k-h200-74-13); }
.intel-catalyst-legend .dot.far  { background: var(--ra-k-bg-55-05); }
/* ds/organisms/intelligence-compare.css — the intelligence panel's composite, moved verbatim from panels/intelligence/intelligence.css
   (S51 WP6, design/PLAN_S51.md section 7). Delivered with its panel: listed in panels/intelligence/manifest.json
   `styles` by relative path, injected as <style data-panel> exactly as before. Class names unchanged; the look
   composes the kit's atoms (text roles, inks, surfaces) where the extractor placed them. */

/* ---------- Header — compare title ---------- */
.intel-cmp-chip .dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.intel-cmp-chip.a .dot { background: var(--info); }
.intel-cmp-chip.b .dot { background: var(--ra-k-h30-78-16); }
/* ds/organisms/intelligence-ai-brief.css — the intelligence panel's composite, moved verbatim from panels/intelligence/intelligence.css
   (S51 WP6, design/PLAN_S51.md section 7). Delivered with its panel: listed in panels/intelligence/manifest.json
   `styles` by relative path, injected as <style data-panel> exactly as before. Class names unchanged; the look
   composes the kit's atoms (text roles, inks, surfaces) where the extractor placed them. */

/* =============================================================
   AI Infrastructure & Power — dedicated briefing layout (ai2-*)
   Mirror of bio2/oil2 pattern for the AI/Infra vertical. Pairs
   with renderAIInfraBriefing(p) in intelligence.js.
   ============================================================= */

/* TODAY'S PULSE — 4 (or 5) separate stat cards in a row.
   Each card: label · value · sub-label · status dot · mini sparkline.
   Replaces the single big-hero design with a cleaner card grid that
   matches the target mockup pixel-for-pixel while preserving all
   data fields (sector flow / AI mix / guides raised / catalysts /
   news / geo — split across cards). */

.ai2-pulse-card .dot {
  position: absolute; top: 14px; right: 14px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--text-4);
  box-shadow: 0 0 6px transparent;
}
.ai2-pulse-card.tone-pos  .dot { background: var(--pos);  box-shadow: 0 0 8px var(--ra-k-pos-70-14-a60); }
.ai2-pulse-card.tone-warn .dot { background: var(--ra-k-h70-82-16); box-shadow: 0 0 8px var(--ra-k-h70-82-16-a60); }
.ai2-pulse-card.tone-info .dot { background: var(--info); box-shadow: 0 0 8px var(--ra-k-info-78-14-a60); }
.ai2-pulse-card.tone-neg  .dot { background: var(--neg);  box-shadow: 0 0 8px var(--ra-k-neg-70-18-a60); }
.ai2-tk-pulse-eyebrow .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--info); box-shadow: 0 0 6px var(--info);
}
.status-pill.ai2-geo-pill .dot {
  width: 6px; height: 6px; border-radius: 50%; display: inline-block; flex-shrink: 1;
}
.ai2-geo-pill.st-neg  .dot { background: var(--neg); }
.ai2-geo-pill.st-warn .dot { background: var(--ra-k-h70-68-16); }
.ai2-geo-pill.st-info .dot { background: var(--ra-k-info-65-13); }
.ai2-ec-eq-elig .dot {
  width: 6px; height: 6px; border-radius: 50%; display: inline-block;
}
.ai2-ec-eq-elig.yes .dot { background: var(--ra-k-h165-62-16); }
.ai2-ec-eq-elig.no .dot { background: var(--neg); }
.ai2-ec-eq-elig.case-by-case .dot { background: var(--ra-k-h70-68-16); }
.ai2-ec-eq-elig.dim .dot { background: var(--text-4); }

/* WP7 review and stats: their variants (the old record button's dot, the stats broker pill's) went with the old Review views
   and the Stats view on 2026-10-18. */

/* WP7 harness-chat: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_harness-chat.json). */
.status-pill.hc-status .dot { width: 7px; height: 7px; border-radius: var(--ra-radius-full, 999px); background: var(--ra-text-dim, #6B7A88); }
/* == ds/atoms/drag-handle.variants.css == */
/* ds/atoms/drag-handle.variants.css — the panels' named variants of ds/atoms/drag-handle.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/atoms/drag-handle.css; edit one panel's state here. */

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
    /* The handle's own width (8px, components.css .drag-handle.v) is less
       than its 24px grid track; a grid item with a definite width does NOT
       stretch by default, so it was landing flush against the START edge
       of the track (measured live: 0px before it, 16px after — an 8+16
       split, not the intended 8+8+8 gutter). Centring it inside the track
       is what actually produces "8 gap + 8 handle + 8 gap". */
    .intel-grid > .drag-handle{justify-self:center}

/* WP7 trading: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_trading.json). */
    /* A definite-width grid item (.drag-handle.v is 8px, components.css)
       does not stretch by default — it lands flush against its track's
       start edge (measured live: 0px before it, 16px after, not 8+8+8).
       Same fix as Intelligence's two handles this session. */
    #p_trad .tx-grid > .drag-handle{justify-self:center}
/* == ds/molecules/filters-row.variants.css == */
/* ds/molecules/filters-row.variants.css — the panels' named variants of ds/molecules/filters-row.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/molecules/filters-row.css; edit one panel's state here. */

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
    #intel-tabs.filters-row{gap:var(--ra-sp-xs)}
/* == ds/molecules/data-table.variants.css == */
/* ds/molecules/data-table.variants.css — the panels' named variants of ds/molecules/data-table.css: each panel's states of this component, moved here
   verbatim (S51 WP7). Listed after ds/themes/light.css so a variant keeps beating the theme's base rule the way it did from
   its panel; a panel-named state is renamed once a second panel carries the same deltas. Edit the component in
   ds/molecules/data-table.css; edit one panel's state here. */

/* WP7 intelligence: the panel's variants of this component, moved here verbatim with their state rules (class names
   unchanged; a panel-named state is renamed once a second panel carries the same deltas). Each family was
   cascade-checked before it moved (design/s51/wp6/promote_variants.py, promote_intelligence.json). */
    /* G09b (CD-461-465): the §T2.4 header contract, scoped over the shared
       .data-table primitive (components.css).
       CD-464/CD-465 (S7-T4, corrected): the earlier note above (padding
       24 L/R per th, auto-layout left alone) was wrong on both counts —
       checked against the frame node directly (391:4756, metadata):
       "Table Headers" is 1067 wide and its six text children sit EDGE TO
       EDGE (SYM x=24 w=80 -> 104; NAME x=104 w=499 -> 603; PRICE x=603
       w=110 -> 713; 1D% x=713 -> 823; 5D% x=823 -> 933; MCAP x=933 w=110
       -> 1043, then 24 to the 1067 edge). There is NO per-cell padding in
       the frame at all — the 24 L/R lives ONLY on the container (already
       supplied by .panel's own 24px padding, confirmed live: the table
       starts flush at the panel's content edge), and columns are laid
       out at FIXED pixel widths with zero gutter between them, which
       only reads correctly when table-layout is fixed. Doubling the
       container's 24 onto every th (padding:0 24px) was pushing the
       first glyph 24px past the frame's target and opening 48px gaps
       between columns instead of 0. table-layout:fixed + the frame's own
       widths (converted to %, below) fixes CD-465's layout mode per its
       own instruction not to chase the auto-computed per-column pixels
       individually; th padding drops to 0 so the container does the
       inset alone, per the frame's actual mechanism. tbody td padding is
       untouched — CD-464/465 name the header only, and td's 24px was not
       measured against the frame by either row. */
    .intel-peers .data-table{table-layout:fixed}
/* == ds/organisms/login-page.css == */
/* ds/organisms/login-page.css — the login page: wrap, ambient and grid backgrounds, the left pitch, brand, stats, foot, the right column (was css/login.css lines 1-57; S51 WP6, 2026-09-14). Verbatim slice: the
   page list of ds/manifest.json keeps it where the sheet was, so the served bundle keeps its rule sequence. */
/* Login — Rally Alpha (redesign) */
.login-wrap {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  position: relative;
  overflow: hidden;
}
html, body { overflow: auto; }
body { background: var(--bg-0); }

.login-ambient, .login-grid-bg { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.login-ambient::before, .login-ambient::after {
  content: ""; position: absolute;
  width: 700px; height: 700px; border-radius: 50%;
  filter: blur(100px);
}
.login-ambient::before { left: -200px; top: -200px;   background: var(--ra-k-info-50-20); opacity: 0.18; }
.login-ambient::after  { right: -200px; bottom: -200px; background: var(--ra-k-pos-60-15);  opacity: 0.10; }

.login-grid-bg {
  background-image:
    linear-gradient(var(--ra-k-bg-30-02-a18) 1px, transparent 1px),
    linear-gradient(90deg, var(--ra-k-bg-30-02-a18) 1px, transparent 1px);
  background-size: 40px 40px;
  mask-image: linear-gradient(180deg, #000 0, #000 50%, transparent 100%);
  opacity: 0.5;
}

.login-left {
  position: relative; z-index: 2;
  display: flex; flex-direction: column; justify-content: space-between;
  padding: var(--ra-sp-4xl);
  border-right: 1px solid var(--line);
  gap: var(--ra-sp-3xl);
}

.login-brand { display: flex; align-items: center; gap: var(--ra-sp-lg); }
.login-brand .logo { width: 220px; }

.login-pitch { display: flex; flex-direction: column; gap: var(--ra-xsp-18); max-width: 500px; }
.login-kicker { font-size: var(--ra-fs-10); text-transform: uppercase; letter-spacing: var(--ra-ls-em-20); }
.login-pitch h1 { font-size: var(--ra-xfs-48); font-weight: 600; line-height: 1.05; letter-spacing: var(--ra-ls-em-n02); }
.login-pitch p { font-size: var(--ra-fs-13); line-height: 1.6; max-width: 440px; }
.login-stats { display: flex; gap: var(--ra-xsp-28); margin-top: var(--ra-sp-sm); }
.login-stats .v { font-size: var(--ra-xfs-20); font-weight: 500; }
.login-stats .l { font-size: var(--ra-fs-9); letter-spacing: var(--ra-ls-em-08); margin-top: var(--ra-sp-3xs); }

.login-foot { display: flex; gap: var(--ra-sp-14); font-size: var(--ra-fs-10); }

.login-right {
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  padding: var(--ra-sp-4xl);
}
/* == ds/organisms/login-card.css == */
/* ds/organisms/login-card.css — the login card: fields, remember, submit, error (was css/login.css lines 58-106; S51 WP6, 2026-09-14). Verbatim slice: the
   page list of ds/manifest.json keeps it where the sheet was, so the served bundle keeps its rule sequence. */
.login-card {
  width: 420px;
  max-width: 100%;
  padding: var(--ra-sp-3xl);
  border-radius: 18px;
  background: linear-gradient(180deg, var(--ra-k-bg-20-015), var(--ra-k-bg-14-01));
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: var(--ra-sp-14);
}
.login-card h2 { font-size: var(--ra-xfs-24); font-weight: 500; letter-spacing: var(--ra-ls-em-n01); margin: 0; }
.login-card .sub { font-size: var(--ra-fs-12); margin-bottom: var(--ra-sp-md); }

.field { display: flex; flex-direction: column; gap: var(--ra-sp-xs); }
/* S20 dev-a, task #7: the bare `.flbl` rule that used to live here was dead
   on this very page -- both `.flbl` spans in login.html sit inside `.field`,
   and css/widgets.css's `.field .flbl` (already token-adopted, task #5) is
   more specific and always won every property it also sets (font, color,
   letter-spacing, text-transform -- all four). Adopting a token onto a rule
   that never renders would look like progress and be none, so it is removed
   instead. */
.login-card .input { font: var(--ts-dropdown-menu-item); letter-spacing: var(--ts-dropdown-menu-item-ls); height: 40px; }

/* The checkbox is the app default (ds/atoms/checkbox.css); it was a hidden input behind a drawn .cbx box. */
.remember { display: flex; align-items: center; gap: var(--ra-sp-md); cursor: pointer; user-select: none; }
.remember .sep { opacity: 0.5; margin: 0 var(--ra-sp-3xs); }

.login-submit { font: var(--ts-h5-widget-title); letter-spacing: var(--ts-h5-widget-title-ls); height: 44px; width: 100%; margin-top: var(--ra-sp-2xs); }
.login-submit .arr { margin-left: var(--ra-sp-2xs); transition: transform var(--mo-dur-nudge); }
.login-submit:hover .arr { transform: translateX(3px); }
/* H24: the busy state while the sign-in POST is out (shell/login.js), one request at a time. */
.login-submit:disabled { opacity: 0.6; cursor: progress; }
.login-submit:disabled .arr { transform: none; }

.err { font: var(--ts-body-small); letter-spacing: var(--ts-body-small-ls); color: var(--neg); min-height: 14px; text-align: center; }
/* == ds/organisms/login-adjust.css == */
/* ds/organisms/login-adjust.css — the login page's light-theme overrides and responsive rules (was css/login.css lines 107-124; S51 WP6, 2026-09-14). Verbatim slice: the
   page list of ds/manifest.json keeps it where the sheet was, so the served bundle keeps its rule sequence. */
/* Light theme overrides */
[data-theme="light"] .login-card { background: linear-gradient(180deg, var(--ra-lt-white), var(--ra-lt-h75-97-006)); }
[data-theme="light"] .login-pitch h1 { color: var(--text-1); }

@media (max-width: 820px) {
  .login-wrap { grid-template-columns: 1fr; }
  .login-left { padding: var(--ra-sp-2xl) var(--ra-xsp-20); border-right: 0; gap: var(--ra-sp-xl); }
  .login-left .login-pitch h1 { font-size: var(--ra-xfs-28); }
  .login-left .login-foot, .login-left .login-stats { display: none; }
  .login-right { padding: var(--ra-xsp-20) var(--ra-xsp-20) var(--ra-xsp-40); }
}
@media (max-width: 480px) {
  .login-left .login-pitch { display: none; }
  .login-card { padding: var(--ra-xsp-22); border-radius: 14px; }
  .login-card h2 { font-size: var(--ra-xfs-20); }
  .login-card .input { height: 36px; }
  .login-submit { height: 40px; }
}
