
  /* ══════════════════════════════════════════════════════════════════════════
     W3 — LIGHT-MODE ORGAN LAYER (§0 gate 7: "light mode is a first-class design
     surface — no dark-first, light-inherits shortcuts")

     The V2 shell was designed for both themes. The organs INSIDE it were not:
     they accreted over a year against #0f1115. Measured at 1366×900 in light with
     getComputedStyle, every view carried text and edges below WCAG AA. Three root
     causes, three fixes — all COMPOSITION, not one literal colour in this block:

     1 · STATE INK. The light palette's state colours are FILL-grade, not TEXT-grade.
         --up #1f9a55 tops out at 3.6:1 on PURE WHITE, yet the organs print it at
         10–13px (2,228 such nodes in the rotation app alone). Rebound inside the
         workspace to inks mixed toward --text until every one clears 4.5:1 on the
         darkest surface the page uses (--panel2 #e8eaed). Mix percentages are keyed
         to HUE, not to the token name: green sits high in luminance and needs far
         more darkening than red. That is why the zh twin below re-keys them — under
         红涨绿跌 --up IS red and --down IS green, so the calibration swaps with them.

     2 · CHROME TOKENS. --rvx-lead/weak/improve/lag are dark-theme literals declared
         once in :root (#3da564/#e0a030/#4f86e0/#d15656) and never given a light
         value — the page's worst offenders (a "Trim" stage chip at 1.96:1). Bound
         here to the STATUS tokens (--ok/--warn/--link/--act), which deliberately do
         not flip with language, so the rotation-map legend keeps in zh exactly the
         hues it has today. (Whether those four SHOULD flip is a doctrine question,
         not a contrast one — deliberately left alone.)

     3 · EDGE. --line #d0d4db draws a 1.37:1 hairline on a panel — a card boundary
         you have to hunt for. Containers get a firmer hairline (1.71:1, matching
         dark's perceptual weight rather than out-defining it); CONTROLS, whose
         border is the affordance and therefore a 1.4.11 "user interface component",
         get a 3.1:1 edge. Static state chips keep a tinted edge and carry their
         state in ink + fill, both of which clear AA on their own.

     Scope discipline: every rule is gated on html[data-theme="light"] AND on the
     workspace subtree, so dark renders byte-identical (A/B measured against a
     pre-W3 render: 72/72 canaries, 0 diffs) and the site nav / footer outside keep the estate
     palette. Shared assets (theme.css, macro-desk.css, sector_cycles.css, and the
     four self-injecting movement modules) are never edited — they ship on other
     pages; each override below names the module it corrects and why.
     ══════════════════════════════════════════════════════════════════════════ */

  /* Inks are COMPUTED here on the page body and REBOUND one level down, on
     .si-stage/.si-side. The two-step is load-bearing, not stylistic: a custom
     property may not reference itself on the element that declares it, so
     `--up: color-mix(… var(--up) …)` on one element is a cycle and silently
     no-ops. Computing on the ancestor and rebinding on the descendant is the
     only form that survives. Body scope (not .si-shell) so the conditional
     shock banner, which renders outside the shell, can reach the same inks. */
  html[data-theme="light"] body.page-baskets {
    --si-ink-up:     color-mix(in srgb, var(--up)     62%, var(--text));  /* 3.32 → 4.82:1 */
    --si-ink-down:   color-mix(in srgb, var(--down)   84%, var(--text));  /* 4.05 → 4.81:1 */
    --si-ink-warn:   color-mix(in srgb, var(--warn)   62%, var(--text));  /* 3.11 → 4.67:1 */
    --si-ink-ok:     color-mix(in srgb, var(--ok)     70%, var(--text));  /* 3.57 → 4.78:1 */
    --si-ink-act:    color-mix(in srgb, var(--act)    88%, var(--text));
    --si-ink-orange: color-mix(in srgb, var(--orange) 58%, var(--text));
    --si-ink-link:   color-mix(in srgb, var(--link)   88%, var(--text));  /* 4.18 → 4.65:1 */
    --si-edge:     color-mix(in srgb, var(--text) 14%, var(--line));  /* 1.37 → 1.71:1 vs panel */
    --si-edge-ctl: color-mix(in srgb, var(--text) 50%, var(--line));  /* 1.37 → 3.11:1 vs bg   */
  }
  /* zh 红涨绿跌 — theme.css swaps --up/--down at the token level, so the mix that
     darkens a green must follow the green, not the name. Red clears AA with far
     less mixing; keeping one percentage for both would print a dour maroon. */
  html[data-theme="light"][data-lang="zh"] body.page-baskets {
    --si-ink-up:   color-mix(in srgb, var(--up)   84%, var(--text));  /* red   */
    --si-ink-down: color-mix(in srgb, var(--down) 62%, var(--text));  /* green */
  }
  html[data-theme="light"] .si-stage,
  html[data-theme="light"] .si-side {
    --up: var(--si-ink-up); --down: var(--si-ink-down); --warn: var(--si-ink-warn);
    --ok: var(--si-ink-ok); --act: var(--si-ink-act); --orange: var(--si-ink-orange);
    --link: var(--si-ink-link); --info: var(--si-ink-link);
    --line: var(--si-edge);
    /* the four dark-only rvx literals finally participate in the theme */
    --rvx-lead: var(--si-ink-ok);   --rvx-weak: var(--si-ink-warn);
    --rvx-improve: var(--si-ink-link); --rvx-lag: var(--si-ink-act);
    /* macro-desk.css owns the actual PANEL hairline through its own tokens, resolved
       once at body scope — rebinding --line alone leaves every card edge untouched
       (the trap that made the first pass measure fixed and paint identical). */
    --desk-hair-strong: var(--si-edge);
    --desk-hair: color-mix(in srgb, var(--si-edge) 92%, transparent);
    --desk-ink-2: color-mix(in srgb, var(--text) 80%, transparent);   /* 4.49 → 5.60:1 */
    /* The rotation-map layer's own hairlines: 9%/15% of --text reads as a crisp
       edge over #0f1115 and as nothing over #eceef1 (1.16:1). Same intent, the
       alpha a light canvas needs. */
    --rvx-hair:  color-mix(in srgb, var(--text) 26%, transparent);   /* 1.16 → 1.68:1 */
    --rvx-hair2: color-mix(in srgb, var(--text) 34%, transparent);   /* 1.29 → 1.96:1 */
    /* glass buttons: theme.css derives these from a text literal at 11%/22%, which
       lands under 1.3:1 on this canvas — give the toggles a real edge. */
    --gbtn-brd: var(--si-edge-ctl);
    --gbtn-brd-hover: color-mix(in srgb, var(--text) 66%, var(--line));
    /* floating glass (the rotation-map hover card) draws its edge at 9% of a text
       literal — invisible against a white page even with the shadow under it. */
    --glass-brd: color-mix(in srgb, var(--text) 22%, transparent);
  }

  /* ── Same-hue tints: the one thing that does NOT survive a theme flip ───────
     A chip that paints `color-mix(<hue> 15–22%, transparent)` behind text of the
     same hue works on #0f1115, where the tint LIGHTENS the surface away from the
     ink. On paper it DARKENS the surface toward the ink, so the pair converges and
     the label drops to 3.7–4.2:1. Thinning the tint (rather than darkening the ink
     further) is the right lever: it keeps the state colours vivid on the ~90% of
     nodes that sit on neutral surfaces, and it is the only lever that does not
     make every green on the page read as forest floor. */
  html[data-theme="light"] .si-stage .rvx-stag { background: color-mix(in srgb, currentColor 8%, var(--panel)); }
  html[data-theme="light"] .si-stage .sr-q { background: color-mix(in srgb, currentColor 8%, transparent); }  /* subsector_rotation.js */
  html[data-theme="light"] .si-stage :is(.sc-chip-all, .sc-bask-all) { background: color-mix(in srgb, var(--link) 8%, var(--panel)); }  /* sector_cycles.css */
  /* The four lane badges hold full-colour EMOJI, which ignore `color` — they are
     graphics on a tint (3:1 bar, measured 4.28–4.46) and keep the lane's identity
     tint at a legible strength rather than being bleached to clear a text bar. */
  html[data-theme="light"] .si-stage .rvx-lane .ic { background: color-mix(in srgb, currentColor 13%, var(--panel)); }
  /* conviction badge: 14% tint pulled "Cautious"/"Constructive" to 4.21:1 */
  html[data-theme="light"] .si-stage :is(.conv-badge.tier-up, .conv-badge.tier-down) { background: color-mix(in srgb, currentColor 8%, transparent); }
  /* the Buy lane's identity border, 30% of a state token, draws at 1.50:1 */
  html[data-theme="light"] .si-stage .rvx-lane.buy { border-color: color-mix(in srgb, var(--rvx-lead) 52%, transparent); }

  /* ── Flow board heat cells ────────────────────────────────────────────────
     The cell tint scales 6%→52% with flow size, so a same-hue numeral is
     unreadable at the top of the scale in EITHER direction (measured 2.30:1 on
     the deepest red — the worst text on the page, and worse still before this
     pass at 2.04:1). No tint thinning fixes it: the tint IS the magnitude read.
     So the cell says direction and size in colour, and the number goes to ink —
     the standard reading of a heat table, and 4.75:1 at the deepest cell. */
  html[data-theme="light"] .si-stage table.scf td.scf-c.pos,
  html[data-theme="light"] .si-stage table.scf td.scf-c.neg { color: var(--text); }

  /* ── subsector_rotation.js · rotation-map legend glyphs ────────────────────
     The module draws its legend from --text at 28–55% alpha: readable as a light
     mark on #0f1115, a ghost on paper (the arrow measured 3.03:1). */
  html[data-theme="light"] .si-stage .sr-lg-arw { color: color-mix(in srgb, var(--text) 80%, transparent); }
  html[data-theme="light"] .si-stage .sr-lg-tail { border-top-color: color-mix(in srgb, var(--text) 62%, transparent); }
  html[data-theme="light"] .si-stage .sr-lg-dot { background: color-mix(in srgb, var(--text) 48%, transparent); }

  /* ── Controls: the border IS the affordance, so it earns the 3:1 bar ──────── */
  html[data-theme="light"] .si-stage :is(
    .tabs button, .tabs .rangeN, .scc-tab, .bcat-chip, .leg-toggle,
    .cyc-zbtn, .cyc-chip, .sc-mbtn, .sc-sbtn, .mini-chip,
    .sr-zc, .sr-toggle, .sr-expand, .sr-vs-more, .rtw-chip-row .rchip,
    .tm-year-chip, .ne-tk, .si-vocab, .si-vr-q, .rcf-help, .ftr-t1-help, .sr-tw-q
  ) { border-color: var(--si-edge-ctl); }

  /* ── State chips whose tint/edge was hand-written in dark-theme rgba ───────
     Each of these hardcodes a #0f1115-era green/red/amber at 8–40% alpha, which
     over a near-white canvas lands at 1.14–1.29:1 — a chip with no visible edge.
     Recomposed over the ink tokens so the tint tracks the theme (and the zh flip)
     instead of a frozen literal. Fills stay light on purpose: a heavier tint
     darkens the surface and pushes the chip's own label back under AA.

     MEMBERSHIP IS EARNED BY STILL HAVING A BOX. This layer is (0,4,1) — it
     out-specifies every de-boxing rule in the V2.1 block below (0,3,0), so a chip
     that V2.1 took to `border:0; background:transparent; padding:2px 0` keeps a
     fill anyway: a colour smear clamped to the glyphs, with no horizontal padding
     to make it read as a chip. Audit the container before adding a selector here.
       · .ftr-strip-chip.up/.dn LEFT (2026-08-03, on main). The session tape's
         movers lost their box in V2.1, so this layer was painting a tint behind
         a chip already at `padding:1px 0` — a smear bleeding into the next mover
         (operator crop). Ink carries the state there now.
       · .fs-chip.headwind/.tailwind LEFT (2026-08-04). Its one instance,
         #factor-season, sits in the hero's receipt row `.rvx-chips:not(.ctx)`,
         which V2.1 de-boxes and paints var(--muted) — no box, no state ink, so
         nothing to recompose. The matching zh tint at the top of this file had to
         go with it — alone, this removal just hands light+zh the OTHER smear.
       · .sr-tw-chip.up/.dn LEFT (2026-08-04). Still a real boxed chip — the
         rotation-map trade-with pills, 1px edge and 2px 7px padding, injected by
         subsector_rotation.js into the `moving` view — but it paints from
         var(--panel2)/var(--line), not a frozen rgba, and it was never in the
         fill list below, so its --si-chip resolved to nothing. Inert, not needed.
       · .tc-chip.* STAY. Their base rules (top of this file) really are frozen
         rgba and they really are boxed pills — no lane emits one on THIS page
         today (only `.tc-chip.lever` and the bare `.si-changed` chips render),
         but the recomposition is what keeps them legible the day one does. */
  html[data-theme="light"] .si-stage :is(.tc-chip.up, .tc-chip.aln) { --si-chip: var(--up); }
  html[data-theme="light"] .si-stage :is(.tc-chip.dn, .tc-chip.was1) { --si-chip: var(--down); }
  html[data-theme="light"] .si-stage .tc-chip.mig { --si-chip: var(--link); }
  html[data-theme="light"] .si-stage :is(
    .tc-chip.up, .tc-chip.dn, .tc-chip.aln, .tc-chip.was1, .tc-chip.mig
  ) {
    background: color-mix(in srgb, var(--si-chip) 9%, var(--panel));
    border-color: color-mix(in srgb, var(--si-chip) 52%, var(--line));
  }

  /* ── sector_cycles.css · phase gallery chip (shared asset — cycle map embed) ──
     `.cyc-gchip.on` paints white on the raw phase hue and light mode re-affirms
     that white. Those hues are dark-theme bright (#2dd4bf teal worst), so the
     selected chip reads 1.09:1 — the single least legible element on the page.
     Inverted here to ink-on-tint: same hue identity, 4.7–6.4:1 across all five
     phases. Scoped to .si-stage so the standalone sector_cycles page is untouched. */
  html[data-theme="light"] .si-stage .cyc-gchip.on {
    color: color-mix(in srgb, var(--ph, var(--link)) 38%, var(--text));
    background: color-mix(in srgb, var(--ph, var(--link)) 10%, var(--panel));
    border-color: color-mix(in srgb, var(--ph, var(--link)) 55%, var(--line));
    box-shadow: none;
  }
  html[data-theme="light"] .si-stage .cyc-gchip.on .gdot { background: currentColor; box-shadow: none; }
  html[data-theme="light"] .si-stage .cyc-gchip.on i { color: inherit; }

  /* ── mini cycle sparkline read-out (conviction cards) ──────────────────────
     The 8px value label is painted by this page's own JS with the phase accent as
     an INLINE colour, which lands at 1.38:1 on white (lime #a3e635 is the worst).
     The phase hue is already carried by .mc-dot right beside it, so the number
     loses nothing by going to ink — and gains 6.5:1. !important is required only
     because the source is an inline style attribute. */
  html[data-theme="light"] .si-stage .mc-read { color: var(--muted) !important; }

  /* ── forming_narratives.js (shared asset — also ships on the baskets page) ──
     The module styles itself with `var(--border, rgba(127,127,127,.25))` and
     `var(--card2, …)`. Neither token exists in this estate's theme, so EVERY card,
     tag and ticker chip falls back to a flat grey that draws at 1.29:1 on light.
     Supplying the missing tokens fixes 47 edges without touching the module or
     duplicating a single rule of it. */
  html[data-theme="light"] .si-stage #forming-narratives {
    --border: var(--si-edge);
    --card2: color-mix(in srgb, var(--text) 5%, var(--panel));
    --fg: var(--text);
  }
  /* the ticker chips are buttons (tabindex + role) but the module styles them
     behind its own id, which outranks the control-edge rule above. */
  html[data-theme="light"] .si-stage #forming-narratives .ne-tk { border-color: var(--si-edge-ctl); }
  /* the one rule in that module with no var() at all: its "hot narrative" flag */
  html[data-theme="light"] .si-stage #forming-narratives .ne-flag {
    color: var(--ink-warn, var(--warn));
    background: color-mix(in srgb, var(--warn) 9%, var(--panel));
    border-color: color-mix(in srgb, var(--warn) 52%, var(--line));
  }
